Spring Boot利用JSR303實(shí)現(xiàn)參數(shù)驗(yàn)證的方法實(shí)例
簡(jiǎn)介
JSR-303 是 JAVA EE 6 中的一項(xiàng)子規(guī)范,叫做 Bean Validation。
在任何時(shí)候,當(dāng)你要處理一個(gè)應(yīng)用程序的業(yè)務(wù)邏輯,數(shù)據(jù)校驗(yàn)是你必須要考慮和面對(duì)的事情。應(yīng)用程序必須通過(guò)某種手段來(lái)確保輸入進(jìn)來(lái)的數(shù)據(jù)從語(yǔ)義上來(lái)講是正確的。在通常的情況下,應(yīng)用程序是分層的,不同的層由不同的開發(fā)人員來(lái)完成。很多時(shí)候同樣的數(shù)據(jù)驗(yàn)證邏輯會(huì)出現(xiàn)在不同的層,這樣就會(huì)導(dǎo)致代碼冗余和一些管理的問(wèn)題,比如說(shuō)語(yǔ)義的一致性等。為了避免這樣的情況發(fā)生,最好是將驗(yàn)證邏輯與相應(yīng)的域模型進(jìn)行綁定。
Bean Validation 為 JavaBean 驗(yàn)證定義了相應(yīng)的元數(shù)據(jù)模型和 API。缺省的元數(shù)據(jù)是 Java Annotations,通過(guò)使用 XML 可以對(duì)原有的元數(shù)據(jù)信息進(jìn)行覆蓋和擴(kuò)展。在應(yīng)用程序中,通過(guò)使用 Bean Validation 或是你自己定義的 constraint,例如 @NotNull, @Max, @ZipCode, 就可以確保數(shù)據(jù)模型(JavaBean)的正確性。constraint 可以附加到字段,getter 方法,類或者接口上面。對(duì)于一些特定的需求,用戶可以很容易的開發(fā)定制化的 constraint。Bean Validation 是一個(gè)運(yùn)行時(shí)的數(shù)據(jù)驗(yàn)證框架,在驗(yàn)證之后驗(yàn)證的錯(cuò)誤信息會(huì)被馬上返回。
Bean Validation 規(guī)范內(nèi)嵌的約束注解

實(shí)例
基本應(yīng)用
引入依賴
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-validation</artifactId> </dependency>
給參數(shù)對(duì)象添加校驗(yàn)注解
@Data
public class User {
private Integer id;
@NotBlank(message = "用戶名不能為空")
private String username;
@Pattern(regexp = "^(?![0-9]+$)(?![a-zA-Z]+$)[0-9A-Za-z]{8,16}$", message = "密碼必須為8~16個(gè)字母和數(shù)字組合")
private String password;
@Email
private String email;
private Integer gender;
}
Controller 中需要校驗(yàn)的參數(shù)Bean前添加 @Valid 開啟校驗(yàn)功能,緊跟在校驗(yàn)的Bean后添加一個(gè)BindingResult,BindingResult封裝了前面Bean的校驗(yàn)結(jié)果。
@RestController
@RequestMapping("/user")
public class UserController {
@PostMapping("")
public Result save (@Valid User user , BindingResult bindingResult) {
if (bindingResult.hasErrors()) {
Map<String , String> map = new HashMap<>();
bindingResult.getFieldErrors().forEach( (item) -> {
String message = item.getDefaultMessage();
String field = item.getField();
map.put( field , message );
} );
return Result.build( 400 , "非法參數(shù) !" , map);
}
return Result.ok();
}
}
測(cè)試如下:

