@Validated 和 @Valid簡(jiǎn)單介紹
-
JSR(Java Specification Requests)是Java界的重要標(biāo)準(zhǔn)熙卡;JSR又細(xì)分很多標(biāo)準(zhǔn)芒率,其中JSR303就代表Bean Validation
-
@Valid
是使用Hibernate validation
的時(shí)候使用 -
@Validated
是只用Spring Validator
校驗(yàn)機(jī)制使用 - @Valid注解與@Validated注解功能大部分類似
- 兩者的不同主要在于:@Valid屬于javax下的凌受,而@Validated屬于spring下
- @Valid支持嵌套校驗(yàn)决摧、而@Validated不支持
- @Validated支持分組,而@Valid不支持
說(shuō)明:java的JSR303聲明了
@Valid
這類接口示辈,在javax包下寥茫,而Hibernate-validator
對(duì)其進(jìn)行了實(shí)現(xiàn) -
引入相關(guān)依賴:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
注解位置
@Validated
:用在類、方法和方法參數(shù)上顽耳。但不能用于成員屬性(field)@Valid
:可以用在方法坠敷、構(gòu)造函數(shù)、方法參數(shù)和成員屬性(field)上-
在Controller層中射富,放在模型參數(shù)對(duì)象前膝迎。
當(dāng)Controller層中參數(shù)是一個(gè)對(duì)象模型時(shí),只有將@Validated/@Valid直接放在該模型前胰耗,該模型內(nèi)部的字段才會(huì)被校驗(yàn)(如果有對(duì)該模型的字段進(jìn)行約束的話)限次。@PostMapping("/queryPage") public PageResult<AssistMsgVO> queryPage(@Validated @RequestBody AssistMsgPageRequest assistMsgPageRequest) { if (assistMsgPageRequest.getMsgType() != null && MsgTypeEnum.fromCode(assistMsgPageRequest.getMsgType()) == null) { throw new BusinessException("消息類型不正確"); } return assistMsgService.queryPage(assistMsgPageRequest); } @Data @ApiModel(value = "AssistMsg分頁(yè)請(qǐng)求參數(shù)", description = "消息表分頁(yè)請(qǐng)求參數(shù)") public class AssistMsgPageRequest implements Serializable { private static final long serialVersionUID = 1L; /** * 頁(yè)碼 */ @ApiModelProperty("頁(yè)碼") @NotNull(message = "頁(yè)碼不能為空") private Long pageNo; /** * 分頁(yè)大小 */ @ApiModelProperty("分頁(yè)大小") @NotNull(message = "分頁(yè)大小不能為空") private Long pageSize; @ApiModelProperty("消息發(fā)送時(shí)間") @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8") private Date msgSendTime; @ApiModelProperty("消息分類") @ConstraintsEnum(message = "消息類型錯(cuò)誤", targetClass = MsgTypeEnum.class,notEmpty = false) private Integer msgType; @ApiModelProperty("狀態(tài)") @ConstraintsEnum(message = "閱讀類型錯(cuò)誤", targetClass = MsgReadStatusEnum.class,notEmpty = false) private Integer hasReaded; }
-
在Controller層中,放在類上柴灯。當(dāng)一些約束是直接出現(xiàn)在Controller層中的參數(shù)前時(shí)卖漫,只有將@Validated放在類上時(shí),參數(shù)前@Max(value=20L)的約束才會(huì)生效
@RestController @Api(tags = "消息表") @RequestMapping("/assistMsg") @Validated public class AssistMsgController { @GetMapping("/unread/count") public Result<Integer> unreadCount(@RequestParam @Max(value=20L) Integer myParam) { return Result.success(assistMsgService.unreadMsgCount()); } }
分組校驗(yàn)
-
@Validated
:提供分組功能赠群,可以在參數(shù)驗(yàn)證時(shí)羊始,根據(jù)不同的分組采用不同的驗(yàn)證機(jī)制 -
@Valid
:沒有分組功能
定義分組接口:
public interface IGroupA {
}
public interface IGroupB {
}
定義需要檢驗(yàn)的參數(shù)bean:
public class StudentBean implements Serializable{
@NotBlank(message = "用戶名不能為空")
private String name;
//只在分組為IGroupB的情況下進(jìn)行驗(yàn)證
@Min(value = 18, message = "年齡不能小于18歲", groups = {IGroupB.class})
private Integer age;
@Pattern(regexp = "^((13[0-9])|(14[5,7,9])|(15([0-3]|[5-9]))|(166)|(17[0,1,3,5,6,7,8])|(18[0-9])|(19[8|9]))\\d{8}$", message = "手機(jī)號(hào)格式錯(cuò)誤")
private String phoneNum;
@Email(message = "郵箱格式錯(cuò)誤")
private String email;
@MyConstraint
private String className;
測(cè)試代碼:
檢驗(yàn)分組為IGroupA的情況
@RestController
public class CheckController {
@PostMapping("stu")
public String addStu(@Validated({IGroupA.class}) @RequestBody StudentBean studentBean){
return "add student success";
}
}
結(jié)果:這里對(duì)分組IGroupB的就沒檢驗(yàn)了
如果把測(cè)試代碼改成下面這樣
@RestController
public class CheckController {
@PostMapping("stu")
public String addStu(@Validated({IGroupA.class, IGroupB.class}) @RequestBody StudentBean studentBean){
return "add student success";
}
}
結(jié)果:對(duì)IGroupB有校驗(yàn)
1、不分 配groups查描,默認(rèn)每次都要進(jìn)行驗(yàn)證
2突委、對(duì)一個(gè)參數(shù)需要多種驗(yàn)證方式時(shí),也可通過(guò)分配不同的組達(dá)到目的冬三。
組序列
默認(rèn)情況下 不同級(jí)別的約束驗(yàn)證是無(wú)序的匀油,但是在一些情況下,順序驗(yàn)證卻是很重要勾笆。
一個(gè)組可以定義為其他組的序列敌蚜,使用它進(jìn)行驗(yàn)證的時(shí)候必須符合該序列規(guī)定的順序。在使用組序列驗(yàn)證的時(shí)候窝爪,如果序列前邊的組驗(yàn)證失敗弛车,則后面的組將不再給予驗(yàn)證。
舉例:
定義組序列:
@GroupSequence({Default.class, IGroupA.class, IGroupB.class})
public interface IGroup {
}
需要校驗(yàn)的Bean蒲每,分別定義IGroupA對(duì)age進(jìn)行校驗(yàn)帅韧,IGroupB對(duì)className進(jìn)行校驗(yàn):
public class StudentBean implements Serializable{
@NotBlank(message = "用戶名不能為空")
private String name;
@Min(value = 18, message = "年齡不能小于18歲", groups = IGroupA.class)
private Integer age;
@Pattern(regexp = "^((13[0-9])|(14[5,7,9])|(15([0-3]|[5-9]))|(166)|(17[0,1,3,5,6,7,8])|(18[0-9])|(19[8|9]))\\d{8}$", message = "手機(jī)號(hào)格式錯(cuò)誤")
private String phoneNum;
@Email(message = "郵箱格式錯(cuò)誤")
private String email;
@MyConstraint(groups = IGroupB.class)
private String className;
測(cè)試代碼:
@RestController
public class CheckController {
@PostMapping("stu")
public String addStu(@Validated({IGroup.class}) @RequestBody StudentBean studentBean){
return "add student success";
}
}
結(jié)果:如果age出錯(cuò),那么對(duì)組序列在IGroupA后的IGroupB不進(jìn)行校驗(yàn)啃勉,即例子中的className不進(jìn)行校驗(yàn)
嵌套校驗(yàn)@Valid忽舟,@Validated不支持嵌套校驗(yàn)
一個(gè)待驗(yàn)證的pojo類,其中還包含了待驗(yàn)證的對(duì)象,需要在待驗(yàn)證對(duì)象上注解@Valid
叮阅,才能驗(yàn)證待驗(yàn)證對(duì)象中的成員屬性刁品,這里不能使用@Validated
。
舉例:
需要約束校驗(yàn)的bean:
public class TeacherBean {
@NotEmpty(message = "老師姓名不能為空")
private String teacherName;
@Min(value = 1, message = "學(xué)科類型從1開始計(jì)算")
private int type;
public class StudentBean implements Serializable{
@NotBlank(message = "用戶名不能為空")
private String name;
@Min(value = 18, message = "年齡不能小于18歲")
private Integer age;
@Pattern(regexp = "^((13[0-9])|(14[5,7,9])|(15([0-3]|[5-9]))|(166)|(17[0,1,3,5,6,7,8])|(18[0-9])|(19[8|9]))\\d{8}$", message = "手機(jī)號(hào)格式錯(cuò)誤")
private String phoneNum;
@Email(message = "郵箱格式錯(cuò)誤")
private String email;
@MyConstraint
private String className;
@NotNull(message = "任課老師不能為空")
@Size(min = 1, message = "至少有一個(gè)老師")
private List<TeacherBean> teacherBeans;
注意:
結(jié)果:這里對(duì)teacherBeans
只校驗(yàn)了NotNull
, 和 Size浩姥,并沒有對(duì)teacher信息里面的字段進(jìn)行校驗(yàn)
這里teacher中的type明顯是不符合約束要求的挑随,但是能檢測(cè)通過(guò),是因?yàn)樵趕tudent中并沒有做 嵌套校驗(yàn)
可以在teacherBeans
中加上 @Valid
勒叠,具體如下:
@Valid
@NotNull(message = "任課老師不能為空")
@Size(min = 1, message = "至少有一個(gè)老師")
private List<TeacherBean> teacherBeans;
自定義校驗(yàn)
第一步:創(chuàng)建自定義注解
@Target({ElementType.FIELD, ElementType.PARAMETER})
@Retention(RUNTIME)
@Documented
@Constraint(validatedBy ={EnumValidator.class})
public @interface ConstraintsEnum {
String message() default " enums Validate error!";
boolean notEmpty() default true;
Class<?> targetClass() default Class.class;
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
message兜挨、payload、groups是必須要寫的眯分,還需要什么方法可根據(jù)自己的實(shí)際業(yè)務(wù)需求
第二步:編寫(第一步中的校驗(yàn)器實(shí)現(xiàn)類)該注解
package com.mdkw.likang.common.annotation;
import lombok.extern.slf4j.Slf4j;
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
import java.lang.reflect.Method;
import java.util.Objects;
@Slf4j
public class EnumValidator implements ConstraintValidator<ConstraintsEnum,Object> {
private final static String METHOD_NAME = "getCode";
private ConstraintsEnum annotation;
@Override
public void initialize(ConstraintsEnum constraintAnnotation) {
this.annotation = constraintAnnotation;
ConstraintValidator.super.initialize(constraintAnnotation);
}
@Override
public boolean isValid(Object value, ConstraintValidatorContext constraintValidatorContext) {
Class<?> cls = annotation.targetClass();
if(cls.isEnum() && !annotation.notEmpty()){
return true;
}
if(cls.isEnum() && null == value){
return false;
}
boolean result = false;
Object[] object = cls.getEnumConstants();
try {
Method method = cls.getMethod(METHOD_NAME);
for (Object o : object) {
Object code = method.invoke(o);
if(Objects.deepEquals(code,value)){
result = true;
break;
}
}
} catch (Exception e) {
log.error("error:",e);
}
return result;
}
}
第三步:使用該注解
package com.mdkw.likang.request;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.mdkw.likang.common.annotation.ConstraintsEnum;
import com.mdkw.likang.enums.MsgReadStatusEnum;
import com.mdkw.likang.enums.MsgTypeEnum;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import javax.validation.constraints.NotNull;
import java.io.Serializable;
import java.util.Date;
@Data
@ApiModel(value = "AssistMsg分頁(yè)請(qǐng)求參數(shù)", description = "消息表分頁(yè)請(qǐng)求參數(shù)")
public class AssistMsgPageRequest implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 頁(yè)碼
*/
@ApiModelProperty("頁(yè)碼")
@NotNull(message = "頁(yè)碼不能為空")
private Long pageNo;
/**
* 分頁(yè)大小
*/
@ApiModelProperty("分頁(yè)大小")
@NotNull(message = "分頁(yè)大小不能為空")
private Long pageSize;
@ApiModelProperty("消息發(fā)送時(shí)間")
@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
private Date msgSendTime;
@ApiModelProperty("消息分類")
@ConstraintsEnum(message = "消息類型錯(cuò)誤", targetClass = MsgTypeEnum.class,notEmpty = false)
private Integer msgType;
}
校驗(yàn)錯(cuò)誤統(tǒng)一處理
- 使用BindingResult類來(lái)容納異常信息拌汇,當(dāng)校驗(yàn)不通過(guò)時(shí),不影響正常程序往下走弊决。我們只需要處理BindingResult中的異常信息即可
@ApiOperation(value = "分頁(yè)查詢")
@PostMapping("/queryPage")
public PageResult<AssistMsgVO> queryPage(@Validated @RequestBody AssistMsgPageRequest assistMsgPageRequest, BindingResult bindingResult) {
if(bindingResult.hasErrors()){
for (ObjectError allError : bindingResult.getAllErrors()) {
System.out.println(allError.getDefaultMessage());
}
}
if (assistMsgPageRequest.getMsgType() != null && MsgTypeEnum.fromCode(assistMsgPageRequest.getMsgType()) == null) {
throw new BusinessException("消息類型不正確");
}
return assistMsgService.queryPage(assistMsgPageRequest);
}
- 如果不采用BindingResult來(lái)容納異常信息時(shí)噪舀,那么異常會(huì)被向外拋出。注解校驗(yàn)不通過(guò)時(shí)飘诗,可能拋出的異常有BindException異常与倡、ValidationException異常(或其子類異常)、MethodArgumentNotValidException異常昆稿。
@Slf4j
@RestControllerAdvice
public class GlobalExceptionHandler {
/**
* 處理Validated校驗(yàn)異常
* <p>
* 注: 常見的ConstraintViolationException異常纺座, 也屬于ValidationException異常
*
* @param e
* 捕獲到的異常
* @return 返回給前端的data
*/
@ResponseStatus(code = HttpStatus.BAD_REQUEST)
@ExceptionHandler(value = {BindException.class, ValidationException.class, MethodArgumentNotValidException.class})
public Map<String, Object> handleParameterVerificationException(Exception e) {
log.error(" handleParameterVerificationException has been invoked", e);
Map<String, Object> resultMap = new HashMap<>(4);
resultMap.put("code", "100001");
String msg = null;
if (e instanceof MethodArgumentNotValidException) {
BindingResult bindingResult = ((MethodArgumentNotValidException) e).getBindingResult();
// getFieldError獲取的是第一個(gè)不合法的參數(shù)(P.S.如果有多個(gè)參數(shù)不合法的話)
FieldError fieldError = bindingResult.getFieldError();
if (fieldError != null) {
msg = fieldError.getDefaultMessage();
}
} else if (e instanceof BindException) {
// getFieldError獲取的是第一個(gè)不合法的參數(shù)(P.S.如果有多個(gè)參數(shù)不合法的話)
FieldError fieldError = ((BindException) e).getFieldError();
if (fieldError != null) {
msg = fieldError.getDefaultMessage();
}
} else if (e instanceof ConstraintViolationException) {
/*
* ConstraintViolationException的e.getMessage()形如
* {方法名}.{參數(shù)名}: {message}
* 這里只需要取后面的message即可
*/
msg = e.getMessage();
if (msg != null) {
int lastIndex = msg.lastIndexOf(':');
if (lastIndex >= 0) {
msg = msg.substring(lastIndex + 1).trim();
}
}
/// ValidationException 的其它子類異常
} else {
msg = "處理參數(shù)時(shí)異常";
}
resultMap.put("msg", msg);
return resultMap;
}
}