springboot自带的@Validated注解的使用-笔记
@Validated介绍
@Validated 是spring提供的,提供了分组校验功能,只能用在类型、方法、参数上;
如果类名上和参数上都加了这个注解,以参数上为准;
@Validated全局校验抛的异常是 ConstraintViolationException
@Validated分组校验抛的异常是 MethodArgumentNotValidException;
@Validated全局校验测试
@Validated
@RestController
public class Test05292Api {
@PostMapping("/t1234")
public UserReq test1234(@Valid @RequestBody UserReq req, BindingResult bindingResult) {
log.info("req:{}", req.toString());
return req;
}
/**
* @Validated全局校验
* 对list参数校验,返回的异常信息如下
* addUsers.list[0].phone: 必须是手机
* addUsers.list[0].company.id: 不能为null
* addUsers.list[0].testCount: 需要在1和99之间
*/
@PostMapping("/users")
public List<UserReq> addUsers(@Valid @RequestBody List<UserReq> list) {
log.info("list:{}", list.toString());
return list;
}
}
@Data
public class UserReq {
@NotBlank
@Size(max = 30,min = 2,message = "长度需要在2-30之间")
private String account;
@Email
private String email;
@Pattern(regexp = "(?:0|86|\\+86)?1[3-9]\\d{9}",message = "必须是手机号")
private String phone;
/**
* 必须是一个过去的或当前的日期
*/
@PastOrPresent
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime startDt;
/**
* 必须是一个将来的或当前的日期
*/
@FutureOrPresent
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime endDt;
/**
* 嵌套校验
*/
@Valid
private CompanyDTO company;
/**
* hibernate.validator包提供的 范围限制注解 @Range ,限制数值的大小
*/
@Range(min = 1,max = 99)
private Integer testCount;
}
@Data
public class CompanyDTO {
@NotNull
private Integer id;
@NotBlank
@Length(max = 30,min=4,message = "长度需要在4-30之间")
private String name;
@Pattern(regexp ="(010|02\\d|0[3-9]\\d{2})-?(\\d{6,8})",message = "必须是国内固定电话号码")
private String tel;
}
{
"code": 9996,
"msg": "参数校验异常",
"content": "addUsers.list[0].testCount: 需要在1和99之间, addUsers.list[0].company.id: 不能为null",
"timestamp": 1685349934258,
"traceDateTime": "2023-05-29 16:45:34"
}
需要用ExceptionHandler手动处理ConstraintViolationException
/**
* Validated全局自动校验,要@Validated加在Controller类名上,同时手动使用BindingResult
*/
@ExceptionHandler({ConstraintViolationException.class})
public ResultVO constraintViolationExceptionHandler(ConstraintViolationException e) {
log.error("Validated全局自动校验出的异常",e);
//校验框架会自动返回多条约束提示信息的拼接
//格式如 test1234.req.company.name: 长度需要在4-30之间,test1234.req.account: 长度需要在2-30之间
//(逗号拼接的字符串 接口方法名.参数名.子参数对象.子参数属性:描述)
return new ResultVO<>(ResultCodeEnum.VALIDATE_FAILED,
e.getMessage());
}
总结
@Validated加在类名上,且api参数里有BindingResult时,
会抛出javax.validation.ConstraintViolationException ,
异常信息返回如下
test1234.req.company.name: 长度需要在4-30之间,
test1234.req.account: 长度需要在2-30之间
格式的错误信息 (逗号拼接的字符串 , 接口方法名.参数名.子参数对象.子参数属性) ;
@Validated全局校验 可以和 @Valid嵌套校验一起使用,
即 @Validated全局校验时,也能处理复合JavaBean的子对象的子属性的参数校验,
需要JavaBean里的子对象上加@Valid注解
@Validated分组校验测试
/**
* @Validated({UpdateActionGroup.class})分组校验测试,抛的异常是 MethodArgumentNotValidException
*/
@RestController
@Slf4j
public class Test05293Api {
/**
* 新增时不校验id
* @param req UserTestReq1
* @return UserTestResp
*
* 触发校验时返回信息为
* userTestReq1.name不能为空 或
* userTestReq1.age不能为null
*/
@PostMapping("/addUser")
public UserTestResp addUser(@RequestBody @Validated
UserTestReq1 req){
log.info("req:{}",req);
return UserTestResp.builder()
.age(req.getAge())
.name(req.getName())
.build();
}
/**
* 更新时校验id
* @param req UserTestReq1 在 UpdateActionGroup下只校验id字段
* @return UserTestResp
* {
* "code": 9996,
* "msg": "参数校验异常",
* "content": "userTestReq1.id不能为null",
* "timestamp": 1685344365874,
* "traceDateTime": "2023-05-29 15:12:45"
* }
*/
@PostMapping("/alterUser")
public UserTestResp alterUser(@RequestBody @Validated({UpdateActionGroup.class})
UserTestReq1 req ){
log.info("req:{}",req);
return UserTestResp.builder()
.id(req.getId())
.age(req.getAge())
.name(req.getName())
.build();
}
}
import lombok.Data;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
/**
* 分组校验 和 嵌套校验 不能同时使用,否则会使得分组校验失效...
*/
@Data
public class UserTestReq1 {
/**
* 在 UpdateActionGroup下只校验id字段
*/
@NotNull(groups = UpdateActionGroup.class)
private Long id;
/**
* 在默认情况下只校验 name字段和age字段
*/
@NotBlank
private String name;
@NotNull
private Integer age;
//............这里可以增加更多字段,然后对字段设置分组
//分组校验可以高度复用 RequestJavaBean 。
//在做一些固定对象数据的增删改查时,用到的都是同一套参数,
//只是每次校验的分组不同,可以使用 @Validated({xxxxGroup.class})分组校验
}
/**
* 创建1个分组 UpdateActionGroup
*/
public interface UpdateActionGroup {
}
需要用ExceptionHandler手动处理MethodArgumentNotValidException
@ExceptionHandler({MethodArgumentNotValidException.class})
public ResultVO<String> MethodArgumentNotValidExceptionHandler(MethodArgumentNotValidException e) {
log.error("自动抛的MethodArgumentNotValidException",e);
// 从异常对象中拿到ObjectError对象
ObjectError objectError = e.getBindingResult().getAllErrors().get(0);
log.error("objectError:{}", JSONObject.toJSONString(objectError, true));
String field = String.valueOf(JSONPath.eval(objectError, "$.field"));
log.error("field:{}", field);
String objName = objectError.getObjectName();
System.err.println(objName);
// 然后提取错误提示信息进行返回
return new ResultVO<>(ResultCodeEnum.VALIDATE_FAILED,
objName + "." + field + objectError.getDefaultMessage());
}
{
"code": 9996,
"msg": "参数校验异常",
"content": "userTestReq1.id不能为null",
"timestamp": 1685349830928,
"traceDateTime": "2023-05-29 16:43:50"
}
总结
@Validated分组校验可以高度复用 RequestJavaBean;
但是不能和 @Vaild嵌套校验功能一起使用;
@Validated分组校验更适合对同一类数据的一套参数,进行分组的定制校验;
但是如果分组校验的参数bean的字段越来越多时,如 UserTestReq1,
可能需要按需创建更多的分组接口,长期下去对类代码的可读性会变差,要使用的话,最好都加上明确的注释,以便于维护代码。