反序列化後的對象驗證
一、概述
在本教程中,我們將了解如何使用 Java 的驗證 API 在反序列化後驗證對象。
2. 手動觸發驗證
Java 的 bean 驗證 API 在JSR 380中定義。它的一個常見用途是在 Spring 控制器中使用@Valid
註釋參數。但是,在本文中,我們將重點關注控制器之外的驗證。
首先,讓我們編寫一個方法來驗證對象的內容是否符合其驗證約束。為此,我們將從默認驗證器工廠獲取Validator
器。然後,我們將對對象應用validate()
方法。此方法返回一Set
ConstraintViolation
。 ConstraintViolation
封裝了一些關於驗證錯誤的提示。為了簡單起見,我們將拋出一個ConstraintViolationException
以防出現任何驗證問題:
<T> void validate(T t) {
Set<ConstraintViolation<T>> violations = validator.validate(t);
if (!violations.isEmpty()) {
throw new ConstraintViolationException(violations);
}
}
如果我們在對像上調用此方法,如果對像不遵守任何驗證約束,它將拋出異常。可以在具有附加約束的現有對像上的任何時候調用此方法。
3.將驗證納入反序列化過程
我們現在的目標是將驗證合併到反序列化過程中。具體來說,我們將覆蓋 Jackson 的反序列化器以在反序列化後立即執行驗證。這將確保任何時候我們反序列化一個對象,如果它不合規,我們不允許任何進一步的處理。
首先,我們需要覆蓋默認的BeanDeserializer
。 BeanDeserializer
是一個可以反序列化對象的類。我們將要調用基本的反序列化方法,然後將我們的 validate() 方法應用於創建的實例。我們的 BeanDeserializerWithValidation 看起來像這樣:
public class BeanDeserializerWithValidation extends BeanDeserializer {
protected BeanDeserializerWithValidation(BeanDeserializerBase src) {
super(src);
}
@Override
public Object deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
Object instance = super.deserialize(p, ctxt);
validate(instance);
return instance;
}
}
下一步是實現我們自己的BeanDeserializerModifier
。這將允許我們使用BeanDeserializerWithValidation
中定義的行為來改變反序列化過程:
public class BeanDeserializerModifierWithValidation extends BeanDeserializerModifier {
@Override
public JsonDeserializer<?> modifyDeserializer(DeserializationConfig config, BeanDescription beanDesc, JsonDeserializer<?> deserializer) {
if (deserializer instanceof BeanDeserializer) {
return new BeanDeserializerWithValidation((BeanDeserializer) deserializer);
}
return deserializer;
}
}
最後,我們需要創建一個ObjectMapper
並將我們的BeanDeserializerModifier
註冊為一個Module
。 Module
是擴展 Jackson 默認功能的一種方式。讓我們把它包裝在一個方法中:
ObjectMapper getObjectMapperWithValidation() {
SimpleModule validationModule = new SimpleModule();
validationModule.setDeserializerModifier(new BeanDeserializerModifierWithValidation());
ObjectMapper mapper = new ObjectMapper();
mapper.registerModule(validationModule);
return mapper;
}
4. 示例用法:從文件中讀取和驗證對象
我們現在將展示一個如何使用自定義ObjectMapper
的小示例。首先,讓我們定義一個Student
對象。一個Student
有一個名字。名稱長度必須在 5 到 10 個字符之間:
public class Student {
@Size(min = 5, max = 10, message = "Student's name must be between 5 and 10 characters")
private String name;
public String getName() {
return name;
}
}
現在讓我們創建一個包含有效Student
對象的 JSON 表示的validStudent.json
文件:
{
"name": "Daniel"
}
我們將在InputStream
中讀取該文件的內容。首先,讓我們定義將InputStream
解析為Student
對象並同時對其進行驗證的方法。為此,我們要使用我們的ObjectMapper
:
Student readStudent(InputStream inputStream) throws IOException {
ObjectMapper mapper = getObjectMapperWithValidation();
return mapper.readValue(inputStream, Student.class);
}
我們現在可以編寫一個測試,我們將:
- 首先將文件內容讀入
InputStream
- 將
InputStream
轉換為Student
對象 - 檢查
Student
對象的內容是否與預期一致
這個測試看起來像這樣:
@Test
void givenValidStudent_WhenReadStudent_ThenReturnStudent() throws IOException {
InputStream inputStream = getClass().getClassLoader().getResourceAsStream(("validStudent.json");
Student result = readStudent(inputStream);
assertEquals("Daniel", result.getName());
}
同樣,我們可以創建一個invalid.json
文件,其中包含名稱少於 5 個字符的Student
的 JSON 表示形式:
{
"name": "Max"
}
現在我們需要調整測試以檢查是否確實拋出了 ConstraintViolationException。此外,我們可以檢查錯誤消息是否正確:
@Test
void givenStudentWithInvalidName_WhenReadStudent_ThenThrows() {
InputStream inputStream = getClass().getClassLoader().getResourceAsStream("invalidStudent.json");
ConstraintViolationException constraintViolationException = assertThrows(ConstraintViolationException.class, () -> readStudent(inputStream));
assertEquals("name: Student's name must be between 5 and 10 characters", constraintViolationException.getMessage());
}
5.結論
在本文中,我們了解瞭如何覆蓋 Jackson 的配置以在反序列化後立即驗證對象。因此,我們可以保證以後不可能對無效對象進行操作。
一如既往,相關代碼可在 GitHub 上獲得。