1. 概述
在本教程中,我們將學習如何使用 Java 的 Validation API 在反序列化後驗證對象。
2. 手動觸發驗證
Java 的 Bean 驗證 API 定義在 JSR 380 中。它的一般用法是在 Spring 控制器中使用的帶有 <em >@Valid</em> 註解的參數。然而,在本文中,我們將重點關注在控制器之外的驗證。
首先,我們編寫一個方法來驗證對象的內容是否符合其驗證約束。為此,我們將從默認驗證器工廠中獲取 `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. 示例用法:從文件讀取和驗證對象
我們將展示如何使用我們的自定義 <em >ObjectMapper</em> 的一個小示例。首先,讓我們定義一個 <em >Student</em> 對象。一個 <em >Student</em> 對象具有一個姓名。該姓名長度必須在 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;
}
}現在,讓我們創建一個 validStudent.json 文件,其中包含一個有效 Student 對象的 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 文件,其中包含一個 Student 的 JSON 表示形式,該 Student 的名字長度小於 5 個字符:
{
"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 的配置以在反序列化對象後立即對其進行驗證。 這樣可以確保無法在之後處理無效對象。