最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

Spring Validator從零掌握對象校驗(yàn)的詳細(xì)過程

 更新時間:2025年02月25日 16:06:35   作者:小巫程序Demo日記  
SpringValidator學(xué)習(xí)指南從零掌握對象校驗(yàn),涵蓋Validator接口、嵌套對象處理、錯誤代碼解析等核心概念,幫助開發(fā)者實(shí)現(xiàn)數(shù)據(jù)校驗(yàn)的規(guī)范與高效,本文詳細(xì)介紹Spring Validator從零掌握對象校驗(yàn),感興趣的朋友一起看看吧

Spring Validator 學(xué)習(xí)指南:從零掌握對象校驗(yàn)

一、Validator 接口的作用:你的數(shù)據(jù)“守門員”

想象你開發(fā)了一個用戶注冊功能,用戶提交的數(shù)據(jù)可能有各種問題:名字沒填、年齡寫成了負(fù)數(shù)……這些錯誤如果直接保存到數(shù)據(jù)庫,會導(dǎo)致后續(xù)流程出錯。Validator 就像一位嚴(yán)格的守門員,在數(shù)據(jù)進(jìn)入系統(tǒng)前,檢查每個字段是否符合規(guī)則。

核心任務(wù)

  • 檢查對象屬性是否合法(如非空、數(shù)值范圍)。
  • 收集錯誤信息,方便后續(xù)提示用戶。

二、Validator 接口的兩大方法:如何工作?

1. supports(Class clazz):我能處理這個對象嗎?

  • 作用:判斷當(dāng)前 Validator 是否支持校驗(yàn)?zāi)硞€類的對象。
  • 關(guān)鍵選擇
    • 精確匹配return Person.class.equals(clazz); → 只校驗(yàn) Person 類。
    • 靈活匹配return Person.class.isAssignableFrom(clazz); → 支持 Person 及其子類。

示例場景

  • 如果你有一個 Student extends Person,使用 equals 時,Student 對象不會被校驗(yàn);使用 isAssignableFrom 則會校驗(yàn)。

2. validate(Object target, Errors errors):執(zhí)行校驗(yàn)!

  • 作用:編寫具體的校驗(yàn)規(guī)則,發(fā)現(xiàn)錯誤時記錄到 Errors 對象。
  • 常用工具ValidationUtils 簡化非空檢查。

示例代碼

public void validate(Object target, Errors errors) {
    // 檢查 name 是否為空
    ValidationUtils.rejectIfEmpty(errors, "name", "name.empty");
    Person person = (Person) target;
    // 檢查年齡是否合法
    if (person.getAge() < 0) {
        errors.rejectValue("age", "negative.age", "年齡不能為負(fù)數(shù)!");
    }
}

三、處理嵌套對象:如何避免重復(fù)代碼?

假設(shè)你有一個 Customer 類,包含 Address 對象:

public class Customer {
    private String firstName;
    private String surname;
    private Address address; // 嵌套對象
}

問題:直接在一個 Validator 中校驗(yàn)所有字段

缺點(diǎn)

  • 若其他類(如 Order)也包含 Address,需重復(fù)編寫地址校驗(yàn)代碼。
  • 維護(hù)困難:修改地址規(guī)則時,需改動多處代碼。

正確做法:

拆分 Validator,組合使用!

步驟 1:為每個類創(chuàng)建獨(dú)立的 Validator

  • AddressValidator(校驗(yàn)地址):
public class AddressValidator implements Validator {
    @Override
    public boolean supports(Class<?> clazz) {
        return Address.class.isAssignableFrom(clazz);
    }
    @Override
    public void validate(Object target, Errors errors) {
        ValidationUtils.rejectIfEmpty(errors, "street", "street.empty");
        ValidationUtils.rejectIfEmpty(errors, "city", "city.empty");
    }
}

CustomerValidator(校驗(yàn)客戶,并復(fù)用 AddressValidator):

public class CustomerValidator implements Validator {
    private final Validator addressValidator;
    // 通過構(gòu)造函數(shù)注入 AddressValidator
    public CustomerValidator(Validator addressValidator) {
        this.addressValidator = addressValidator;
    }
    @Override
    public boolean supports(Class<?> clazz) {
        return Customer.class.isAssignableFrom(clazz);
    }
    @Override
    public void validate(Object target, Errors errors) {
        // 1. 校驗(yàn)客戶的直屬字段(firstName, surname)
        ValidationUtils.rejectIfEmpty(errors, "firstName", "firstName.empty");
        ValidationUtils.rejectIfEmpty(errors, "surname", "surname.empty");
        Customer customer = (Customer) target;
        Address address = customer.getAddress();
        // 2. 校驗(yàn)嵌套的 Address 對象
        if (address == null) {
            errors.rejectValue("address", "address.null");
            return;
        }
        // 3. 關(guān)鍵:切換錯誤路徑到 "address",防止字段名沖突
        errors.pushNestedPath("address");
        try {
            ValidationUtils.invokeValidator(addressValidator, address, errors);
        } finally {
            errors.popNestedPath(); // 恢復(fù)原始路徑
        }
    }
}

