瑤海區(qū)網(wǎng)站建設(shè)中國(guó)關(guān)鍵詞官網(wǎng)
@Validated、@Valid和BindingResult
Bean Validation是Java定義的一套基于注解的數(shù)據(jù)校驗(yàn)規(guī)范,比如@Null、@NotNull、@Pattern等,它們位于 javax.validation.constraints這個(gè)包下。
hibernate validator是對(duì)這個(gè)規(guī)范的實(shí)現(xiàn),并增加了一些其他校驗(yàn)注解,如 @NotBlank、@NotEmpty、@Length等,它們位于org.hibernate.validator.constraints這個(gè)包下。
依賴(lài)
hibernate validator框架已經(jīng)集成在 spring-boot-starter-web中,所以無(wú)需再添加其他依賴(lài)。如果不是Spring Boot項(xiàng)目,需要添加如下依賴(lài)。

@Valid和@Validated 區(qū)別
Spring Validation驗(yàn)證框架對(duì)參數(shù)的驗(yàn)證機(jī)制提供了@Validated(Spring's JSR-303規(guī)范,是標(biāo)準(zhǔn)JSR-303的一個(gè)變種)。
javax提供了@Valid,配合BindingResult可以直接提供參數(shù)驗(yàn)證結(jié)果(標(biāo)準(zhǔn)JSR-303規(guī)范)。
@Validation對(duì)@Valid進(jìn)行了二次封裝,在使用上并沒(méi)有區(qū)別,但在分組、注解位置、嵌套驗(yàn)證等功能上有所不同
分組
@Validated:提供了一個(gè)分組功能,可以在入?yún)Ⅱ?yàn)證時(shí),根據(jù)不同的分組采用不同的驗(yàn)證機(jī)制。
@Valid:沒(méi)有分組校驗(yàn)的功能。
注解地方
@Validated:用在類(lèi)型、方法和方法參數(shù)上(類(lèi), 方法, 參數(shù))。但不能用于成員屬性。
@Valid:可以用在方法、構(gòu)造函數(shù)、方法參數(shù)和成員屬性上(方法, 構(gòu)造器, 參數(shù),字段, 泛型),可以用@Valid實(shí)現(xiàn)嵌套驗(yàn)證
兩者是否能用于成員屬性(字段)上直接影響能否提供嵌套驗(yàn)證的功能
如A類(lèi)中引用B類(lèi),且A、B二類(lèi)都有內(nèi)部校驗(yàn),為了使B類(lèi)也生效,在A類(lèi)中引用B類(lèi)時(shí),在B類(lèi)變量上加@Valid注解,如果B類(lèi)為集合等類(lèi)型且不能為空還需要再加@NotEmpty。
BindingResult
BindingResult用在實(shí)體類(lèi)校驗(yàn)信息返回結(jié)果綁定。
該類(lèi)作為方法入?yún)?#xff0c;要寫(xiě)在實(shí)體對(duì)象后面。
@PostMapping("/menus")
public Result addMenu(@RequestBody @Valid Menu menu, BindingResult result) {}
規(guī)則注解
validator內(nèi)置注解

hibernate validator擴(kuò)展注解

