Spring Validator從零掌握對象校驗的詳細過程
Spring Validator 學習指南:從零掌握對象校驗
一、Validator 接口的作用:你的數據“守門員”
想象你開發(fā)了一個用戶注冊功能,用戶提交的數據可能有各種問題:名字沒填、年齡寫成了負數……這些錯誤如果直接保存到數據庫,會導致后續(xù)流程出錯。Validator 就像一位嚴格的守門員,在數據進入系統前,檢查每個字段是否符合規(guī)則。
核心任務:
- 檢查對象屬性是否合法(如非空、數值范圍)。
- 收集錯誤信息,方便后續(xù)提示用戶。
二、Validator 接口的兩大方法:如何工作?
1. supports(Class clazz)
:我能處理這個對象嗎?
- 作用:判斷當前 Validator 是否支持校驗某個類的對象。
- 關鍵選擇:
- 精確匹配:
return Person.class.equals(clazz);
→ 只校驗Person
類。 - 靈活匹配:
return Person.class.isAssignableFrom(clazz);
→ 支持Person
及其子類。
- 精確匹配:
示例場景:
- 如果你有一個
Student extends Person
,使用equals
時,Student
對象不會被校驗;使用isAssignableFrom
則會校驗。
2. validate(Object target, Errors errors)
:執(zhí)行校驗!
- 作用:編寫具體的校驗規(guī)則,發(fā)現錯誤時記錄到
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", "年齡不能為負數!"); } }
三、處理嵌套對象:如何避免重復代碼?
假設你有一個 Customer
類,包含 Address
對象:
public class Customer { private String firstName; private String surname; private Address address; // 嵌套對象 }
問題:直接在一個 Validator 中校驗所有字段
缺點:
- 若其他類(如
Order
)也包含Address
,需重復編寫地址校驗代碼。 - 維護困難:修改地址規(guī)則時,需改動多處代碼。
正確做法:
拆分 Validator,組合使用!
步驟 1:為每個類創(chuàng)建獨立的 Validator
- AddressValidator(校驗地址):
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(校驗客戶,并復用 AddressValidator):
public class CustomerValidator implements Validator { private final Validator addressValidator; // 通過構造函數注入 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. 校驗客戶的直屬字段(firstName, surname) ValidationUtils.rejectIfEmpty(errors, "firstName", "firstName.empty"); ValidationUtils.rejectIfEmpty(errors, "surname", "surname.empty"); Customer customer = (Customer) target; Address address = customer.getAddress(); // 2. 校驗嵌套的 Address 對象 if (address == null) { errors.rejectValue("address", "address.null"); return; } // 3. 關鍵:切換錯誤路徑到 "address",防止字段名沖突 errors.pushNestedPath("address"); try { ValidationUtils.invokeValidator(addressValidator, address, errors); } finally { errors.popNestedPath(); // 恢復原始路徑 } } }
步驟 2:實際使用
// 創(chuàng)建 Validator AddressValidator addressValidator = new AddressValidator(); CustomerValidator customerValidator = new CustomerValidator(addressValidator); // 準備測試數據 Customer customer = new Customer(); customer.setFirstName(""); // 空名字 customer.setAddress(new Address()); // 空地址 // 執(zhí)行校驗 Errors errors = new BeanPropertyBindingResult(customer, "customer"); customerValidator.validate(customer, errors); // 輸出錯誤 if (errors.hasErrors()) { errors.getAllErrors().forEach(error -> { System.out.println("字段:" + error.getObjectName() + "." + error.getCode()); }); } // 輸出結果: // 字段:customer.firstName.empty // 字段:customer.address.street.empty
四、關鍵技巧與常見問題
1. 錯誤路徑管理
pushNestedPath
和 popNestedPath
:
確保嵌套對象的錯誤字段帶上前綴(如 address.street
),避免與主對象的字段名沖突。
2. 防御性編程
在組合 Validator 時,檢查注入的 Validator 是否支持目標類型:
public CustomerValidator(Validator addressValidator) { if (!addressValidator.supports(Address.class)) { throw new IllegalArgumentException("必須支持 Address 類型!"); } this.addressValidator = addressValidator; }
3. 國際化支持
錯誤代碼(如 firstName.empty
)可對應語言資源文件(如 messages_zh.properties
),實現多語言提示:
# messages_zh.properties firstName.empty=名字不能為空 address.street.empty=街道地址不能為空
五、總結:為什么這樣設計?
- 代碼復用:
AddressValidator
可被其他需要校驗地址的類(如Order
、Company
)直接使用。 - 單一職責:每個 Validator 只負責一個類的校驗,邏輯清晰,易于維護。
- 靈活擴展:新增嵌套對象(如
PaymentInfo
)時,只需創(chuàng)建新的 Validator 并注入,無需修改已有代碼。
3.2. 將錯誤代碼解析為錯誤信息:深入解析與實例演示
一、核心概念:錯誤代碼的多層次解析
當你在 Spring 中調用 rejectValue
方法注冊錯誤時(例如校驗用戶年齡不合法),Spring 不會只記錄你指定的單一錯誤代碼,而是自動生成一組層級化的錯誤代碼。這種設計允許開發(fā)者通過不同層級的錯誤代碼,靈活定義錯誤消息,實現“從具體到通用”的覆蓋策略。
二、錯誤代碼生成規(guī)則
假設在 PersonValidator
中觸發(fā)以下校驗邏輯:
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
)中查找對應的消息。一旦找到匹配項,立即停止搜索。
示例消息資源文件:
# messages.properties too.darn.old.age.int=年齡必須是整數且不超過 120 歲 too.darn.old.age=年齡不能超過 120 歲 too.darn.old=輸入的值不合理
匹配過程:
- 優(yōu)先查找
too.darn.old.age.int
→ 若存在則使用。 - 若未找到,查找
too.darn.old.age
→ 若存在則使用。 - 最后查找
too.darn.old
→ 作為兜底消息。
四、實戰(zhàn)演示:從代碼到錯誤消息
步驟 1:創(chuàng)建實體類與校驗器
// 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=年齡必須是整數且不能超過 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); }); } }
輸出結果:
錯誤消息:年齡必須是整數且不能超過 120 歲
解析:
因為 too.darn.old.age.int
在消息文件中存在,優(yōu)先使用該消息。若刪除這行,則會匹配 too.darn.old.age
,以此類推。
五、自定義錯誤代碼生成策略
默認的 DefaultMessageCodesResolver
生成的代碼格式為:錯誤代碼 + 字段名 + 字段類型
。
若需修改規(guī)則,可自定義 MessageCodesResolver
。
示例:簡化錯誤代碼
@Configuration public class ValidationConfig { @Bean public MessageCodesResolver messageCodesResolver() { DefaultMessageCodesResolver resolver = new DefaultMessageCodesResolver(); resolver.setMessageCodeFormatter(DefaultMessageCodesResolver.Format.POSTFIX_ERROR_CODE); return resolver; } }
效果:
調用 rejectValue("age", "too.darn.old")
生成的代碼變?yōu)椋?/p>
age.too.darn.old
too.darn.old
六、常見問題與解決方案
問題 1:如何查看實際生成的錯誤代碼?
在測試代碼中打印錯誤對象:
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
(類名小寫)。
七、總結:為何需要層級化錯誤代碼?
- 靈活覆蓋:允許針對特定字段或類型定制消息,同時提供通用兜底。
- 國際化友好:不同語言可定義不同層級的消息,無需修改代碼。
- 代碼解耦:校驗邏輯與具體錯誤消息分離,提高可維護性。
學習建議:
- 通過調試觀察
errors.getCodes()
的輸出,深入理解代碼生成規(guī)則。 - 在項目中優(yōu)先使用字段級錯誤代碼(如
too.darn.old.age
),提高錯誤消息的精準度。
到此這篇關于Spring Validator 學習指南:從零掌握對象校驗的文章就介紹到這了,更多相關Spring Validator 對象校驗內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
SpringBoot中使用Guava實現單機令牌桶限流的示例
本文主要介紹了SpringBoot中使用Guava實現單機令牌桶限流的示例,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2023-06-06教你如何把Eclipse創(chuàng)建的Web項目(非Maven)導入Idea
這篇文章主要介紹了教你如何把Eclipse創(chuàng)建的Web項目(非Maven)導入Idea,本文通過圖文并茂的形式給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下2021-04-04淺談Java并發(fā)中ReentrantLock鎖應該怎么用
本文主要介紹了ava并發(fā)中ReentrantLock鎖的具體使用,文中通過示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下2021-11-11