步驟 2:實(shí)際使用

// 創(chuàng)建 Validator
AddressValidator addressValidator = new AddressValidator();
CustomerValidator customerValidator = new CustomerValidator(addressValidator);
// 準(zhǔn)備測試數(shù)據(jù)
Customer customer = new Customer();
customer.setFirstName(""); // 空名字
customer.setAddress(new Address()); // 空地址
// 執(zhí)行校驗(yàn)
Errors errors = new BeanPropertyBindingResult(customer, "customer");
customerValidator.validate(customer, errors);
// 輸出錯誤
if (errors.hasErrors()) {
    errors.getAllErrors().forEach(error -> {
        System.out.println("字段:" + error.getObjectName() + "." + error.getCode());
    });
}
// 輸出結(jié)果:
// 字段:customer.firstName.empty
// 字段:customer.address.street.empty

四、關(guān)鍵技巧與常見問題

1. 錯誤路徑管理

pushNestedPathpopNestedPath
確保嵌套對象的錯誤字段帶上前綴(如 address.street),避免與主對象的字段名沖突。

2. 防御性編程

在組合 Validator 時,檢查注入的 Validator 是否支持目標(biāo)類型:

public CustomerValidator(Validator addressValidator) {
    if (!addressValidator.supports(Address.class)) {
        throw new IllegalArgumentException("必須支持 Address 類型!");
    }
    this.addressValidator = addressValidator;
}

3. 國際化支持

錯誤代碼(如 firstName.empty)可對應(yīng)語言資源文件(如 messages_zh.properties),實(shí)現(xiàn)多語言提示:

# messages_zh.properties
firstName.empty=名字不能為空
address.street.empty=街道地址不能為空

五、總結(jié):為什么這樣設(shè)計?

  • 代碼復(fù)用AddressValidator 可被其他需要校驗(yàn)地址的類(如 Order、Company)直接使用。
  • 單一職責(zé):每個 Validator 只負(fù)責(zé)一個類的校驗(yàn),邏輯清晰,易于維護(hù)。
  • 靈活擴(kuò)展:新增嵌套對象(如 PaymentInfo)時,只需創(chuàng)建新的 Validator 并注入,無需修改已有代碼。

3.2. 將錯誤代碼解析為錯誤信息:深入解析與實(shí)例演示

一、核心概念:錯誤代碼的多層次解析

當(dāng)你在 Spring 中調(diào)用 rejectValue 方法注冊錯誤時(例如校驗(yàn)用戶年齡不合法),Spring 不會只記錄你指定的單一錯誤代碼,而是自動生成一組層級化的錯誤代碼。這種設(shè)計允許開發(fā)者通過不同層級的錯誤代碼,靈活定義錯誤消息,實(shí)現(xiàn)“從具體到通用”的覆蓋策略。

二、錯誤代碼生成規(guī)則

假設(shè)在 PersonValidator 中觸發(fā)以下校驗(yàn)邏輯:

errors.rejectValue("age", "too.darn.old");

生成的錯誤代碼(按優(yōu)先級從高到低):

  • too.darn.old.age.int字段名 + 錯誤代碼 + 字段類型
  • too.darn.old.age字段名 + 錯誤代碼
  • too.darn.old原始錯誤代碼

三、消息資源文件的匹配策略

Spring 的 MessageSource 會按照錯誤代碼的優(yōu)先級順序,在消息資源文件(如 messages.properties)中查找對應(yīng)的消息。一旦找到匹配項(xiàng),立即停止搜索。

示例消息資源文件

# messages.properties
too.darn.old.age.int=年齡必須是整數(shù)且不超過 120 歲
too.darn.old.age=年齡不能超過 120 歲
too.darn.old=輸入的值不合理

匹配過程

  • 優(yōu)先查找 too.darn.old.age.int → 若存在則使用。
  • 若未找到,查找 too.darn.old.age → 若存在則使用。
  • 最后查找 too.darn.old → 作為兜底消息。

