非Controller控制层参数校验要怎么做

上篇文章中我们介绍了如何自定义Springboot参数约束注解和约束校验器,默认情况下,Springboot中的参数校验都是在Controller控制层完成的,那如果我们想要在非Controller控制层 进行参数校验要怎么办呢?本文将介绍非Controller控制层 参数校验的方法。

看源码

从上篇文章中我们知道Spring提供了JSR-303 的一个变种  @Validated  ,下面我们看一下 @Validated 的部分源码。

图片图片

Validator

看注释官方是让我们去看看Validator#validate() 方法,官方让我看我们就去看呗,索性直接看Validator 源码:

public interface Validator {

  Set validate(T object, Class... groups);

  Set validateProperty(T object,
              String propertyName,
              Class... groups);

  Set validateValue(Class beanType,
              String propertyName,
              Object value,
              Class... groups);

 BeanDescriptor getConstraintsForClass(Class clazz);

  T unwrap(Class type);

 ExecutableValidator forExecutables();
}

那Validator是做什么用的呢?

Validator 接口 定义了用于执行验证的方法,用于验证对象的字段值是否符合指定的约束条件。它主要提供了以下几个核心方法:

  • Set validate(T object, Class... groups):
  • 该方法用于验证给定对象是否符合指定的约束条件。

    参数 object是要验证的对象,参数 groups可选,表示验证组。

    返回一个 Set 集合,其中包含违反约束条件的 ConstraintViolation 对象。

  • Set validateProperty(T object, String propertyName, Class... groups):
  •       该方法用于验证给定对象的特定属性是否符合指定的约束条件。

          参数 object是要验证的对象,参数 propertyName是属性名,参数 groups可选,表示验证组。

          返回一个 Set 集合,其中包含违反约束条件的 ConstraintViolation 对象。

  • Set validateValue(Class beanType, String propertyName, Object value, Class... groups):
  •        该方法用于验证给定属性值是否符合指定的约束条件,而不需要实际创建对象实例。

           参数 beanType是对象类型,参数 propertyName是属性名,参数 value是属性值,参数 groups可选,表示验证组。

           返回一个 Set 集合,其中包含违反约束条件的 ConstraintViolation 对象。

    这些方法能够对 Java 对象进行全面的验证,从整个对象级别到单个属性值的验证,都可以通过 Validator 接口提供的方法来实现。

    实操

    从上面的介绍我们可以看到,实际上参数校验是通过 Validator 接口的实现类来验证的。下面我们通过代码来看一下 Validator 接口怎么用?

    先把我们的老演员拿出来,下面再校验就用它了。

    @Data
    public class UserBean {
    
        @NotBlank
        private String username;
    
        @Min(value = 18)
        private Integer age;
    }

    依赖Spring 容器

    JSR 303 提供了 Validator 接口作为规范接口,用于实现数据校验功能。在实际应用中,我们并不需要自己从头开始实现这一接口,因为已经有官方参考实现——Hibernate Validator。Spring 框架作为广泛使用的Java开发框架,自然也遵循这一原则,它并没有重复造轮子,而是采用了 Hibernate Validator 的实现来支持校验功能。

    因此,当我们使用 Spring 框架进行开发时,通常意味着我们可以直接利用 Spring 提供的集成,将 Validator 接口注入到我们的组件中,然后方便地使用它来进行数据校验。

    下面来看下如何在Springboot中使用 Validator 接口。

    @SpringBootTest
    class SpringbootPracticeApplicationTests {
     @Resource
        private Validator validator;
    
     @Test
        public void testValidator(){
            UserBean userBean = new UserBean();
            userBean.setAge(17);
            userBean.setUsername(null);
    
            Set constraintViolations = validator.validate(userBean);
            System.out.println("validate 校验对象属性:");
            System.out.println(constraintViolations);
            System.out.println();
    
            constraintViolations = validator.validateProperty(userBean, "age");
            System.out.println("validateProperty校验age属性是否合规:");
            System.out.println(constraintViolations);
            System.out.println();
    
            constraintViolations = validator.validateValue(UserBean.class , "age", 14 );
            System.out.println("validateValue校验age属性的值是否合规:");
            System.out.println(constraintViolations);
        }
    }

    测试结果:

    validate 校验对象属性:
    [ConstraintViolationImpl{interpolatedMessage='不能为空', propertyPath=username, rootBeanClass=class site.suncodernote.validation.UserBean, messageTemplate='{jakarta.validation.constraints.NotBlank.message}'}, 
    ConstraintViolationImpl{interpolatedMessage='最小不能小于18', propertyPath=age, rootBeanClass=class site.suncodernote.validation.UserBean, messageTemplate='{jakarta.validation.constraints.Min.message}'}]
    
    validateProperty校验age属性是否合规:
    [ConstraintViolationImpl{interpolatedMessage='最小不能小于18', propertyPath=age, rootBeanClass=class site.suncodernote.validation.UserBean, messageTemplate='{jakarta.validation.constraints.Min.message}'}]
    
    validateValue校验age属性的值是否合规:
    [ConstraintViolationImpl{interpolatedMessage='最小不能小于18', propertyPath=age, rootBeanClass=class site.suncodernote.validation.UserBean, messageTemplate='{jakarta.validation.constraints.Min.message}'}]

    从打印结果中可以发现具体是哪些字段约束不通过、具体的提示信息都有。

    不依赖Spring 容器

    JSR 303  是一种规范,意味着它 不依赖Spring 容器 ,能用Java的地方都可以用它,那如果我们在非Spring框架的项目中怎么使用它呢?

    其实,最主要的是能获取到 Validator 接口的实现,然后就可以进行验证了。Validator 接口的实现可以通过如下代码获取,前提是要引入 Hibernate Validator 的依赖或者自己重新造轮子实现Validator 接口。

    public class ValidationUtils {
    
        public static Validator getValidator(){
            ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
            return factory.getValidator();
        }
    }
    /**
     * @author 公众号-索码理(suncodernote)
     */
    public class ValidatorTest {
        public static void main(String[] args) {
            Validator validator = ValidationUtils.getValidator();
    
            UserBean userBean = new UserBean();
            userBean.setAge(17);
            userBean.setUsername(null);
    
            Set constraintViolations = validator.validate(userBean);
            System.out.println("validate 校验对象属性:");
            System.out.println(constraintViolations);
            System.out.println();
    
            constraintViolations = validator.validateProperty(userBean, "age");
            System.out.println("validateProperty校验age属性是否合规:");
            System.out.println(constraintViolations);
            System.out.println();
    
            constraintViolations = validator.validateValue(UserBean.class , "age", 14 );
            System.out.println("validateValue校验age属性的值是否合规:");
            System.out.println(constraintViolations);
        }
    }

    打印结果:

    23:57:10.700 [main] INFO org.hibernate.validator.internal.util.Version -- HV000001: Hibernate Validator 8.0.1.Final
    validate 校验对象属性:
    [ConstraintViolationImpl{interpolatedMessage='最小不能小于18', propertyPath=age, rootBeanClass=class site.suncodernote.validation.UserBean, messageTemplate='{jakarta.validation.constraints.Min.message}'}, 
    ConstraintViolationImpl{interpolatedMessage='不能为空', propertyPath=username, rootBeanClass=class site.suncodernote.validation.UserBean, messageTemplate='{jakarta.validation.constraints.NotBlank.message}'}]
    
    validateProperty校验age属性是否合规:
    [ConstraintViolationImpl{interpolatedMessage='最小不能小于18', propertyPath=age, rootBeanClass=class site.suncodernote.validation.UserBean, messageTemplate='{jakarta.validation.constraints.Min.message}'}]
    
    validateValue校验age属性的值是否合规:
    [ConstraintViolationImpl{interpolatedMessage='最小不能小于18', propertyPath=age, rootBeanClass=class site.suncodernote.validation.UserBean, messageTemplate='{jakarta.validation.constraints.Min.message}'}]

    可以看到 依赖Spring 容器 和 不依赖Spring 容器 ,两者的调用结果都是相同的,不同之处就在于 Validator 的获取,前者直接从 Spring 容器 获取,后者需要通过工厂类获取,适用范围更广一点,在Spring框架中也能使用。

    总结

    本文介绍了在非Controller控制层参数校验的两种方法,不管是哪种获取方法只要能达到结果就行,不过建议使用后者,可以把后者封装成工具类,统一管理校验方法。