異常的統(tǒng)一處理
參數(shù)校驗(yàn)不通過(guò)時(shí),會(huì)拋出 BingBindException 異常,可以在統(tǒng)一異常處理中,做統(tǒng)一處理,這樣就不用在每個(gè)需要參數(shù)校驗(yàn)的地方都用 BindingResult 獲取校驗(yàn)結(jié)果了。
@Slf4j
@RestControllerAdvice(basePackages = "com.itwolfed.controller")
public class GlobalExceptionControllerAdvice {
@ExceptionHandler(value= {MethodArgumentNotValidException.class , BindException.class})
public Result handleVaildException(Exception e){
BindingResult bindingResult = null;
if (e instanceof MethodArgumentNotValidException) {
bindingResult = ((MethodArgumentNotValidException)e).getBindingResult();
} else if (e instanceof BindException) {
bindingResult = ((BindException)e).getBindingResult();
}
Map<String,String> errorMap = new HashMap<>(16);
bindingResult.getFieldErrors().forEach((fieldError)->
errorMap.put(fieldError.getField(),fieldError.getDefaultMessage())
);
return Result.build(400 , "非法參數(shù) !" , errorMap);
}
}
分組解決校驗(yàn)
新增和修改對(duì)于實(shí)體的校驗(yàn)規(guī)則是不同的,例如id是自增的時(shí)候,新增時(shí)id要為空,修改則必須不為空;新增和修改,若用的恰好又是同一種實(shí)體,那就需要用到分組校驗(yàn)。
校驗(yàn)注解都有一個(gè)groups屬性,可以將校驗(yàn)注解分組,我們看下@NotNull的源碼:
@Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE })
@Retention(RUNTIME)
@Repeatable(List.class)
@Documented
@Constraint(validatedBy = { })
public @interface NotNull {
String message() default "{javax.validation.constraints.NotNull.message}";
Class<?>[] groups() default { };
Class<? extends Payload>[] payload() default { };
@Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE })
@Retention(RUNTIME)
@Documented
@interface List {
NotNull[] value();
}
}
從源碼可以看出 groups 是一個(gè)Class<?>類型的數(shù)組,那么就可以創(chuàng)建一個(gè)Groups.
public class Groups {
public interface Add{}
public interface Update{}
}
給參數(shù)對(duì)象的校驗(yàn)注解添加分組
@Data
public class User {
@Null(message = "新增不需要指定id" , groups = Groups.Add.class)
@NotNull(message = "修改需要指定id" , groups = Groups.Update.class)
private Integer id;
@NotBlank(message = "用戶名不能為空")
@NotNull
private String username;
@Pattern(regexp = "^(?![0-9]+$)(?![a-zA-Z]+$)[0-9A-Za-z]{8,16}$", message = "密碼必須為8~16個(gè)字母和數(shù)字組合")
private String password;
@Email
private String email;
private Integer gender;
}
Controller 中原先的@Valid不能指定分組 ,需要替換成@Validated
@RestController
@RequestMapping("/user")
public class UserController {
@PostMapping("")
public Result save (@Validated(Groups.Add.class) User user) {
return Result.ok();
}
}
測(cè)試如下:

自定義校驗(yàn)注解
雖然JSR303和springboot-validator 已經(jīng)提供了很多校驗(yàn)注解,但是當(dāng)面對(duì)復(fù)雜參數(shù)校驗(yàn)時(shí),還是不能滿足我們的要求,這時(shí)候我們就需要 自定義校驗(yàn)注解。
例如User中的gender,用 1代表男 2代表女,我們自定義一個(gè)校驗(yàn)注解@ListValue,指定取值只能1和2。
創(chuàng)建約束規(guī)則
@Documented
@Constraint(validatedBy = { ListValueConstraintValidator.class })
@Target({ METHOD, FIELD, ANNOTATION_TYPE })
@Retention(RUNTIME)
public @interface ListValue {
String message() default "";
Class<?>[] groups() default { };
Class<? extends Payload>[] payload() default { };
int[] vals() default { };
}
一個(gè)標(biāo)注(annotation) 是通過(guò)@interface關(guān)鍵字來(lái)定義的. 這個(gè)標(biāo)注中的屬性是聲明成類似方法 的樣式的. 根據(jù)Bean Validation API 規(guī)范的要求:
- message屬性, 這個(gè)屬性被用來(lái)定義默認(rèn)得消息模版, 當(dāng)這個(gè)約束條件被驗(yàn)證失敗的時(shí)候,通過(guò) 此屬性來(lái)輸出錯(cuò)誤信息。
- groups 屬性, 用于指定這個(gè)約束條件屬于哪(些)個(gè)校驗(yàn)組. 這個(gè)的默認(rèn)值必須是Class<?>類型數(shù)組。
- payload 屬性, Bean Validation API 的使用者可以通過(guò)此屬性來(lái)給約束條件指定嚴(yán)重級(jí)別. 這個(gè)屬性并不被API自身所使用。
除了這三個(gè)強(qiáng)制性要求的屬性(message, groups 和 payload) 之外, 我們還添 加了一個(gè)屬性用來(lái)指定所要求的值. 此屬性的名稱vals在annotation的定義中比較特 殊, 如果只有這個(gè)屬性被賦值了的話, 那么, 在使用此annotation到時(shí)候可以忽略此屬性名稱.
另外, 我們還給這個(gè)annotation標(biāo)注了一些元標(biāo)注( meta annotatioins):
- @Target({ METHOD, FIELD, ANNOTATION_TYPE }): 表示此注解可以被用在方法, 字段或者 annotation聲明上。
- @Retention(RUNTIME): 表示這個(gè)標(biāo)注信息是在運(yùn)行期通過(guò)反射被讀取的.
- @Constraint(validatedBy = ListValueConstraintValidator.class): 指明使用哪個(gè)校驗(yàn)器(類) 去校驗(yàn)使用了此標(biāo)注的元素.
- @Documented: 表示在對(duì)使用了該注解的類進(jìn)行javadoc操作到時(shí)候, 這個(gè)標(biāo)注會(huì)被添加到 javadoc當(dāng)中.
創(chuàng)建約束校驗(yàn)器
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
import java.util.HashSet;
import java.util.Set;
public class ListValueConstraintValidator implements ConstraintValidator<ListValue,Integer> {
private Set<Integer> set = new HashSet<>();
/**
* 初始化方法
*/
@Override
public void initialize(ListValue constraintAnnotation) {
int[] vals = constraintAnnotation.vals();
for (int val : vals) {
set.add(val);
}
}
/**
* 判斷是否校驗(yàn)成功
*
* @param value 需要校驗(yàn)的值
* @param context
* @return
*/
@Override
public boolean isValid(Integer value, ConstraintValidatorContext context) {
return set.contains(value);
}
}
ListValueConstraintValidator定義了兩個(gè)泛型參數(shù), 第一個(gè)是這個(gè)校驗(yàn)器所服務(wù)到標(biāo)注類型(在我們的例子中即ListValue), 第二個(gè)這個(gè)校驗(yàn)器所支持到被校驗(yàn)元素的類型 (即Integer)。
如果一個(gè)約束標(biāo)注支持多種類型的被校驗(yàn)元素的話, 那么需要為每個(gè)所支持的類型定義一個(gè)ConstraintValidator,并且注冊(cè)到約束標(biāo)注中。
這個(gè)驗(yàn)證器的實(shí)現(xiàn)就很平常了, initialize() 方法傳進(jìn)來(lái)一個(gè)所要驗(yàn)證的標(biāo)注類型的實(shí)例, 在本 例中, 我們通過(guò)此實(shí)例來(lái)獲取其vals屬性的值,并將其保存為Set集合中供下一步使 用。
isValid()是實(shí)現(xiàn)真正的校驗(yàn)邏輯的地方, 判斷一個(gè)給定的int對(duì)于@ListValue這個(gè)約束條件來(lái)說(shuō) 是否是合法的。
在參數(shù)對(duì)象中使用@ListValue注解。
@Data
public class User {
@Null(message = "新增不需要指定id" , groups = Groups.Add.class)
@NotNull(message = "修改需要指定id" , groups = Groups.Update.class)
private Integer id;
@NotBlank(message = "用戶名不能為空")
@NotNull
private String username;
@Pattern(regexp = "^(?![0-9]+$)(?![a-zA-Z]+$)[0-9A-Za-z]{8,16}$", message = "密碼必須為8~16個(gè)字母和數(shù)字組合")
private String password;
@Email
private String email;
@ListValue( message = "性別應(yīng)指定相應(yīng)的值" , vals = {1,2} , groups = {Groups.Add.class , Groups.Update.class})
private Integer gender;
}
測(cè)試如下:

