一、@Valid注解
1、介绍
@Valid注解可以作用于:方法、属性(包括枚举中的常量)、构造函数、方法的形参上。
import javax.validation.Valid;
@Target({ElementType.METHOD, ElementType.FIELD, ElementType.CONSTRUCTOR, ElementType.PARAMETER, ElementType.TYPE_USE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Valid {
}
2、参数校验使用注解
(1)空校验
注解 | 应用 |
---|---|
@Null | 用于基本类型上,限制只能为null |
@NotNull | 用在基本类型上;不能为null,但可以为empty,没有Size的约束 |
@NotEmpty | 用在集合类上面;不能为null,而且长度必须大于0 |
@NotBlank | 只能作用在String上,不能为null,而且调用trim()后,长度必须大于0 |
注意:null和empty的区别
String a = new String
String b = ""
String c = null
- 此时a是分配了内存空间,但值为空,是绝对的空,是一种有值(值存在为空而已)
- 此时b是分配了内存空间,值为空字符串,是相对的空,是一种有值(值存在为空字串)
- 此时c是未分配内存空间,无值,是一种无值(值不存在)
(2)Boolean校验
注解 | 应用 |
---|---|
@AssertFalse | 限制必须为false |
@AssertTrue | 限制必须为true |
(3)长度校验
注解 | 应用 |
---|---|
@Size(max,min) | 验证对象(Array,Collection,Map,String)长度是否在给定的范围之内 |
@Length(min=, max=) | 验证字符串 长度是否在给定的范围之内 |
(4)日期校验
注解 | 应用 |
---|---|
@Past | 限制必须是一个过去的日期,并且类型为java.util.Date |
@Future | 限制必须是一个将来的日期,并且类型为java.util.Date |
@Pattern(value) | 限制必须符合指定的正则表达式 |
(5)数值校验
建议使用在Stirng,Integer类型,不建议使用在int类型上,因为表单值为“”时无法转换为int,但可以转换为Stirng为"",Integer为null。
注解 | 应用 |
---|---|
@Min(value) | 验证 Number 和 String 对象必须为一个不小于指定值的数字 |
@Max(value) | 验证 Number 和 String 对象必须为一个不大于指定值的数字 |
@DecimalMax(value) | 限制必须为一个不大于指定值的数字,小数存在精度 |
@DecimalMin(value) | 限制必须为一个不小于指定值的数字,小数存在精度 |
@Digits(integer,fraction) | 限制必须为一个小数,且整数部分的位数不能超过integer,小数部分的位数不能超过fraction |
@Digits | 验证 Number 和 String 的构成是否合法 |
@Range(max =3 , min =1 , message = " ") | Checks whether the annotated value lies between (inclusive) the specified minimum and maximum |
注意:Max和Min是对你填的“数字”是否大于或小于指定值,这个“数字”可以是number或者string类型。长度限制用length。
(6)其他校验
注解 | 应用 |
---|---|
验证注解的元素值是Email,也可以通过正则表达式和flag指定自定义的email |
3、集成方法
3.1、添加依赖:
三种方式添加依赖
<!--第一种:valid依赖-->
<dependency>
<groupId>javax.validation</groupId>
<artifactId>validation-api</artifactId>
<version>版本号</version>
</dependency>
<!-- 第二种:集成于web依赖中(注意版本号)-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>2.0.5.RELEASE</version>
</dependency>
<!-- 第三种:springboot的validation-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
3.2、实体类中添加相关校验注解
3.3、接口类中添加 @Valid 注解
(1)对象参数
直接在对象前面加@Valid,相关校验注解加在DTO里面
import jakarta.validation.constraints.Max;
import jakarta.validation.constraints.NotBlank;
import lombok.Data;
import org.hibernate.validator.constraints.Length;
import org.springframework.validation.annotation.Validated;
@Data
public class UrlDTO {
@NotBlank(message = "name不能为空")
private String urlName;
@Length(min = 1, max = 10)
private String urlCode;
@Max(5)
private Integer urlType;
}
@RequestMapping("/insert")
public String insert(@Valid UrlDTO urlDTO) {
return "insert OK";
}
@RequestMapping("/add")
public String add(@RequestBody @Valid UrlDTO urlDTO) {
return "add OK";
}
@RequestMapping("/batchAdd")
public String batchAdd( @RequestBody @Valid List<UrlDTO> urlDTOs) {
return "batch add OK";
}
如果是嵌套的实体对象,则需要在最外层属性上添加 @Valid 注解:
public class User {
@NotBlank(message = "姓名不为空")
private String username;
@NotBlank(message = "密码不为空")
private String password;
//嵌套必须加 @Valid,否则嵌套中的验证不生效
@Valid
@NotNull(message = "用户信息不能为空")
private UserInfo userInfo;
}
(2)非对象参数
直接在参数前面加相关校验注解
(3)集合
在Controller上添加@Validated注解,方法上添加@Valid注解。
3.4、全局异常处理类中处理 @Valid 抛出的异常
全局异常类的处理方式有很多种:
(1)方式一
//异常处理类
@Slf4j
@RestControllerAdvice("com.cbj.db_work.controller") //表明需要处理异常的范围
public class GlobalExceptionHandler {
@ExceptionHandler(value = MethodArgumentNotValidException.class) // 参数异常抛出的异常类型为MethodArgumentNotValidException,这里捕获这个异常
// R为统一返回的处理类
public R validExceptionHandler(MethodArgumentNotValidException e){
System.out.println("数据异常处理");
log.error("数据校验出现问题,异常类型:{}",e.getMessage(),e.getClass());
BindingResult bindingResult = e.getBindingResult();
Map<String,String> map = new HashMap<>();
bindingResult.getFieldErrors().forEach((item)->{
String message = item.getDefaultMessage();
// 获取错误的属性字段名
String field = item.getField();
map.put(field,message);
});
return R.error().code(CodeEnum.VALID_EXCETIPON.getCode()).message(CodeEnum.VALID_EXCETIPON.getMsg()).data("errorData",map);
}
}
(2)方式二
@ControllerAdvice
@RestControllerAdvice
@Slf4j
public class ValidExceptionHandler extends GlobalExceptionHandler {
// GET请求参数异常处理
@ExceptionHandler(value = ConstraintViolationException.class)
public Result<Object> constraintViolationExceptionHandler(ConstraintViolationException e) {
StringBuilder msg = new StringBuilder();
Set<ConstraintViolation<?>> constraintViolations = e.getConstraintViolations();
for (ConstraintViolation<?> constraintViolation : constraintViolations) {
String message = constraintViolation.getMessage();
msg.append(message).append(";");
}
return ResultResponse.getFailResult(ResultCode.BODY_NOT_MATCH.getResultCode(), msg.toString());
}
@ExceptionHandler(ArithmeticException.class)
public Result<Object> arithmeticExceptionHandler(ArithmeticException e) {
e.printStackTrace();
return ResultResponse.getFailResult(ResultCode.NOT_FOUND.getResultCode(), "算术异常!"+e.getMessage());
}
// POST请求参数异常处理
@ExceptionHandler(BindException.class)
public Result<Object> bindExceptionHandler(BindException e) {
FieldError fieldError = e.getBindingResult().getFieldError();
String msg;
if (Objects.isNull(fieldError)) {
msg = "POST请求参数异常:" + JSON.toJSONString(e.getBindingResult());
log.info(msg);
} else {
msg = fieldError.getDefaultMessage();
}
return ResultResponse.getFailResult(ResultCode.BODY_NOT_MATCH.getResultCode(), msg);
}
}
4、运行流程
(1)get请求:因为 @Valid 不支持平面的参数效验(直接写在参数中字段的效验)所以基于 GET 请求的参数还是按照原先方式进行效验, 而且GET 请求一般接收参数也较少,使用正常判断逻辑进行参数效验即可。
(2) POST请求:则可以以实体对象为参数,可以使用 @Valid 方式进行效验。如果效验通过,则进入业务逻辑,否则抛出异常,交由全局异常处理器进行处理。
5、demo
dto
package com.pluscache.demo.dto;
import jakarta.validation.constraints.Max;
import jakarta.validation.constraints.NotBlank;
import lombok.Data;
import org.hibernate.validator.constraints.Length;
@Data
public class UrlDTO {
@NotBlank(message = "name不能为空")
private String urlName;
@Length(min = 1, max = 10)
private String urlCode;
@Max(5)
private Integer urlType;
}
package com.pluscache.demo.dto;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class ResponseMessage {
private int code;
private String message;
private Object data;
}
controller
package com.pluscache.demo.controller;
import com.pluscache.demo.dto.UrlDTO;
import jakarta.validation.Valid;
import jakarta.validation.constraints.Max;
import jakarta.validation.constraints.NotBlank;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
@RestController
@RequestMapping("/url")
@Validated
public class UrlController {
@RequestMapping("/getUser")
public String getUser(@NotBlank String urlName, @Max(5) Integer urlType) {
return "getUser OK";
}
@RequestMapping("/insert")
public String insert(@Valid UrlDTO urlDTO) {
return "insert OK";
}
@RequestMapping("/add")
public String add(@RequestBody @Valid UrlDTO urlDTO) {
return "add OK";
}
@RequestMapping("/batchAdd")
public String batchAdd(@Valid @RequestBody List<UrlDTO> urlDTOs) {
return "batch add OK";
}
}
全局异常处理
package com.pluscache.demo.hander;
import com.pluscache.demo.dto.ResponseMessage;
import jakarta.validation.ConstraintViolationException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.validation.BindingResult;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.MissingServletRequestParameterException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@RestControllerAdvice
@Slf4j
public class GlobalExceptionHandler {
/**
* 忽略参数异常处理器
*
* @param e 忽略参数异常
* @return ResponseResult
*/
@ResponseStatus(HttpStatus.BAD_REQUEST)
@ExceptionHandler(MissingServletRequestParameterException.class)
public ResponseMessage parameterMissingExceptionHandler(MissingServletRequestParameterException e) {
log.error("", e);
return new ResponseMessage(600, "请求参数 " + e.getParameterName() + " 不能为空",null);
}
/**
* 缺少请求体异常处理器
*
* @param e 缺少请求体异常
* @return ResponseResult
*/
@ResponseStatus(HttpStatus.BAD_REQUEST)
@ExceptionHandler(HttpMessageNotReadableException.class)
public ResponseMessage parameterBodyMissingExceptionHandler(HttpMessageNotReadableException e) {
log.error("", e);
return new ResponseMessage(600, "参数体不能为空",null);
}
/**
* 自定义验证异常
* MethodArgumentNotValidException 方法参数无效异常
*/
@ResponseStatus(HttpStatus.BAD_REQUEST) //设置状态码为 400
@ExceptionHandler({MethodArgumentNotValidException.class})
@ResponseBody
public ResponseMessage paramExceptionHandler(MethodArgumentNotValidException ex) {
ResponseMessage responseMessage = new ResponseMessage();
responseMessage.setCode(600);
Map<String, String> errorsMap = new HashMap<>();
//处理异常
ex.getBindingResult()
.getAllErrors()
.forEach(
(error) -> {
if (error instanceof FieldError) {
String fieldName = ((FieldError) error).getField();
String errorMessage = error.getDefaultMessage();
errorsMap.put(fieldName, errorMessage);
} else {
errorsMap.put(error.getObjectName(), error.getDefaultMessage());
}
});
responseMessage.setData(errorsMap);
return responseMessage;
}
@ResponseStatus(HttpStatus.BAD_REQUEST)
@ExceptionHandler(ConstraintViolationException.class)
@ResponseBody
public ResponseMessage handleValidationExceptions(ConstraintViolationException ex) {
ResponseMessage response = new ResponseMessage();
response.setCode(600);
response.setMessage(ex.getMessage());
return response;
}
}
测试:
(1)普通参数:
localhost:1111/plusDemo/url/getUser?urlName=
(2)对象参数
(3)@RequestBody对象参数
(4)集合参数
(5)嵌套
现在UrlDTO中再加两个对象参数
package com.pluscache.demo.dto;
import jakarta.validation.Valid;
import jakarta.validation.constraints.Max;
import jakarta.validation.constraints.NotBlank;
import lombok.Data;
import org.hibernate.validator.constraints.Length;
import java.util.List;
@Data
public class UrlDTO {
@NotBlank(message = "name不能为空")
private String urlName;
@Length(min = 1, max = 10)
private String urlCode;
@Max(5)
private Integer urlType;
@Valid
UserDTO userDTO;
@Valid
List<UserDTO> userDTOList;
}
package com.pluscache.demo.dto;
import com.baomidou.mybatisplus.annotation.TableName;
import jakarta.validation.constraints.Max;
import lombok.Data;
@Data
@TableName("t_user")
public class UserDTO {
private String userName;
private String userAccount;
@Max(30)
private Integer age;
}
访问localhost:1111/plusDemo/url/add
(6)无全局异常处理
现在把全局异常处理去掉,
访问localhost:1111/plusDemo/url/add
后台打印
Resolved [org.springframework.web.bind.MethodArgumentNotValidException: Validation failed for argument [0] in public java.lang.String com.pluscache.demo.controller.UrlController.add(com.pluscache.demo.dto.UrlDTO) with 3 errors: [Field error in object 'urlDTO' on field 'urlName': rejected value []; codes [NotBlank.urlDTO.urlName,NotBlank.urlName,NotBlank.java.lang.String,NotBlank]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [urlDTO.urlName,urlName]; arguments []; default message [urlName]]; default message [name不能为空]] [Field error in object 'urlDTO' on field 'urlCode': rejected value [abcfd56hgfh]; codes [Length.urlDTO.urlCode,Length.urlCode,Length.java.lang.String,Length]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [urlDTO.urlCode,urlCode]; arguments []; default message [urlCode],10,1]; default message [length must be between 1 and 10]] [Field error in object 'urlDTO' on field 'urlType': rejected value [7]; codes [Max.urlDTO.urlType,Max.urlType,Max.java.lang.Integer,Max]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [urlDTO.urlType,urlType]; arguments []; default message [urlType],5]; default message [must be less than or equal to 5]] ]
二、@Validated注解
1、@Validated 和 @Valid 区别
(1)@Validate 是对@Valid 的封装
(2)@Validate 可以进行分组验证 ,一个对象中都写了验证而你只需要验证该方法需要的验证时使用
2、使用场景
(1)我们使用同一个VO(Request)类来传递save和update方法的数据,但对于id来说,通常有框架帮我们生成id,我们不需要传id此时需要使用注解@Null表名id数据必须为空。但对于update方法,我们必须传id才能进行update操作,所以同一个字段面对不同的场景不同需求就可以使用分组校验。
注意:这种情况,一旦开启了分组校验,就必须把所有的校验规则都指定组别,不然校验也不生效
。
(2)上文提到的,配合@Valid做集合对象的校验。
3、demo:
(1)创建分组接口
并不需要实现编写什么代码,标明分类。
//分组接口 1
public interface InsertGroup {
}
//分组接口 2
public class UpdateGroup {
}
(2)DTO:
import com.pluscache.demo.dto.group.InsertGroup;
import com.pluscache.demo.dto.group.UpdateGroup;
import jakarta.validation.Valid;
import jakarta.validation.constraints.Max;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Null;
import lombok.Data;
import org.hibernate.validator.constraints.Length;
import java.util.List;
@Data
public class UrlDTO {
@Null(message = "无需传id",groups = InsertGroup.class)
@NotNull(message = "必须传入id",groups = UpdateGroup.class)
private Integer id;
@NotBlank(message = "name不能为空")
private String urlName;
@Length(min = 1, max = 10)
private String urlCode;
@Max(5)
private Integer urlType;
@Valid
UserDTO userDTO;
@Valid
List<UserDTO> userDTOList;
}
(3)controller
package com.pluscache.demo.controller;
import com.pluscache.demo.dto.UrlDTO;
import com.pluscache.demo.dto.group.InsertGroup;
import com.pluscache.demo.dto.group.UpdateGroup;
import jakarta.validation.Valid;
import jakarta.validation.constraints.Max;
import jakarta.validation.constraints.NotBlank;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
@RestController
@RequestMapping("/url")
@Validated
public class UrlController {
@RequestMapping("/getUser")
public String getUser(@NotBlank String urlName, @Max(5) Integer urlType) {
return "getUser OK";
}
@RequestMapping("/insert")
public String insert(@Validated(InsertGroup.class) UrlDTO urlDTO) {
return "insert OK";
}
@RequestMapping("/update")
public String update(@Validated(UpdateGroup.class) UrlDTO urlDTO) {
return "update OK";
}
@RequestMapping("/add")
public String add(@RequestBody @Valid UrlDTO urlDTO) {
return "add OK";
}
@RequestMapping("/batchAdd")
public String batchAdd(@Valid @RequestBody List<UrlDTO> urlDTOs) {
return "batch add OK";
}
}
(4)测试
而访问update接口,不加id校验不通过,加上id则成功