四、實(shí)戰(zhàn)演示:從代碼到錯誤消息

步驟 1:創(chuàng)建實(shí)體類與校驗(yàn)器

// Person.java
public class Person {
    private String name;
    private int age;
    // getters/setters
}
// PersonValidator.java
public class PersonValidator implements Validator {
    @Override
    public boolean supports(Class<?> clazz) {
        return Person.class.isAssignableFrom(clazz);
    }
    @Override
    public void validate(Object target, Errors errors) {
        Person person = (Person) target;
        if (person.getAge() > 120) {
            errors.rejectValue("age", "too.darn.old");
        }
    }
}

步驟 2:配置消息資源文件

src/main/resources/messages.properties 中定義:

# 具體到字段類型
too.darn.old.age.int=年齡必須是整數(shù)且不能超過 120 歲
# 具體到字段
too.darn.old.age=年齡不能超過 120 歲
# 通用錯誤
too.darn.old=輸入的值無效

步驟 3:編寫測試代碼

@SpringBootTest
public class ValidationTest {
    @Autowired
    private MessageSource messageSource;
    @Test
    public void testAgeValidation() {
        Person person = new Person();
        person.setAge(150); // 觸發(fā)錯誤
        Errors errors = new BeanPropertyBindingResult(person, "person");
        PersonValidator validator = new PersonValidator();
        validator.validate(person, errors);
        // 提取錯誤消息
        errors.getFieldErrors("age").forEach(error -> {
            String message = messageSource.getMessage(error.getCode(), null, Locale.getDefault());
            System.out.println("錯誤消息:" + message);
        });
    }
}

輸出結(jié)果

錯誤消息:年齡必須是整數(shù)且不能超過 120 歲

解析
因?yàn)?too.darn.old.age.int 在消息文件中存在,優(yōu)先使用該消息。若刪除這行,則會匹配 too.darn.old.age,以此類推。

五、自定義錯誤代碼生成策略

默認(rèn)的 DefaultMessageCodesResolver 生成的代碼格式為:
錯誤代碼 + 字段名 + 字段類型
若需修改規(guī)則,可自定義 MessageCodesResolver。

示例:簡化錯誤代碼

@Configuration
public class ValidationConfig {
    @Bean
    public MessageCodesResolver messageCodesResolver() {
        DefaultMessageCodesResolver resolver = new DefaultMessageCodesResolver();
        resolver.setMessageCodeFormatter(DefaultMessageCodesResolver.Format.POSTFIX_ERROR_CODE);
        return resolver;
    }
}

效果
調(diào)用 rejectValue("age", "too.darn.old") 生成的代碼變?yōu)椋?/p>

  • age.too.darn.old
  • too.darn.old

六、常見問題與解決方案

問題 1:如何查看實(shí)際生成的錯誤代碼?

在測試代碼中打印錯誤對象:

errors.getFieldErrors("age").forEach(error -> {
    System.out.println("錯誤代碼列表:" + Arrays.toString(error.getCodes()));
});

輸出

錯誤代碼列表:[too.darn.old.age.int, too.darn.old.age, too.darn.old]

問題 2:字段類型在代碼中如何表示?

Spring 使用字段的簡單類名(如 int、String)。對于自定義類型(如 Address),代碼中會使用 address(類名小寫)。

七、總結(jié):為何需要層級化錯誤代碼?

  • 靈活覆蓋:允許針對特定字段或類型定制消息,同時提供通用兜底。
  • 國際化友好:不同語言可定義不同層級的消息,無需修改代碼。
  • 代碼解耦:校驗(yàn)邏輯與具體錯誤消息分離,提高可維護(hù)性。

學(xué)習(xí)建議

  • 通過調(diào)試觀察 errors.getCodes() 的輸出,深入理解代碼生成規(guī)則。
  • 在項(xiàng)目中優(yōu)先使用字段級錯誤代碼(如 too.darn.old.age),提高錯誤消息的精準(zhǔn)度。