分類(lèi)
空與非空
注解 | 支持Java類(lèi)型 | 說(shuō)明 |
@Null | Object | 為null |
@NotNull | Object | 不為null |
@NotBlank | CharSequence | 不為null,且必須有一個(gè)非空格字符 |
@NotEmpty | CharSequence、Collection、Map、Array | 不為null,且不為空(length/size>0) |
Boolean
注解 | 支持Java類(lèi)型 | 說(shuō)明 | 備注 |
@AssertTrue | boolean、Boolean | 為true | 為null有效 |
@AssertFalse | boolean、Boolean | 為false | 為null有效 |
日期
注解 | 支持Java類(lèi)型 | 說(shuō)明 | 備注 |
@Future | Date、 Calendar、 Instant、 LocalDate、 LocalDateTime、 LocalTime、 MonthDay、 OffsetDateTime、 OffsetTime、 Year、 YearMonth、 ZonedDateTime、 HijrahDate、 JapaneseDate、 MinguoDate、 ThaiBuddhistDate | 驗(yàn)證日期為當(dāng)前時(shí)間之后 | 為null有效 |
@FutureOrPresent | Date、 Calendar、 Instant、 LocalDate、 LocalDateTime、 LocalTime、 MonthDay、 OffsetDateTime、 OffsetTime、 Year、 YearMonth、 ZonedDateTime、 HijrahDate、 JapaneseDate、 MinguoDate、 ThaiBuddhistDate | 驗(yàn)證日期為當(dāng)前時(shí)間或之后 | 為null有效 |
@Past | Date、 Calendar、 Instant、 LocalDate、 LocalDateTime、 LocalTime、 MonthDay、 OffsetDateTime、 OffsetTime、 Year、 YearMonth、 ZonedDateTime、 HijrahDate、 JapaneseDate、 MinguoDate、 ThaiBuddhistDate | 驗(yàn)證日期為當(dāng)前時(shí)間之前 | 為null有效 |
@PastOrPresent | Date、 Calendar、 Instant、 LocalDate、 LocalDateTime、 LocalTime、 MonthDay、 OffsetDateTime、 OffsetTime、 Year、 YearMonth、 ZonedDateTime、 HijrahDate、 JapaneseDate、 MinguoDate、 ThaiBuddhistDate | 驗(yàn)證日期為當(dāng)前時(shí)間或之前 | 為null有效 |
數(shù)值
注解 | 支持Java類(lèi)型 | 說(shuō)明 | 備注 |
@Max | BigDecimal、BigInteger, byte、short、int、long以及包裝類(lèi) | 小于或等于 | 為null有效 |
@Min | BigDecimal、BigInteger, byte、short、int、long以及包裝類(lèi) | 大于或等于 | 為null有效 |
@DecimalMax | BigDecimalBigInteger、CharSequence, byte、short、int、long以及包裝類(lèi) | 小于或等于 | 為null有效 |
@DecimalMin | BigDecimal、BigIntegerCharSequence, byte、short、int、long以及包裝類(lèi) | 大于或等于 | 為null有效 |
@Negative | BigDecimal、BigInteger, byte、short、int、long、float、double以及包裝類(lèi) | 負(fù)數(shù) | 為null有效,0無(wú)效 |
@NegativeOrZero | BigDecimal、BigInteger, byte、short、int、long、float、double以及包裝類(lèi) | 負(fù)數(shù)或零 | 為null有效 |
@Positive | BigDecimal、BigInteger, byte、short、int、long、float、double以及包裝類(lèi) | 正數(shù) | 為null有效,0無(wú)效 |
@PositiveOrZero | BigDecimal、BigInteger, byte、short、int、long、float、double以及包裝類(lèi) | 正數(shù)或零 | 為null有效 |
@Digits(integer = 3, fraction = 2) | BigDecimal、BigInteger、CharSequence, byte、short、int、long以及包裝類(lèi) | 整數(shù)位數(shù)和小數(shù)位數(shù)上限 | 為null有效 |
@Length | String | 字符串長(zhǎng)度范圍 | @Length |
@Range | 數(shù)值類(lèi)型和String | 指定范圍 | @Range |
其他
注解 | 支持Java類(lèi)型 | 說(shuō)明 | 備注 |
@Pattern | CharSequence | 匹配指定的正則表達(dá)式 | 為null有效 |
CharSequence | 郵箱地址 | 為null有效,默認(rèn)正則 '.*' | |
@Size | CharSequence、Collection、Map、Array | 大小范圍(length/size>0) | 為null有效 |
@URL | URL地址驗(yàn)證 | @URL |
使用
單參數(shù)校驗(yàn)
需要在參數(shù)前添加注解,而且controller類(lèi)上必須添加@Validated注解。
@RestController
@RequestMapping("/menu")
@Validated // 單參數(shù)校驗(yàn)需要加的注解
public class SysMenuController {@DeleteMapping("/menus")public Result deleteMenu(@NotNull(message = "id不能為空") Long id) {}
}
對(duì)象參數(shù)校驗(yàn)
先在對(duì)象的校驗(yàn)屬性上添加注解,然后在Controller方法的對(duì)象參數(shù)前添加@Valid、@Validated
// 對(duì)象
public class Menu {private Long menuId;@NotNull(message =parentId不能為空")private Long parentId;
}
@PostMapping("/menus")
public Result addMenu(@RequestBody @Valid Menu menu, BindingResult result) {
}
對(duì)象嵌套
// 對(duì)象
public class PagedQueryReqBody<T> {private Integer page_no;private Integer page_row_no;@NotNullprivate String page_flg;@Validprivate T data_request;
}public class DataReqPQ {@NotNullprivate String car_no;
}
// 接口
@PostMapping(value = "/queryParameter")
public Result queryParameter(@RequestBody @Validated PagedQueryReqBody<DataReqPQ> requestMsg, BindingResult result){
}
分組校驗(yàn)
新建組
Validated有自己默認(rèn)的組 Default.class
public interface Update {
}
public interface Add extends Default {
}
// 對(duì)象
public class User {@NotBlank(message = "id不能為空",groups = {Update.class})private String id;private String name;@NotBlank(message = "密碼不能為空",groups = {Add.class})private String password;
}
id屬性的校驗(yàn)屬于Update分組的校驗(yàn)
password屬性的校驗(yàn)屬于Add、Default分組的校驗(yàn)
使用分組
使用默認(rèn)分組:Add分組繼承Default,所以校驗(yàn)password,不校驗(yàn)id
@PostMapping("/addUser")
public Resp addUser(@Validated @RequestBody User uer) {
}
使用Update分組:只校驗(yàn)id,不校驗(yàn)password
@PostMapping("/updateUser")
public Resp updateUser(@Validated(Update.class) @RequestBody User user) {
}
異常處理
全局異常處理類(lèi)
缺少參數(shù)拋出的異常是MissingServletRequestParameterException
單參數(shù)校驗(yàn)失敗后拋出的異常是ConstraintViolationException
get請(qǐng)求的對(duì)象參數(shù)校驗(yàn)失敗后拋出的異常是BindException
post請(qǐng)求的對(duì)象參數(shù)校驗(yàn)失敗后拋出的異常是MethodArgumentNotValidException
不同異常對(duì)象的結(jié)構(gòu)不同,對(duì)異常消息的提取方式也就不同。
@Slf4j
@RestControllerAdvice
public class ExceptionAdvice {@ResponseBody@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)// 設(shè)置狀態(tài)碼為500@ExceptionHandler(MethodArgumentNotValidException.class)public String postExceptionHandler(MethodArgumentNotValidException e){log.error("執(zhí)行異常",e);BindingResult exceptions = e.getBindingResult();if (exceptions.hasErrors()) {}}@ResponseBody@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)// 設(shè)置狀態(tài)碼為500@ExceptionHandler(ConstraintViolationException.class)public String paramExceptionHandler(ConstraintViolationException e){log.error("執(zhí)行異常",e);}
}
BindingResult異常
Controller方法的中處理
@PostMapping("addUser")
public Result addUser(@RequestBody @Valid User user,BindingResult result){//校驗(yàn)到錯(cuò)誤if (result.hasErrors()) {//獲得錯(cuò)誤信息列表List<String> errMsgs = result.getAllErrors().stream().map(DefaultMessageSourceResolvable::getDefaultMessage).collect(toList());String lists = StringUtils.join(lists, ";");return new Result(“” "", lists);}return new Result(“”, "", null);
}
AOP校驗(yàn)
/**
*將此注解加在需要進(jìn)行參數(shù)校驗(yàn)的方法上,
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface ParamValid {
}
@Aspect
@Component
public class ParamValidAspect {private static final Logger log = LoggerFactory.getLogger(ParamValidAspect.class);@Before("@annotation(paramValid)")public void paramValid(JoinPoint point, ParamValid paramValid){Object[] paramObj = point.getArgs();if (paramObj.length > 0){Arrays.stream(paramObj).forEach(e ->{if (e instanceof BindingResult) {BindingResult result = (BindingResult) e;Result errorMap = this.validRequestParams(result);if (errorMap != null){ServletRequestAttributes res = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();HttpServletResponse response = res.getResponse();response.setCharacterEncoding("UTF-8");response.setContentType(MediaType.APPLICATION_JSON_UTF8_VALUE);response.setStatus(HttpStatus.BAD_REQUEST.value());OutputStream output = null;try {output = response.getOutputStream();String error = objectMapper.writeValueAsString(errorMap);//響應(yīng)錯(cuò)誤信息output.write(error.getBytes("UTF-8"));}catch (IOException e){log.error(e.getMessage());}finally{try{if (output != null){output.close();}} catch (IOException e) {log.error(e.getMessage());}}}}});}}/*** 校驗(yàn)*/private Result validRequestParams(BindingResult result) {if (result.hasErrors()) {List<String> errMsgs = result.getAllErrors().stream().map(DefaultMessageSourceResolvable::getDefaultMessage).collect(toList());String lists = StringUtils.join(lists, ";");return new Result("", "", lists);}return null;}
}