源碼地址
https://github.com/gf-huanchupk/SpringBootLearning
參考
- https://www.ibm.com/developerworks/cn/java/j-lo-jsr303/index.html
- https://docs.jboss.org/hibernate/validator/4.3/reference/zh-CN/pdf/hibernate validator reference.pdf
總結(jié)
到此這篇關(guān)于Spring Boot利用JSR303實(shí)現(xiàn)參數(shù)驗(yàn)證的文章就介紹到這了,更多相關(guān)Spring Boot用JSR303參數(shù)驗(yàn)證內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
搭建Spring Boot聚合項(xiàng)目的實(shí)現(xiàn)示例
本文主要介紹了搭建Spring Boot聚合項(xiàng)目的實(shí)現(xiàn)示例,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2025-04-04
Spring?BeanFactory容器的構(gòu)建和使用示例詳解
BeanFactory是Spring框架中的一部分,它提供了IoC(控制反轉(zhuǎn))的實(shí)現(xiàn)機(jī)制,下面小編就來(lái)和大家簡(jiǎn)單聊聊BeanFactory容器的構(gòu)建和使用示例吧2023-07-07
SpringBoot結(jié)合Redis哨兵模式的實(shí)現(xiàn)示例
這篇文章主要介紹了SpringBoot結(jié)合Redis哨兵模式的實(shí)現(xiàn)示例,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-04-04
java在hashmap初始化時(shí)賦初值過(guò)程解析
這篇文章主要介紹了java在hashmap初始化時(shí)賦初值過(guò)程解析,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2019-10-10
spring-boot中使用spring-boot-devtools的實(shí)現(xiàn)代碼
這篇文章主要介紹了spring-boot中使用spring-boot-devtools的實(shí)現(xiàn)代碼,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2019-11-11
如何動(dòng)態(tài)改變Retrofit的base url和rest版本詳解
這篇文章主要給大家介紹了關(guān)于如何動(dòng)態(tài)改變Retrofit的base url和rest版本的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2018-09-09

