## 前言 在 Javaweb 的开发中,为了防止懂技术的人对数据库的恶意攻击,我们通常使用参数校验对无效数据进行筛选,Java 生态下的@valid 注解配 置 SpringBoot 的使用可以方便快速的完成对数据校验的各种场景。同时数据校验分为`前端校验`和`后端校验`。 可为何前端做完校验之后,还要在后端进行校验? 如果有人拿到了 url 地址,使用第三方测试工具比如 postman 就可以跳过前端页面的参数检验,所以为了数据库数据正确性,我们十分有必要对传来的数据在后端进行第二次校验 ## 一、@Valid 注解 ### 1、源码解析 通过源码可以看出: @Valid 注解可以作用于:方法、属性(包括枚举中的常量)、构造函数、方法的形参上。 ```java @Target({ElementType.METHOD, ElementType.FIELD, ElementType.CONSTRUCTOR, ElementType.PARAMETER, ElementType.TYPE_USE}) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface Valid { } ``` **关于注解源码解析,可以参考如下链接:** [Java 如何自定义注解](https://blog.csdn.net/m0_58680865/article/details/125041168) ### 2、所属的包 ```java import javax.validation.Valid; ``` ### 3、参数校验使用注解 #### (1)空校验 | 注解 | 应用 | | --------- | -------------------------------------------------------------------- | | @Null | 用于基本类型上,限制只能为 null | | @NotNull | 用在基本类型上;不能为 null,但可以为 empty,没有 Size 的约束 | | @NotEmpty | 用在集合类上面;不能为 null,而且长度必须大于 0 | | @NotBlank | 只能作用在 String 上,不能为 null,而且调用 trim()后,长度必须大于 0 | 插播一条小内容!!!`null和empty有何区别?` ```java String a = new String String b = "" String c = null ``` 1. 此时 a 是分配了内存空间,但值为空,是绝对的空,是一种有值(值存在为空而已) 2. 此时 b 是分配了内存空间,值为空字符串,是相对的空,是一种有值(值存在为空字串) 3. 此时 c 是未分配内存空间,无值,是一种无值(值不存在) #### (2)Boolean 校验 | 注解 | 应用 | | ------------ | ---------------- | | @AssertFalse | 限制必须为 false | | @AssertTrue | 限制必须为 true | #### (3)长度校验 | 注解 | 应用 | | ------------------- | ------------------------------------------------------------------------------------------------------------------------------------- | | @Size(max,min) | 验证对象(Array,[Collection](https://so.csdn.net/so/search?q=Collection&spm=1001.2101.3001.7020),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 | 验证注解的元素值是 Email,也可以通过正则表达式和 flag 指定自定义的 email | ### 4、具体使用 #### 使用 @Valid 进行参数效验步骤: 1. 实体类中添加 @Valid 相关注解 2. 接口类中添加 @Valid 注解 3. 全局异常处理类中处理 @Valid 抛出的异常 #### 运行流程: 整个过程如下图所示,用户访问接口,然后进行参数效验,因为 @Valid 不支持平面的参数效验(直接写在参数中字段的效验)所以基于 GET 请求的参数还是按照原先方式进行效验,而 POST 则可以以实体对象为参数,可以使用 @Valid 方式进行效验。如果效验通过,则进入业务逻辑,否则抛出异常,交由全局异常处理器进行处理。  #### 代码实践: ##### (1)添加 maven 依赖(三种方式添加依赖) ```Markup javax.validation validation-api 版本号 org.springframework.boot spring-boot-starter-web 2.0.5.RELEASE org.springframework.boot spring-boot-starter-validation ``` ##### (2)创建 request 实体类 ```java import lombok.Data; import lombok.NoArgsConstructor; import org.hibernate.validator.constraints.Length; import javax.validation.constraints.Max; import javax.validation.constraints.NotBlank; @Data @NoArgsConstructor public class TestRequest { @NotBlank(message = "name不为空") private String name; @Length(max = 3,message = "address最大长度是3") private String address; @Max(value = 5,message = "reqNo最大值是5") private String reqNo; } ``` ##### (3)创建 controller ```java @RestController public class ValidTestController { @RequestMapping("/valid/test") public void test(@Valid @RequestBody TestRequest request){ System.out.println(request); } ``` ##### (4)postman 测试  **postman 返回结果:** ```json { "timestamp": "2022-11-12T09:54:24.202+00:00", "status": 400, "error": "Bad Request", "trace": "org.springframework.web.bind.MethodArgumentNotValidException: Validation failed for argument [0] in public void com.example.controller.ValidTestController.test(com.example.domain.TestRequest) with 3 errors: [Field error in object 'testRequest' on field 'name': rejected value []; codes [NotBlank.testRequest.name,NotBlank.name,NotBlank.java.lang.String,NotBlank]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [testRequest.name,name]; arguments []; default message [name]]; default message [name不为空]] [Field error in object 'testRequest' on field 'reqNo': rejected value [8]; codes [Max.testRequest.reqNo,Max.reqNo,Max.java.lang.String,Max]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [testRequest.reqNo,reqNo]; arguments []; default message [reqNo],5]; default message [reqNo最大值是5]] [Field error in object 'testRequest' on field 'address': rejected value [gtyjh]; codes [Length.testRequest.address,Length.address,Length.java.lang.String,Length]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [testRequest.address,address]; arguments []; default message [address],3,0]; default message [address最大长度是3]] \r\n\tat org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor.resolveArgument(RequestResponseBodyMethodProcessor.java:141)\r\n\tat org.springframework.web.method.support.HandlerMethodArgumentResolverComposite.resolveArgument(HandlerMethodArgumentResolverComposite.java:122)\r\n\tat org.springframework.web.method.support.InvocableHandlerMethod.getMethodArgumentValues(InvocableHandlerMethod.java:179)\r\n\tat org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:146)\r\n\tat org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:117)\r\n\tat org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:895)\r\n\tat org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:808)\r\n\tat org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87)\r\n\tat org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1071)\r\n\tat org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:964)\r\n\tat org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1006)\r\n\tat org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:909)\r\n\tat javax.servlet.http.HttpServlet.service(HttpServlet.java:681)\r\n\tat org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:883)\r\n\tat javax.servlet.http.HttpServlet.service(HttpServlet.java:764)\r\n\tat org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:227)\r\n\tat org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162)\r\n\tat org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:53)\r\n\tat org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189)\r\n\tat org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162)\r\n\tat org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:100)\r\n\tat org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:117)\r\n\tat org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189)\r\n\tat org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162)\r\n\tat org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:93)\r\n\tat org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:117)\r\n\tat org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189)\r\n\tat org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162)\r\n\tat org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:201)\r\n\tat org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:117)\r\n\tat org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189)\r\n\tat org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162)\r\n\tat org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:197)\r\n\tat org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:97)\r\n\tat org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:541)\r\n\tat org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:135)\r\n\tat org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:92)\r\n\tat org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:78)\r\n\tat org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:360)\r\n\tat org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:399)\r\n\tat org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:65)\r\n\tat org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:890)\r\n\tat org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1789)\r\n\tat org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49)\r\n\tat org.apache.tomcat.util.threads.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1191)\r\n\tat org.apache.tomcat.util.threads.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:659)\r\n\tat org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)\r\n\tat java.lang.Thread.run(Thread.java:748)\r\n", "message": "Validation failed for object='testRequest'. Error count: 3", "errors": [ { "codes": [ "NotBlank.testRequest.name", "NotBlank.name", "NotBlank.java.lang.String", "NotBlank" ], "arguments": [ { "codes": [ "testRequest.name", "name" ], "arguments": null, "defaultMessage": "name", "code": "name" } ], "defaultMessage": "name不为空", "objectName": "testRequest", "field": "name", "rejectedValue": "", "bindingFailure": false, "code": "NotBlank" }, { "codes": [ "Max.testRequest.reqNo", "Max.reqNo", "Max.java.lang.String", "Max" ], "arguments": [ { "codes": [ "testRequest.reqNo", "reqNo" ], "arguments": null, "defaultMessage": "reqNo", "code": "reqNo" }, 5 ], "defaultMessage": "reqNo最大值是5", "objectName": "testRequest", "field": "reqNo", "rejectedValue": "8", "bindingFailure": false, "code": "Max" }, { "codes": [ "Length.testRequest.address", "Length.address", "Length.java.lang.String", "Length" ], "arguments": [ { "codes": [ "testRequest.address", "address" ], "arguments": null, "defaultMessage": "address", "code": "address" }, 3, 0 ], "defaultMessage": "address最大长度是3", "objectName": "testRequest", "field": "address", "rejectedValue": "gtyjh", "bindingFailure": false, "code": "Length" } ], "path": "/valid/test" } ``` **从后端返回给 postman 的结果可以看出,三个字段的校验都已经实现。但是,特别情况,RequestBody 可能是嵌套的实体,这个时候,对于嵌套的实体类来说,嵌套必须加 @Valid,如果只在字段上添加校验注解嵌套中的验证不生效。** **如果只在嵌套类字段上加上校验注解,如下:** ```java import lombok.Data; import lombok.NoArgsConstructor; import org.hibernate.validator.constraints.Length; import javax.validation.constraints.Max; import javax.validation.constraints.NotBlank; @Data @NoArgsConstructor public class TestRequest { @NotBlank(message = "name不为空") private String name; @Length(max = 3, message = "address最大长度是3") private String address; @Max(value = 5, message = "reqNo最大值是5") private String reqNo; private TestRequestInner inner; @Data @NoArgsConstructor public static class TestRequestInner { @Length(max = 3, message = "最大长度是3") private String sonName; private Integer sonAge; private String schoolNo; } } ``` **postman 测试:**  控制台打印:  可以看出,嵌套类中 sonName 的长度校验并没有起到作用。 **在嵌套类的外层加上@Valid 注解,如下:** ```java @Data @NoArgsConstructor public class TestRequest { @NotBlank(message = "name不为空") private String name; @Length(max = 3, message = "address最大长度是3") private String address; @Max(value = 5, message = "reqNo最大值是5") private String reqNo; @Valid private TestRequestInner inner; // 即使放在list集合里面仍然是需要加上 @Valid 注解 // @Valid // private List inner; @Data @NoArgsConstructor public static class TestRequestInner { @Length(max = 3, message = "最大长度是3") private String sonName; private Integer sonAge; private String schoolNo; } } ``` postman 测试: 校验成功。  ### 5、异常处理 刚才的测试我们看到,校验注解全部生效,但是所有的异常全部抛出给 postman,从控制台可以看出:  程序抛出了`MethodArgumentNotValidException`异常信息,在实际业务中,有时候需要处理这个异常,这个时候就需要一个全局异常处理类中处理 @Valid 抛出的异常。 **抛出的异常结构:**  **代码如下:** 实体类如上不变,controller 接口方法改为返回 string: ```java @RequestMapping("/valid/test") public String test(@Valid @RequestBody TestRequest request){ System.out.println(request); return "success"; } ``` **异常处理类:** _我们可以根据上图抛出的异常结构,去 get 我们想要获得的内容。_ ```java import lombok.extern.slf4j.Slf4j; import org.springframework.http.HttpStatus; import org.springframework.validation.BindingResult; import org.springframework.validation.FieldError; import org.springframework.web.bind.MethodArgumentNotValidException; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.ResponseStatus; import org.springframework.web.bind.annotation.RestControllerAdvice; import java.util.List; @RestControllerAdvice @Slf4j public class GlobalExceptionHandler { /** * 自定义验证异常 * MethodArgumentNotValidException 方法参数无效异常 */ @ResponseStatus(HttpStatus.BAD_REQUEST) //设置状态码为 400 @ExceptionHandler({MethodArgumentNotValidException.class}) public String paramExceptionHandler(MethodArgumentNotValidException e) { BindingResult exceptions = e.getBindingResult(); // 判断异常中是否有错误信息,如果存在就使用异常中的消息,否则使用默认消息 if (exceptions.hasErrors()) { List errors = exceptions.getAllErrors(); if (!errors.isEmpty()) { // 这里列出了全部错误参数,按正常逻辑,只需要第一条错误即可 FieldError fieldError = (FieldError) errors.get(0); return fieldError.getDefaultMessage(); } } return "请求参数错误"; } } ``` **postman 测试:**  ### 6、springboot 项目中的异常处理 上述的异常处理只是一个简单的 string 返回,但是在实际项目中,返回结构是固定的,下面对于固定的返回结构,做异常处理。 #### (1)request 实体类 ```java import lombok.Data; import lombok.NoArgsConstructor; import org.hibernate.validator.constraints.Length; import javax.validation.Valid; import javax.validation.constraints.Max; import javax.validation.constraints.NotBlank; import java.util.List; @Data @NoArgsConstructor public class TestRequest { @NotBlank(message = "name不为空") private String name; @Length(max = 3, message = "address最大长度是3") private String address; @Max(value = 5, message = "reqNo最大值是5") private String reqNo; @Valid private List inner; @Data @NoArgsConstructor public static class TestRequestInner { @Length(max = 3, message = "最大长度是3") private String sonName; private Integer sonAge; @NotBlank(message = "schoolNo不空") private String schoolNo; } } ``` #### (2)结果返回实体类 ```java import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; import lombok.NoArgsConstructor; import java.util.List; @Data @NoArgsConstructor @Builder @AllArgsConstructor public class ResponseResult { private List provideInfos; @Data @NoArgsConstructor @Builder @AllArgsConstructor public static class ProvideInfo { private String code; private String detail; } } ``` #### (3)controller 接口方法 ```java import com.example.domain.ResponseResult; import com.example.domain.TestRequest; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import javax.validation.Valid; @RestController public class ValidTestController { @RequestMapping("/valid/test") public ResponseEntity test(@Valid @RequestBody TestRequest request) { System.out.println(request); return null; } } ``` #### (4)postman 测试  **再插播一个小插曲!!!!** 刚开始写的结果返回实体类中的 provideInfo 是这样式儿滴:  然后运行项目就出了这个错:  英文版是这样式儿滴:  上网查了一下,问题出现在这:  **原因:** 本应当(只能)使用无参构造器,但编译器却发现代码中使用了有参(全参)构造器,这个全参构造器出现在 Builder 类的`build()方法`中,该方法试图调用一个全参构造器。这个报错信息表明,`@NoArgsConstructor`抑制了`@Builder`生成全参构造器,只生成了一个无参构造器,使用 lombok 插件 delombok `@Builder`和`@NoArgsConstructor`两个注解,可以证实这一抑制现象。 解决方法:一种方法是`同时使用@Builder、@NoArgsConstructor和@AllArgConstructor`,还有一种方法是显式添加`@Tolerate`注解的无参构造器。 然后在 provideInfo 上添加了`@AllArgConstructor`注解(如下图),成功运行!  #### (5)全局异常处理类各种形式 对于全局异常类的处理,涉及到`拦截器`相关内容,这里不做多说。全局异常类的处理方式有很多种: ###### 方式一: ```java // Enum枚举类 public enum CodeEnum { // 根据自己的项目需求更改状态码,这里只是一个示范 UNKNOW_EXCEPTION(10000,"系统未知错误"), VALID_EXCETIPON(10001,"参数格式校验错误"); private int code; private String msg; CodeEnum(int code, String msg){ this.code = code; this.msg = msg; } public int getCode() { return code; } public void setCode(int code) { this.code = code; } public String getMsg() { return msg; } public void setMsg(String msg) { this.msg = msg; } } //异常处理类 @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 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); } } ``` ###### 方式二: ```java @ControllerAdvice @RestControllerAdvice @Slf4j public class ValidExceptionHandler extends GlobalExceptionHandler { // GET请求参数异常处理 @ExceptionHandler(value = ConstraintViolationException.class) public Result constraintViolationExceptionHandler(ConstraintViolationException e) { StringBuilder msg = new StringBuilder(); Set> 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 arithmeticExceptionHandler(ArithmeticException e) { e.printStackTrace(); return ResultResponse.getFailResult(ResultCode.NOT_FOUND.getResultCode(), "算术异常!"+e.getMessage()); } // POST请求参数异常处理 @ExceptionHandler(BindException.class) public Result 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); } } ``` 特别的,get 请求校验: ```java @RestController @RequestMapping(value = "/test") @Slf4j //@ApiIgnore @Validated public class TestController { @GetMapping(value = "/test") public Result test(@NotNull(message = "name必传") @NotBlank(message = "name格式错误")String name) { return ResultResponse.getSuccessResult("hello: " + name); } } ``` ## 二、@Validated 注解 #### 1、@Validated 和 @Valid 区别 1. @Validate 是对@Valid 的封装 2. @Validate 可以进行分组验证 ,一个对象中都写了验证而你只需要验证该方法需要的验证时使用 #### 2、为何要分组校验? 假设有这样一种场景: 我们使用同一个 VO(Request)类来传递 save 和 update 方法的数据,但对于 id 来说,通常有框架帮我们生成 id,我们不需要传 id 此时需要使用注解@Null 表名 id 数据必须为空。但对于 update 方法,我们必须传 id 才能进行 update 操作,所以`同一个字段面对不同的场景不同需求就可以使用分组校验`。 #### 3、代码实操 ##### (1)创建分组接口 这里并不需要实现编写什么代码,标明分类。 ```java //分组接口 1 public interface InsertGroup { } //分组接口 2 public class UpdateGroup { } ``` ##### (2)Request 实体类 ```java @Data @NoArgsConstructor public class TestRequest { @Null(message = "无需传id",groups = InsertGroup.class) @NotBlank(message = "必须传入id",groups = UpdateGroup.class) private String id; } ``` ##### (3)controller 接口 ```java @RequestMapping("/valid/test") public ResponseEntity test(@Validated({UpdateGroup.class})@RequestBody TestRequest request) { System.out.println(request); return null; } ``` ##### (4)postman 测试  ##### (5)注意事项!!! `当我们在controller层指定分组后,属性上没有表名分组的校验还执行么?` 下面的 Request 实体中,id 进行了分组,address 没有进行分组: ```java @Data @NoArgsConstructor public class TestRequest { @Null(message = "无需传id",groups = InsertGroup.class) @NotBlank(message = "必须传入id",groups = UpdateGroup.class) private String id; @Length(max = 3, message = "address最大长度是3") private String address; } ``` postman 测试: 如下图,校验没有成功!!!!说明`一旦开启了分组校验,就必须把所有的校验规则都指定组别,不然不生效`。  ## 三、自定义校验注解 ### 业务场景 `假设我们有一个字段比如showStatus只能由0和1两个取值,我们可以使用正则,也可以自定义注解校验,这里我们展示如何使用自定义校验`. 如下图所示:  ### 自定义注解实现过程 #### (1)编写自定义注解 ```java import javax.validation.Constraint; import javax.validation.Payload; import java.lang.annotation.*; // Target表示注解使用的范围 @Target({ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE, ElementType.CONSTRUCTOR, ElementType.PARAMETER, ElementType.TYPE_USE}) // 获取注解的时间,固定值 @Retention(RetentionPolicy.RUNTIME) // 匹配的校验器,我们稍后编写,关联注解和校验器 @Constraint(validatedBy = {StatusValueValidator.class}) @Documented public @interface StatusValue { // 错误信息去哪找,通常我们使用配置文件,稍后编写 String message() default "{com.cbj.db_work.valid.ListValue.message}"; // 支持分组校验 Class>[] groups() default {}; // 自定义负载信息 Class extends Payload>[] payload() default {}; // 指定参数,就是上图中指定的可取值的范围 int [] vals() default {}; } ``` #### (2)自定义校验器 ```java import javax.validation.ConstraintValidator; import javax.validation.ConstraintValidatorContext; import java.util.HashSet; import java.util.Set; // 实现ConstraintValidator接口,泛型值:<自定义注解类,被校验值的数据类型> public class StatusValueValidator implements ConstraintValidator { // 整体思路,使用set在initialize获得参数信息,在isValid方法中校验,成功true,失败false Set set = new HashSet<>(); // 初始化方法,可以得到详细信息 @Override public void initialize(StatusValue constraintAnnotation) { int[] vals = constraintAnnotation.vals(); for (int val : vals) { set.add(val); } } /** * 校验是否匹配 * @param value 就是需要校验的值 * @param constraintValidatorContext * @return */ @Override public boolean isValid(Integer integer, ConstraintValidatorContext constraintValidatorContext) { return set.contains(integer); } } ``` #### (3)编写配置文件  ```prism language-h com.cbj.db_work.valid.ListValue.message=必须提交指定的值 ``` 在配置文件中可以指定匹配错误时显示的信息,也可以 message 指定。  #### (4)postman 测试 
评论