zoukankan      html  css  js  c++  java
  • Hibernate-Validator框架完成服务端参数据校验(巨详细)

    什么是Hibernate Validator?

    Hibernate Validator是Hibernate提供的一个开源框架,使用注解方式非常方便的实现服务端的数据校验。

    官网:http://hibernate.org/validator/

    hibernate Validator是 Bean Validation 的参考实现 。

    Hibernate Validator 提供了 JSR 303 规范中所有内置 constraint(约束) 的实现,除此之外还有一些附加的 constraint。

    在日常开发中,Hibernate Validator经常用来验证bean的字段,基于注解,方便快捷高效。

    在SpringBoot的spring-boot-starter-web启动器中已经集成了相关依赖:

    或者可以添加依赖:

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-validation</artifactId>
    </dependency>

    Bean校验的常用注解:

    @Valid 被注释的元素是一个对象,需要检查此对象的所有字段值
    @Validated 被注解的元素是一个对象或者一个类,需要检查此对象的所有字段值
    @AssertFalse 所注解的元素必须是Boolean类型,且值为false
    @AssertTrue 所注解的元素必须是Boolean类型,且值为true
    @DecimalMax 所注解的元素必须是数字,且值小于等于给定的值
    @DecimalMin 所注解的元素必须是数字,且值大于等于给定的值
    @Digits 所注解的元素必须是数字,且值必须是指定的位数
    @Future 所注解的元素必须是将来某个日期
    @Max(value) 所注解的元素必须是数字,且值小于等于给定的值
    @Min(value) 所注解的元素必须是数字,且值小于等于给定的值
    @Range 所注解的元素需在指定范围区间内
    @NotNull 所注解的元素值不能为null
    @NotBlank 所注解的元素值有内容
    @Null 所注解的元素值为null
    @Past 所注解的元素必须是某个过去的日期
    @PastOrPresent 所注解的元素必须是过去某个或现在日期
    @Pattern(value) 所注解的元素必须满足给定的正则表达式
    @Size 所注解的元素必须是String、集合或数组,且长度大小需保证在给定范围之内
    @Email 所注解的元素需满足Email格式

    第一类:实体类参数校验

    (1)实体类上加上注解,使用json传参,加@RequestBody解析json参数映射为实体类

    @Data
    public class User implements Serializable {
        private String id;
        
        @NotNull(message = "姓名不能为空")
        @Size(min = 1, max = 20, message = "姓名长度必须在1-20之间")
        private String name;
    
        @Min(value = 10, message = "年龄必须大于10")
        @Max(value = 150, message = "年龄必须小于150")
        private Integer age;
    
        @Email(message = "邮箱格式不正确")
        private String email;
        
    }

    (2)Controller中加上注解

    @Controller
    public class LoginController {
    
        @PostMapping("/test")
        @ResponseBody
        public AjaxResult test(@Validated @RequestBody User user){
            System.out.println(user);
            return AjaxResult.success(user);
        }
        
    }

    (3)PostMan测试

     响应数据:数据不友好,使用全局异常捕获返回友好提示。

    {
        "timestamp": "2020-06-17T10:16:50.591+0000",
        "status": 400,
        "error": "Bad Request",
        "errors": [
            {
                "codes": [
                    "Email.user.email",
                    "Email.email",
                    "Email.java.lang.String",
                    "Email"
                ],
                "arguments": [
                    {
                        "codes": [
                            "user.email",
                            "email"
                        ],
                        "arguments": null,
                        "defaultMessage": "email",
                        "code": "email"
                    },
                    [],
                    {
                        "arguments": null,
                        "defaultMessage": ".*",
                        "codes": [
                            ".*"
                        ]
                    }
                ],
                "defaultMessage": "邮箱格式不正确",
                "objectName": "user",
                "field": "email",
                "rejectedValue": "123456789",
                "bindingFailure": false,
                "code": "Email"
            },
            {
                "codes": [
                    "NotNull.user.name",
                    "NotNull.name",
                    "NotNull.java.lang.String",
                    "NotNull"
                ],
                "arguments": [
                    {
                        "codes": [
                            "user.name",
                            "name"
                        ],
                        "arguments": null,
                        "defaultMessage": "name",
                        "code": "name"
                    }
                ],
                "defaultMessage": "姓名不能为空",
                "objectName": "user",
                "field": "name",
                "rejectedValue": null,
                "bindingFailure": false,
                "code": "NotNull"
            }
        ],
        "message": "Validation failed for object='user'. Error count: 2",
        "path": "/test"
    }

    (4)全局处理异常,处理 @RequestBody参数校验异常,统一返回格式自定义

    @RestControllerAdvice
    public class GlobalExceptionHandler {
    
        /**
         * 处理 @RequestBody参数校验异常
         */
        @ExceptionHandler(value = MethodArgumentNotValidException.class)
        public AjaxResult handleBindGetException(MethodArgumentNotValidException ex) {
    
            Map<String, Object> body = new LinkedHashMap<>();
            body.put("timestamp", new Date());
    
            // 获取所有异常
            List<String> errors = ex.getBindingResult()
                    .getFieldErrors()
                    .stream()
                    .map(DefaultMessageSourceResolvable::getDefaultMessage)
                    .collect(Collectors.toList());
            body.put("errors", errors);
            return  AjaxResult.error("提交的数据校验失败", body);
        }
    
    }

    (5)再次测试:

     

     第二类:使用表单传参,即不使用@RequestBody,跟上面的第一类异常捕获的异常类型不同而已。

    @Controller
    public class LoginController {
    
        @PostMapping("/test1")
        @ResponseBody
        public AjaxResult test1(@Validated User user){
            System.out.println(user);
            return AjaxResult.success(user);
        }
        
    } 
    @RestControllerAdvice
    public class GlobalExceptionHandler {
    
        /**
         * 处理不带任何注解的参数绑定校验异常,即没有@RequestBody
         */
        @ExceptionHandler(BindException.class)
        public AjaxResult handleBingException(BindException ex) {
            Map<String, Object> body = new LinkedHashMap<>();
            body.put("timestamp", new Date());
            // 获取所有异常
            List<String> errors = ex.getBindingResult()
                    .getFieldErrors()
                    .stream()
                    .map(DefaultMessageSourceResolvable::getDefaultMessage)
                    .collect(Collectors.toList());
            body.put("errors", errors);
            return AjaxResult.error("提交的数据校验失败", body);
        }
    
    }

     第三类:单个参数校验

    (1)直接在参数前加上校验注解

    /**
     * @author shw
     * @version 1.0
     * @date 2020/6/17 17:14
     */
    @RestController
    @Validated
    public class UserController  {
         @GetMapping("/test3")
         public AjaxResult test3(@NotNull(message = "name不能为空")  String name ,@Email(message = "邮箱格式不正确") String email){
            System.out.println(name);
            System.out.println(email);
            return AjaxResult.success(name+" "+email);
        }
    }

    注意:需要在类上添加@Validated注解,否则不会校验。

    @RestControllerAdvice
    public class GlobalExceptionHandler {
        
        /**
         * 处理所有参数校验时抛出的异常
         * @param ex
         * @return
         */
        @ExceptionHandler(value = ValidationException.class)
        public AjaxResult handleBindException(ValidationException ex) {
            AjaxResult body = new AjaxResult();
        
            body.put("timestamp", new Date());
        
            // 获取所有异常
            List<String> errors = new LinkedList<>();
            if (ex instanceof ConstraintViolationException) {
                ConstraintViolationException exs = (ConstraintViolationException) ex;
                Set<ConstraintViolation<?>> violations = exs.getConstraintViolations();
                for (ConstraintViolation<?> item : violations) {
                    errors.add(item.getMessage());
                }
            }
            body.put("errors", errors);
            body.put("code",900);
            body.put("msg","提交的数据校验失败");
        
            return body;
        }
    
    }

     第四类:参数校验分组

    在实际开发中经常会遇到这种情况:添加用户时,id是由后端生成的,不需要校验id是否为空,但是修改用户时就需要校验id是否为空。如果在接收参数的User实体类的id属性上添加NotNull,那么都会校验,显然不符合要求。这时候就可以定义分组,在需要校验id的时候校验,不需要的时候不校验。

    (1)定义表示组别的接口类

    import javax.validation.groups.Default;
    public interface Create extends Default {
    }
    import javax.validation.groups.Default;
    public interface Update extends Default { 
    }

    (2)在实体类的注解中标记id使用上面定义的组

    @Data
    public class User implements Serializable {
    
        @NotNull(message = "id不能为空",groups = Update.class)
        private String id;
    
        @NotNull(message = "姓名不能为空")
        @Size(min = 1, max = 20, message = "姓名长度必须在1-20之间")
        private String name;
    
        @Min(value = 10, message = "年龄必须大于10")
        @Max(value = 150, message = "年龄必须小于150")
        private Integer age;
    
        @Email(message = "邮箱格式不正确")
        private String email;
    
    }

    (3)在controller中使用@Validated指定使用哪个@Controllerpublicclass LoginController {

        @PostMapping("/test4")
        @ResponseBody
        // 指定Update,这样就会校验id属性是否为空
        // 注意:一般来说要添加Default.class,否则不会执行其他的校验(但是我也可以不加,也校验了,哈哈哈,不知道为啥)public AjaxResult test4(@Validated({Update.class, Default.class}) User user){
         System.out.println(user); return AjaxResult.success(user); } }

    @Controller
    public class LoginController {
    
        @PostMapping("/test4")
        @ResponseBody
      // 这里就没有加default.class,但是测试结果还是校验其他数据了的
    public AjaxResult test4(@Validated(Create.class) User user){ System.out.println(user); return AjaxResult.success(user); } }

    在controller中使用@Valid 或者@Validated 注解校验的区别:

     嵌套验证:

    public class Item {
        @NotNull(message = "id不能为空")
        @Min(value = 1, message = "id必须为正整数")
        private Long id;
    
        @Valid
        @NotNull(message = "props不能为空")
        @Size(min = 1, message = "至少要有一个属性")
        private List<Prop> props;
    }
    public class Prop {
        @NotNull(message = "pid不能为空")
        @Min(value = 1, message = "pid必须为正整数")
        private Long pid;
    
        @NotNull(message = "vid不能为空")
        @Min(value = 1, message = "vid必须为正整数")
        private Long vid;
    
        @NotBlank(message = "pidName不能为空")
        private String pidName;
    
        @NotBlank(message = "vidName不能为空")
        private String vidName;
    }
    @RestController
    public class ItemController {
        @RequestMapping("/item/add")
        public void addItem(@Validated Item item, BindingResult bindingResult) {
            doSomething();
        }
    }

    验证多个对象:

    @Controller  
    public class PeopleController {  
        @RequestMapping("/add")  
        public @ResponseBody String add(@Validated People pp, BindingResult result1, @Validated Person ps, BindingResult result2)  
        {  
            if(result1.hasErrors())  
            {  
                return false;  
            }  
            if(result2.hasErrors())  
            {  
                return false;  
            }  
            return true;  
        }  
    }
  • 相关阅读:
    如何在软件频繁改变时测试?归纳总结
    PE文件格式资源
    PE文件格式重定位
    PEB模块链表Ldr
    PE文件格式导入表和IAT
    PE文件格式导出表
    win32汇编问题指令
    win32汇编问题堆栈
    硬盘基本知识
    SEH异常处理
  • 原文地址:https://www.cnblogs.com/sun2020/p/13155285.html
Copyright © 2011-2022 走看看