對象反序列化後驗證

Jackson
Remote
0
08:54 PM · Nov 30 ,2025

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 的配置以在反序列化對象後立即驗證對象的方法。 這樣,我們就可以保證之後無法處理無效對象。

user avatar
0 位用戶收藏了這個故事!
收藏

發佈 評論

Some HTML is okay.