• JSR303以及常见Validator实现


    《乐之者java-validator》学习笔记

    前言

    传统参数校验

    假设有登录场景,登录参数为LoginForm:

    @Data
    public class LoginForm {
        private String username;
        private String password;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    使用校验逻辑如下:

     public void login(LoginForm form){
        if(form == null){
             throw new RuntimeException("login登录时参数为null");
         }
    
         if(form.getUsername() == null  || form.getUsername().trim().isEmpty()){
             throw new RuntimeException("login登录时为username不合规");
         }
    
         if(form.getPassword() == null  || form.getPassword().trim().isEmpty()){
             throw new RuntimeException("login登录时为password不合规");
         }
    
         //todo business....
     }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    JSR303 规范

    什么是Java EE 规范

    java EE 规范只有API,没有实现具体的实现,由各厂商实现。例如:

    • javax.sql.*
    • javax.servlet
    • javax.jmx

    java se自带的规范

    在这里插入图片描述
    并不是仅有这些,上述为javaee常见的一些规范。
    对于一些上面没有的,则需要手动引入api依赖。

    java ee规范由谁制定

    https://jcp.org/en/home/index

    Java Community Process

    从javax 到 jakarta

    oracle收购sun之后,oralce开始实行java商业授权。迫于java开源社区的压力,oracle将javax规范捐献给了eclipse.
    但是不允许使用javax.*,所以才有了现在的jakarta
    所以以后会出现:jakarta.sq.....l

    不要apache.jakarta混为一谈。


    JSR303的实现 - Hibernate

    maven依赖

    <dependency>
          <groupId>org.hibernate.validator</groupId>
          <artifactId>hibernate-validator</artifactId>
          <version>6.0.18.Final</version>
          <exclusions>
            <exclusion>
              <groupId>jakarta.validation</groupId>
              <artifactId>jakarta.validation-api</artifactId>
            </exclusion>
          </exclusions>
        </dependency>
    
        <!-- hibernate-validator中已经包含了:validation-api
        <dependency>
          <groupId>javax.validation</groupId>
          <artifactId>validation-api</artifactId>
          <version>2.0.1.Final</version>
        </dependency>
        -->
    
        <!-- 等价于jakarta.validation-api
        <dependency>
          <groupId>jakarta.validation</groupId>
          <artifactId>jakarta.validation-api</artifactId>
          <version>2.0.1</version>
        </dependency>
      -->
       <!-- 用于解析message中的el表达式 -->
        <dependency>
          <groupId>org.apache.tomcat.embed</groupId>
          <artifactId>tomcat-embed-el</artifactId>
          <version>9.0.29</version>
        </dependency>
    
    • 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

    UserInfo

    @Data
    public class UserInfo {
        private Long id;
        private String name;
        private Integer age;
        private String email;
        private LocalDate birthdate;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    ValidatorUtil

    public class ValidatorUtil {
        private static Validator validator;
        static {
        	//涉及到SPI
            validator = Validation.buildDefaultValidatorFactory().getValidator();
        }
    
        public static void valid(UserInfo userInfo){
            //如果校验对象userInfo 不通过,则存在校验不通过信息
            Set<ConstraintViolation<UserInfo>> set =   validator.validate(userInfo);
            set.stream().forEach(cv -> {
                System.out.println("属性:"+cv.getPropertyPath()+",属性值:"+cv.getInvalidValue()+",校验不通过,触发规则:"+cv.getMessage());
            });
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    Validator初始化流程

    Validation.buildDefaultValidatorFactory().getValidator();逻辑可以拆分为如下两步:

    1. Validation.buildDefaultValidatorFactory() : 获取 ValidatorFactory
    2. $1.getValidator(); : 获取Validator
    获取 ValidatorFactory
    	public static ValidatorFactory buildDefaultValidatorFactory() {
    		 //1.1 byDefaultProvider() -- 返回 GenericBootstrapImpl();
             //1.2 GenericBootstrapImpl.configure() --根据SPI 加载 javax.validation.spi.ValidationProvider
             //1.3 ValidationProvider.buildValidatorFactory();
    		return byDefaultProvider().configure().buildValidatorFactory();
    	}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    SPI
    ValidationProvider(SPI) -> hibernate.jar/META-INF/services/javax.validation.spi.ValidationProvider
    在这里插入图片描述

    HibernateValidator

    public class HibernateValidator implements ValidationProvider<HibernateValidatorConfiguration> {
    
    	@Override
    	public HibernateValidatorConfiguration createSpecializedConfiguration(BootstrapState state) {
    		return HibernateValidatorConfiguration.class.cast( new ConfigurationImpl( this ) );
    	}
    
    	@Override
    	public Configuration<?> createGenericConfiguration(BootstrapState state) {
    		return new ConfigurationImpl( state );
    	}
    	
    	//返回ValidatorFactoryImpl
    	@Override
    	public ValidatorFactory buildValidatorFactory(ConfigurationState configurationState) {
    		return new ValidatorFactoryImpl( configurationState );
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    最终获取的ValidatorFactory: org.hibernate.validator.internal.engine.ValidatorFactoryImpl

    获取Validator

    最终Validator类型: org.hibernate.validator.internal.engine.ValidatorFactoryImpl



    demo1

    constraints

    @Data
    public class UserInfo {
        @javax.validation.constraints.NotNull
        private Long id;
    
        @javax.validation.constraints.NotEmpty
        private String name;
    
        @org.hibernate.validator.constraints.Range
        private Integer age;
    
        @javax.validation.constraints.Email
        private String email;
    
        private LocalDate birthdate;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    test-case

     public void test01(){
            UserInfo userInfo = new UserInfo();
            userInfo.setName("");
            userInfo.setAge(120);
            userInfo.setEmail("www.baidu.com");
            ValidatorUtil.valid(userInfo);
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    执行结果
    在这里插入图片描述

    常见约束constraints

    javax.validation.constraints

    在这里插入图片描述

    org.hibernate.validator.constraints

    在这里插入图片描述

    约束与Validator的绑定关系—ConstraintHelper

    package org.hibernate.validator.internal.metadata.core;
    public class ConstraintHelper {
    	public ConstraintHelper() {
    		Map<Class<? extends Annotation>, List<ConstraintValidatorDescriptor<?>>> tmpConstraints = new HashMap<>();
    		//为NotNull.class 绑定 NotNullValidator.class
    		putConstraint( tmpConstraints, NotNull.class, NotNullValidator.class );
    		putConstraint( tmpConstraints, Null.class, NullValidator.class );
    		putConstraint( tmpConstraints, Email.class, EmailValidator.class );
    
    		//......
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    分组校验

    例如上面的UserInfo,

    • id: 在新增时不校验,在编辑时校验id
    • 其他属性,在新增或编辑时都校验

    定义分组-class

    public class ValidGroups {
        public interface  Add{}
        public interface  Edit{}
    }
    
    • 1
    • 2
    • 3
    • 4

    UserInfo

    @Data
    public class UserInfo {
        @javax.validation.constraints.NotNull(groups = {ValidGroups.Edit.class})
        private Long id;
    
        @javax.validation.constraints.NotEmpty(groups = {ValidGroups.Add.class,ValidGroups.Edit.class})
        private String name;
    
        @org.hibernate.validator.constraints.Range(min = 18,message = "年龄小于{value},未成年",groups = {ValidGroups.Add.class,ValidGroups.Edit.class})
        private Integer age;
    
        @javax.validation.constraints.Email(groups = {ValidGroups.Add.class,ValidGroups.Edit.class})
        private String email;
    
        private LocalDate birthdate;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    javax.validation.Validator

    public interface Validator {
    	<T> Set<ConstraintViolation<T>> validate(T object, Class<?>... groups);
    }
    
    • 1
    • 2
    • 3

    ValidatorUtil

      public  static List<String>  valid(UserInfo userInfo,Class validGroup){
            Set<ConstraintViolation<UserInfo>> set =   validator.validate(userInfo,validGroup);
             List<String> list = set.stream().map(cv ->
                 "属性:" + cv.getPropertyPath() + ",属性值:" + cv.getInvalidValue() + ",校验不通过,触发规则:" + cv.getMessage()
            ).collect(Collectors.toList());
    
            list.forEach(System.out::println);
            return list;
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    级联校验

    UserInfo中存在grade年级信息,年级信息有自己的校验规则。

    @Data
    public class UserInfo {
       //略...
        @javax.validation.Valid //级联校验
        private Grade grade;
    }
    @Data
    public class Grade {
        @NotEmpty
        private String gradeNo;
    
        @Range(min = 5,max = 40)
        private int classNum;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    在执行validator.validate(userInfo),若需要开启级联注解,则需要使用 @javax.validation.Valid注解。


    自定义校验

    定义校验规则constraints

    @Target({ FIELD })
    @Retention(RUNTIME)
    @Documented
    @Constraint(validatedBy = {UserStatusValidator.class }) //指定validator
    public @interface UserStatus {
        String message() default "{user_status 必须为100/101/1002}";
        Class<?>[] groups() default { };
        Class<? extends Payload>[] payload() default { };
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    定义Validator

    public class UserStatusValidator implements javax.validation.ConstraintValidator<UserStatus, Integer> {
        @Override
        public boolean isValid(Integer value, ConstraintValidatorContext context) {
            return value == 100 || value == 101 || value == 102;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    failfast 校验

    前文中的validator,会将所有的不合规校验信息全部收集完成之后,才返回。
    如何设置,发现一个不合规信息,立即返回。

        Validator failfastValidator = Validation.byProvider(HibernateValidator.class)
                    .configure().failFast(true).buildValidatorFactory().getValidator();
    
    • 1
    • 2

    非javabean校验

    UserInfoService

    public class UserInfoService {
        public String getByName(@NotNull  String name) throws NoSuchMethodException {
            // 校验...
            Method method = this.getClass().getDeclaredMethod("getByName", String.class);
            ValidatorUtil.validNotBean(this,method,new Object[]{name});
            return "name:" + name;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    ExecutableValidator

    public class ValidatorUtil {
        private static Validator validator;
        private static ExecutableValidator executableValidator;
        static {
      
            validator = Validation.buildDefaultValidatorFactory().getValidator();
            executableValidator = validator.forExecutables();
        }
    
        public static <T> List<String>  validNotBean(T object , Method method, Object[] params , Class<?> ... groups){
            Set<ConstraintViolation<T>> set =  executableValidator.validateParameters(object, method, params, groups);
             List<String> list = set.stream().map(cv ->
                 "属性:" + cv.getPropertyPath() + ",属性值:" + cv.getInvalidValue() + ",校验不通过,触发规则:" + cv.getMessage()
            ).collect(Collectors.toList());
    
            list.forEach(System.out::println);
            return list;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    Springboot 校验

    Springboot环境搭建

    maven依赖
      <dependencies>
        <dependency>
          <groupId>org.springframework.boot</groupId>
          <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
      </dependencies>
    
      <dependencyManagement>
        <dependencies>
          <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-dependencies</artifactId>
            <version>2.6.7</version>
            <scope>import</scope>
            <type>pom</type>
          </dependency>
        </dependencies>
      </dependencyManagement>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    App主类

    @SpringBootApplication
    public class App {
    
        public static void main( String[] args )
        {
            SpringApplication.run(App.class);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    Controller

    @RestController
    public class UserInfoController {
    
        @GetMapping("/getByName")
        public String getByName(String name){
            return "name:" + name;
        }
    
        @GetMapping("/addUser")
        public String addUser(UserInfo userInfo){
            List<String> list = ValidatorUtil.valid(userInfo);
            if(list.size() > 0){
                return "校验不成功";
            }
            return "添加成功";
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    Springmvc校验 - 声明式

    @javax.validation.Valid

    @GetMapping("/addUser2")
        public String addUser2(@javax.validation.Valid UserInfo userInfo, BindingResult bindingResult){
            if(bindingResult.hasErrors()){
                bindingResult.getAllErrors().forEach(error ->{
                    System.out.println(error.getObjectName()+"::"+error.getDefaultMessage());
                });
    
                bindingResult.getFieldErrors().forEach(fieldError -> {
                    System.out.println(fieldError.getField()+":"+fieldError.getDefaultMessage()+",没有通过校验的值:"+fieldError.getRejectedValue() );
                });
            }
            return "添加成功";
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    @javax.validation.Valid 无法指定分组。

    @org.springframework.validation.annotation.Validated - 指定分组

     @GetMapping("/addUser3")
        public String addUser3(@org.springframework.validation.annotation.Validated(ValidGroups.Add.class) UserInfo userInfo, BindingResult bindingResult){
            if(bindingResult.hasErrors()){
                bindingResult.getAllErrors().forEach(error ->{
                    System.out.println(error.getObjectName()+"::"+error.getDefaultMessage());
                });
                bindingResult.getFieldErrors().forEach(fieldError -> {
                    System.out.println(fieldError.getField()+":"+fieldError.getDefaultMessage()+",没有通过校验的值:"+fieldError.getRejectedValue() );
                });
            }
            return "添加成功";
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    统一异常处理

    controller – 无BindingResult处理

     @GetMapping("/addUser4")
        public String addUser4(@org.springframework.validation.annotation.Validated(ValidGroups.Add.class) UserInfo userInfo){
            return "添加成功";
        }
    
    • 1
    • 2
    • 3
    • 4

    若校验不通过,则会直接抛出异常。

    @ExceptionHandler

    在每个controller中添加@ExceptionHandler注解的方法,来处理该controller的异常信息。

      @ExceptionHandler(BindException.class)
       public String handlerEx(BindException e){
           List<FieldError> fieldErrors = e.getFieldErrors();
           StringBuilder sb = new StringBuilder();
           fieldErrors.forEach(fe ->
                   sb.append("属性:").append(fe.getField()).append("验证不通过,原因:").append(fe.getDefaultMessage()).append(";")
           );
    
           return sb.toString();
       }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    全局异常处理-ControllerAdvice

    参见:SpringMVC-特性annotation - @ControllerAdvice部分。

    Valid 和 Validate 区别

    1. Validate 支持分组
    2. Validate支持方法参数上的自动校验。
    	@GetMapping("/getByName")
     	 //在方法上或者类上增加  @Validated :@Null方有效果
        public String getByName(@NotNull String name){
            return "name:" + name;
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    此时的@Null校验是不起作用的,需要在该方法或者类上增加@Validate注解。
    当在类上增加此注解时,若校验不通过,抛出ConstraintViolationException异常.

    
       @ExceptionHandler(ConstraintViolationException.class)
       public String handlerEx2(ConstraintViolationException e){
           Set<ConstraintViolation<?>> constraintViolations = e.getConstraintViolations();
           StringBuilder sb = new StringBuilder();
          //TODO ....
           return sb.toString();
       }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
  • 相关阅读:
    百亿规模京东实时浏览记录系统的设计与实现
    python3GUI--仿做一个网易云音乐(第三弹v2.0)By:PyQt5(附下载地址)
    【.Net】ASP.NET项目使用Swagger生成API文档
    java设计模式
    批量替换文本中的多组字符串
    基于Springboot + vue 开发的外卖餐购项目(后台管理+消费者端)
    外设驱动库开发笔记49:BY25Qxx存储器驱动
    09-07 周三 周志华-机器学习的下载和阅读总结
    Shiro安全框架
    第十四章第一节:Java集合框架之ArrayList
  • 原文地址:https://blog.csdn.net/it_freshman/article/details/125491461