到此這篇關(guān)于Spring Validator 學(xué)習(xí)指南:從零掌握對象校驗(yàn)的文章就介紹到這了,更多相關(guān)Spring Validator 對象校驗(yàn)內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • Java?Collection?接口和常用方法綜合詳解

    Java?Collection?接口和常用方法綜合詳解

    Collection派生出三個子接口,Set代表不可重復(fù)的無序集合、List代表可重復(fù)的有序集合、Queue是java提供的隊(duì)列實(shí)現(xiàn),通過它們不斷的擴(kuò)展出很多的集合類,接下來我們詳細(xì)介紹一下
    2021-11-11
  • Java的DataInputStream和DataOutputStream數(shù)據(jù)輸入輸出流

    Java的DataInputStream和DataOutputStream數(shù)據(jù)輸入輸出流

    這里我們來看一下Java的DataInputStream和DataOutputStream數(shù)據(jù)輸入輸出流的使用示例,兩個類分別繼承于FilterInputStream和FilterOutputStream:
    2016-06-06
  • 一文帶你你搞懂Java的3種IO模型

    一文帶你你搞懂Java的3種IO模型

    在Java中,一共有三種IO模型,分別是阻塞IO(BIO)、非阻塞IO(NIO)和異步IO(AIO),本文將給大家詳解的介紹這三種IO模型,文中有相關(guān)的代碼示例,需要的朋友可以參考下
    2023-05-05
  • Spring Boot支持Crontab任務(wù)改造的方法

    Spring Boot支持Crontab任務(wù)改造的方法

    這篇文章主要介紹了Spring Boot支持Crontab任務(wù)改造的方法,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2019-01-01
  • java.io.File的renameTo方法移動文件失敗的解決方案

    java.io.File的renameTo方法移動文件失敗的解決方案

    這篇文章主要介紹了java.io.File的renameTo方法移動文件失敗的解決方案,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2021-07-07
  • java讀取文件:char的ASCII碼值=65279,顯示是一個空字符的解決

    java讀取文件:char的ASCII碼值=65279,顯示是一個空字符的解決

    這篇文章主要介紹了java讀取文件:char的ASCII碼值=65279,顯示是一個空字符的解決,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2020-08-08
  • Java使用PostgreSQL存儲檢索節(jié)氣與季節(jié)的相關(guān)數(shù)據(jù)

    Java使用PostgreSQL存儲檢索節(jié)氣與季節(jié)的相關(guān)數(shù)據(jù)

    這篇文章主要介紹了Java使用PostgreSQL存儲檢索節(jié)氣與季節(jié)的相關(guān)數(shù)據(jù),在眾多檢索領(lǐng)域中,對自然現(xiàn)象的檢索,如節(jié)氣與季節(jié)的檢索,雖然看似簡單,卻蘊(yùn)含著豐富的文化內(nèi)涵和實(shí)用價值,需要的朋友可以參考下
    2025-10-10
  • SpringBoot如何返回頁面的實(shí)現(xiàn)方法

    SpringBoot如何返回頁面的實(shí)現(xiàn)方法

    SpringBoot中使用Controller和頁面的結(jié)合能夠很好地實(shí)現(xiàn)用戶的功能及頁面數(shù)據(jù)的傳遞。本文介紹了如何實(shí)現(xiàn)頁面的返回以及這里面所包含的坑,感興趣的可以了解一下
    2021-07-07
  • java集成大華攝像頭的步驟及常用功能實(shí)現(xiàn)

    java集成大華攝像頭的步驟及常用功能實(shí)現(xiàn)

    大華攝像頭SDK是專為開發(fā)者設(shè)計,用于Windows 64位系統(tǒng)上集成和控制大華網(wǎng)絡(luò)攝像機(jī)的軟件開發(fā)工具包,這篇文章主要介紹了java集成大華攝像頭的步驟及常用功能實(shí)現(xiàn)的相關(guān)資料,需要的朋友可以參考下
    2025-11-11
  • 慢sql提前分析預(yù)警和動態(tài)sql替換-Mybatis-SQL

    慢sql提前分析預(yù)警和動態(tài)sql替換-Mybatis-SQL

    為防止慢SQL問題而開發(fā)的MyBatis組件,該組件能夠在開發(fā)、測試階段自動分析SQL語句,并在出現(xiàn)慢SQL問題時通過Ducc配置實(shí)現(xiàn)動態(tài)替換,從而快速止損
    2025-05-05

最新評論

长岛县| 三门县| 上思县| 广河县| 遂溪县| 酉阳| 灌南县| 黑龙江省| 岚皋县| 临沂市| 昌宁县| 即墨市| 类乌齐县| 凤山市| 正蓝旗| 庆城县| 巧家县| 虞城县| 岱山县| 罗江县| 高要市| 容城县| 上高县| 沭阳县| 舒兰市| 蕉岭县| 陵水| 上高县| 姚安县| 始兴县| 延长县| 邵阳县| 武清区| 从江县| 南昌县| 怀来县| 临洮县| 泸定县| 安多县| 普兰店市| 汽车|