欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

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

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

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

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

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

核心任務(wù)

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

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

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

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

示例場(chǎng)景

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

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

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

示例代碼

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ù)!");
    }
}

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

假設(shè)你有一個(gè) Customer 類(lèi),包含 Address 對(duì)象:

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

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

缺點(diǎn)

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

正確做法:

拆分 Validator,組合使用!

步驟 1:為每個(gè)類(lèi)創(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)客戶(hù),并復(fù)用 AddressValidator):

public class CustomerValidator implements Validator {
    private final Validator addressValidator;
    // 通過(guò)構(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)客戶(hù)的直屬字段(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 對(duì)象
        if (address == null) {
            errors.rejectValue("address", "address.null");
            return;
        }
        // 3. 關(guān)鍵:切換錯(cuò)誤路徑到 "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)備測(cè)試數(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);
// 輸出錯(cuò)誤
if (errors.hasErrors()) {
    errors.getAllErrors().forEach(error -> {
        System.out.println("字段:" + error.getObjectName() + "." + error.getCode());
    });
}
// 輸出結(jié)果:
// 字段:customer.firstName.empty
// 字段:customer.address.street.empty

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

1. 錯(cuò)誤路徑管理

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

2. 防御性編程

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

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

3. 國(guó)際化支持

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

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

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

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

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

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

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

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

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

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

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

  • too.darn.old.age.int字段名 + 錯(cuò)誤代碼 + 字段類(lèi)型
  • too.darn.old.age字段名 + 錯(cuò)誤代碼
  • too.darn.old原始錯(cuò)誤代碼

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

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

示例消息資源文件

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

匹配過(guò)程

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

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

步驟 1:創(chuàng)建實(shí)體類(lèi)與校驗(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 中定義:

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

步驟 3:編寫(xiě)測(cè)試代碼

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

輸出結(jié)果

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

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

五、自定義錯(cuò)誤代碼生成策略

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

示例:簡(jiǎn)化錯(cuò)誤代碼

@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

六、常見(jiàn)問(wèn)題與解決方案

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

在測(cè)試代碼中打印錯(cuò)誤對(duì)象:

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

輸出

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

問(wèn)題 2:字段類(lèi)型在代碼中如何表示?

Spring 使用字段的簡(jiǎn)單類(lèi)名(如 intString)。對(duì)于自定義類(lèi)型(如 Address),代碼中會(huì)使用 address(類(lèi)名小寫(xiě))。

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

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

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

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

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

相關(guān)文章

  • Java基礎(chǔ)必學(xué)TreeSet集合

    Java基礎(chǔ)必學(xué)TreeSet集合

    這篇文章主要介紹了Java必學(xué)基礎(chǔ)TreeSet集合,TreeSet集合實(shí)現(xiàn)了SortedSet接口,?可以對(duì)集合中元素進(jìn)行自然排序,?要求集合中的元素必須是可比較的。下文詳細(xì)介紹需要的朋友可以參考一下
    2022-04-04
  • MyEclipse安裝JS代碼提示的教程(Spket插件)

    MyEclipse安裝JS代碼提示的教程(Spket插件)

    本篇文章主要介紹了MyEclipse安裝JS代碼提示的教程(Spket插件),小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧
    2017-12-12
  • maven打包zip包含bin下啟動(dòng)腳本的完整代碼

    maven打包zip包含bin下啟動(dòng)腳本的完整代碼

    這篇文章主要介紹了maven打包zip包含bin下啟動(dòng)腳本,本文給大家講解的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2021-10-10
  • SpringBoot中使用Guava實(shí)現(xiàn)單機(jī)令牌桶限流的示例

    SpringBoot中使用Guava實(shí)現(xiàn)單機(jī)令牌桶限流的示例

    本文主要介紹了SpringBoot中使用Guava實(shí)現(xiàn)單機(jī)令牌桶限流的示例,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2023-06-06
  • 教你如何把Eclipse創(chuàng)建的Web項(xiàng)目(非Maven)導(dǎo)入Idea

    教你如何把Eclipse創(chuàng)建的Web項(xiàng)目(非Maven)導(dǎo)入Idea

    這篇文章主要介紹了教你如何把Eclipse創(chuàng)建的Web項(xiàng)目(非Maven)導(dǎo)入Idea,本文通過(guò)圖文并茂的形式給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2021-04-04
  • 關(guān)于jvm內(nèi)存如何查看

    關(guān)于jvm內(nèi)存如何查看

    本文介紹了如何在Java虛擬機(jī)(JVM)中進(jìn)行內(nèi)存管理,包括查看JVM內(nèi)存情況的常用方法和工具,這些方法包括使用JDK自帶的工具如jps、jstat、jmap和jconsole,以及一些第三方監(jiān)控工具如Prometheus、Grafana和ElasticStack等,通過(guò)這些方法
    2024-09-09
  • 小米推送Java代碼

    小米推送Java代碼

    今天小編就為大家分享一篇關(guān)于小米推送Java代碼,小編覺(jué)得內(nèi)容挺不錯(cuò)的,現(xiàn)在分享給大家,具有很好的參考價(jià)值,需要的朋友一起跟隨小編來(lái)看看吧
    2019-01-01
  • Java框架設(shè)計(jì)靈魂之反射的示例詳解

    Java框架設(shè)計(jì)靈魂之反射的示例詳解

    反射就是把Java類(lèi)中的各個(gè)成員映射成一個(gè)個(gè)的Java對(duì)象。本文將通過(guò)示例為大家詳細(xì)講解Java框架設(shè)計(jì)的靈魂:反射,感興趣的可以了解一下
    2022-06-06
  • Spring.Net IOC依賴(lài)注入原理流程解析

    Spring.Net IOC依賴(lài)注入原理流程解析

    這篇文章主要介紹了Spring.Net IOC依賴(lài)注入原理流程解析,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下
    2020-07-07
  • 淺談Java并發(fā)中ReentrantLock鎖應(yīng)該怎么用

    淺談Java并發(fā)中ReentrantLock鎖應(yīng)該怎么用

    本文主要介紹了ava并發(fā)中ReentrantLock鎖的具體使用,文中通過(guò)示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2021-11-11

最新評(píng)論