1. 概述
在本教程中,我們將學習如何使用 Java 的 Validation 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;
}
}
現在我們創建一個 validStudent.json 文件,其中包含一個有效 Student 對象的 JSON 表示形式:
{
"name": "Daniel"
}
我們將讀取該文件的內容到 InputStream 中。首先,我們定義一個方法,該方法將 InputStream 解析為 Student 對象,並在同一時間對其進行驗證:
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 表示形式,該對象具有長度小於 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 的配置以在反序列化對象後立即驗證對象的方法。 這樣,我們就可以保證之後無法處理無效對象。