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;  
        }  
    }
  • 相关阅读:
    Maidsafe-去中心化互联网白皮书
    The Top 20 Cybersecurity Startups To Watch In 2021 Based On Crunchbase
    Top 10 Blockchain Security and Smart Contract Audit Companies
    The 20 Best Cybersecurity Startups To Watch In 2020
    Blockchain In Cybersecurity: 11 Startups To Watch In 2019
    004-STM32+BC26丨260Y基本控制篇(阿里云物联网平台)-在阿里云物联网平台上一型一密动态注册设备(Android)
    涂鸦开发-单片机+涂鸦模组开发+OTA
    000-ESP32学习开发-ESP32烧录板使用说明
    03-STM32+Air724UG远程升级篇OTA(阿里云物联网平台)-STM32+Air724UG使用阿里云物联网平台OTA远程更新STM32程序
    03-STM32+Air724UG远程升级篇OTA(自建物联网平台)-STM32+Air724UG实现利用http/https远程更新STM32程序(TCP指令,单片机程序检查更新)
  • 原文地址:https://www.cnblogs.com/sun2020/p/13155285.html
Copyright © 2011-2022 走看看