• @Valid和@Validated注解校验以及异常处理


    前言

    在Javaweb的开发中,为了防止懂技术的人对数据库的恶意攻击,我们通常使用参数校验对无效数据进行筛选,Java生态下的@valid注解配
    置SpringBoot的使用可以方便快速的完成对数据校验的各种场景。同时数据校验分为前端校验后端校验

    可为何前端做完校验之后,还要在后端进行校验?

    如果有人拿到了url地址,使用第三方测试工具比如postman就可以跳过前端页面的参数检验,所以为了数据库数据正确性,我们十分有必要对传来的数据在后端进行第二次校验

    一、@Valid注解

    1、源码解析

    通过源码可以看出:

    @Valid注解可以作用于:方法、属性(包括枚举中的常量)、构造函数、方法的形参上。

    @Target({ElementType.METHOD, ElementType.FIELD, ElementType.CONSTRUCTOR, ElementType.PARAMETER, ElementType.TYPE_USE})
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    public @interface Valid {
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    关于注解源码解析,可以参考如下链接:

    Java如何自定义注解

    2、所属的包

    import javax.validation.Valid;
    
    • 1

    3、参数校验使用注解

    (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
    
    • 1
    • 2
    • 3
    1. 此时a是分配了内存空间,但值为空,是绝对的空,是一种有值(值存在为空而已)
    2. 此时b是分配了内存空间,值为空字符串,是相对的空,是一种有值(值存在为空字串)
    3. 此时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验证注解的元素值是Email,也可以通过正则表达式和flag指定自定义的email

    4、具体使用

    使用 @Valid 进行参数效验步骤:

    1. 实体类中添加 @Valid 相关注解
    2. 接口类中添加 @Valid 注解
    3. 全局异常处理类中处理 @Valid 抛出的异常

    运行流程:

    整个过程如下图所示,用户访问接口,然后进行参数效验,因为 @Valid 不支持平面的参数效验(直接写在参数中字段的效验)所以基于 GET 请求的参数还是按照原先方式进行效验,而 POST 则可以以实体对象为参数,可以使用 @Valid 方式进行效验。如果效验通过,则进入业务逻辑,否则抛出异常,交由全局异常处理器进行处理。

    在这里插入图片描述

    代码实践:

    (1)添加maven依赖(三种方式添加依赖)
    			
    		<dependency>
    			<groupId>javax.validationgroupId>
    			<artifactId>validation-apiartifactId>
    			<version>版本号version>
    		dependency>
    
    		
    		<dependency>
    			<groupId>org.springframework.bootgroupId>
    			<artifactId>spring-boot-starter-webartifactId>
    			<version>2.0.5.RELEASEversion>
    		dependency>
    
    		
    		<dependency>
    			<groupId>org.springframework.bootgroupId>
    			<artifactId>spring-boot-starter-validationartifactId>
    		dependency>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    (2)创建request实体类
    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;
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    (3)创建controller
    @RestController
    public class ValidTestController {
    
        @RequestMapping("/valid/test")
        public void test(@Valid @RequestBody TestRequest request){
            System.out.println(request);
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    (4)postman测试

    在这里插入图片描述

    postman返回结果:

    {
        "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"
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88

    从后端返回给postman的结果可以看出,三个字段的校验都已经实现。但是,特别情况,RequestBody可能是嵌套的实体,这个时候,对于嵌套的实体类来说,嵌套必须加 @Valid,如果只在字段上添加校验注解嵌套中的验证不生效。

    如果只在嵌套类字段上加上校验注解,如下:

    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;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32

    postman测试:
    在这里插入图片描述

    控制台打印:
    在这里插入图片描述
    可以看出,嵌套类中sonName的长度校验并没有起到作用。

    在嵌套类的外层加上@Valid注解,如下:

    @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;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31

    postman测试:

    校验成功。

    在这里插入图片描述

    5、异常处理

    刚才的测试我们看到,校验注解全部生效,但是所有的异常全部抛出给postman,从控制台可以看出:
    在这里插入图片描述
    程序抛出了MethodArgumentNotValidException异常信息,在实际业务中,有时候需要处理这个异常,这个时候就需要一个全局异常处理类中处理 @Valid 抛出的异常。

    抛出的异常结构:

    在这里插入图片描述

    代码如下:

    实体类如上不变,controller接口方法改为返回string:

     @RequestMapping("/valid/test")
        public String test(@Valid @RequestBody TestRequest request){
            System.out.println(request);
            return "success";
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    异常处理类:

    我们可以根据上图抛出的异常结构,去get我们想要获得的内容。

    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 "请求参数错误";
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34

    postman测试:

    在这里插入图片描述

    6、springboot项目中的异常处理

    上述的异常处理只是一个简单的string返回,但是在实际项目中,返回结构是固定的,下面对于固定的返回结构,做异常处理。

    (1)request实体类

    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<TestRequestInner> inner;
    
        @Data
        @NoArgsConstructor
        public static class TestRequestInner {
            @Length(max = 3, message = "最大长度是3")
            private String sonName;
    
            private Integer sonAge;
    
            @NotBlank(message = "schoolNo不空")
            private String schoolNo;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37

    (2)结果返回实体类

    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<ProvideInfo> provideInfos;
    
        @Data
        @NoArgsConstructor
        @Builder
        @AllArgsConstructor
        public static class ProvideInfo {
            private String code;
            private String detail;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    (3)controller接口方法

    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<ResponseResult> test(@Valid @RequestBody TestRequest request) {
            System.out.println(request);
            return null;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    (4)postman测试

    在这里插入图片描述

    再插播一个小插曲!!!!

    刚开始写的结果返回实体类中的provideInfo是这样式儿滴:
    在这里插入图片描述
    然后运行项目就出了这个错:

    在这里插入图片描述
    英文版是这样式儿滴:

    在这里插入图片描述

    上网查了一下,问题出现在这:

    在这里插入图片描述

    原因:

    本应当(只能)使用无参构造器,但编译器却发现代码中使用了有参(全参)构造器,这个全参构造器出现在Builder类的build()方法中,该方法试图调用一个全参构造器。这个报错信息表明,@NoArgsConstructor抑制了@Builder生成全参构造器,只生成了一个无参构造器,使用lombok插件delombok @Builder@NoArgsConstructor两个注解,可以证实这一抑制现象。

    解决方法:一种方法是同时使用@Builder、@NoArgsConstructor和@AllArgConstructor,还有一种方法是显式添加@Tolerate注解的无参构造器。

    然后在provideInfo上添加了@AllArgConstructor注解(如下图),成功运行!
    在这里插入图片描述

    (5)全局异常处理类各种形式

    对于全局异常类的处理,涉及到拦截器相关内容,这里不做多说。全局异常类的处理方式有很多种:

    方式一:
    // 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<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);
        }
        
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    方式二:
    @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);
        }
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38

    特别的,get请求校验:

    @RestController
    @RequestMapping(value = "/test")
    @Slf4j
    //@ApiIgnore
    @Validated
    public class TestController {
    
        @GetMapping(value = "/test")
        public Result<Object> test(@NotNull(message = "name必传")
                                       @NotBlank(message = "name格式错误")String name) {
            return ResultResponse.getSuccessResult("hello: " + name);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    二、@Validated注解

    1、@Validated 和 @Valid 区别

    1. @Validate 是对@Valid 的封装
    2. @Validate 可以进行分组验证 ,一个对象中都写了验证而你只需要验证该方法需要的验证时使用

    2、为何要分组校验?

    假设有这样一种场景:

    我们使用同一个VO(Request)类来传递save和update方法的数据,但对于id来说,通常有框架帮我们生成id,我们不需要传id此时需要使用注解@Null表名id数据必须为空。但对于update方法,我们必须传id才能进行update操作,所以同一个字段面对不同的场景不同需求就可以使用分组校验

    3、代码实操

    (1)创建分组接口

    这里并不需要实现编写什么代码,标明分类。

    //分组接口 1
    public interface InsertGroup {
    }
    
    //分组接口 2
    public class UpdateGroup {
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    (2)Request实体类
    @Data
    @NoArgsConstructor
    public class TestRequest {
    
        @Null(message = "无需传id",groups = InsertGroup.class)
        @NotBlank(message = "必须传入id",groups = UpdateGroup.class)
        private String id;
        
      }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    (3)controller接口
    	@RequestMapping("/valid/test")
        public ResponseEntity<ResponseResult> test(@Validated({UpdateGroup.class})@RequestBody TestRequest request) {
            System.out.println(request);
            return null;
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    (4)postman测试

    在这里插入图片描述

    (5)注意事项!!!

    当我们在controller层指定分组后,属性上没有表名分组的校验还执行么?

    下面的Request实体中,id进行了分组,address没有进行分组:

    @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;
        
      }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    postman测试:

    如下图,校验没有成功!!!!说明一旦开启了分组校验,就必须把所有的校验规则都指定组别,不然不生效

    在这里插入图片描述

    三、自定义校验注解

    业务场景

    假设我们有一个字段比如showStatus只能由0和1两个取值,我们可以使用正则,也可以自定义注解校验,这里我们展示如何使用自定义校验.

    如下图所示:

    在这里插入图片描述

    自定义注解实现过程

    (1)编写自定义注解

    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 {};
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    (2)自定义校验器

    import javax.validation.ConstraintValidator;
    import javax.validation.ConstraintValidatorContext;
    import java.util.HashSet;
    import java.util.Set;
    
    // 实现ConstraintValidator接口,泛型值:<自定义注解类,被校验值的数据类型>
    public class StatusValueValidator implements ConstraintValidator<StatusValue, Integer> {
    
        // 整体思路,使用set在initialize获得参数信息,在isValid方法中校验,成功true,失败false
        Set<Integer> 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);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31

    (3)编写配置文件

    在这里插入图片描述

    com.cbj.db_work.valid.ListValue.message=必须提交指定的值
    
    • 1

    在配置文件中可以指定匹配错误时显示的信息,也可以message指定。

    在这里插入图片描述

    (4)postman测试

    在这里插入图片描述

  • 相关阅读:
    Mysql——》BufferPool相关信息
    [附源码]计算机毕业设计springboot动物保护协会网站
    Go语言设计与实现 学习笔记 第一章 介绍
    conda虚拟环境笔记收录
    线性表-顺序表学习笔记(基础)
    实在TARS大模型斩获多项重磅大奖,AI领域实力认可
    mPEG-DSPE 178744-28-0 甲氧基-聚乙二醇-磷脂酰乙醇胺线性PEG磷脂
    Java8 特性 -- Lambda 详解
    开源维修上门服务小程序SAAS系统源码 带完整搭建教程
    【运维】Shell编程(学习笔记)
  • 原文地址:https://blog.csdn.net/m0_58680865/article/details/127817779