美文网首页程序员
Springboot HibernateValidator 中自

Springboot HibernateValidator 中自

作者: Wayne维基 | 来源:发表于2020-09-07 00:33 被阅读0次

    问题背景

    服务原来自定义了Validation检查器,

    自定义的ConstraintValidator可能的写法举例:

    public class ProjectAuth implements ConstraintValidator<ProjectAuth, Object> {
    
        @Autowired
        private ProjectService projectService;
    
        @Override
        public void initialize(ProjectAuth project) {
            // ...省略若干
        }
    
        @Override
        public boolean isValid(Object value, ConstraintValidatorContext context) {
            // ...省略若干
            return true;
        }
    

    看上去一切正常,但是后来引入了HibernateValidator,上述代码中Autowired注入的服务是null,也就是说服务没有注入。

    @Autowired
    private ProjectService projectService;
    

    解决问题

    解决问题的第一直觉是,增加@Component,这样spring可能有自动注入机制注入ProjectAuth,然后顺便注入依赖。

    @Component
    public class ProjectAuth implements ConstraintValidator<ProjectAuth, Object>
    

    因为服务注入失败,所以第一直觉是要加@Component 或者 @Service
    ,但是试验过之后并没有解决,注入依然是null,这时候问题引起了我的兴趣。

    之后搜索了很多中文的博客...(没有找到解决的方法,用法都差不多)
    关键是找到stackoverflow上的类似问题
    Spring Boot - Hibernate custom constraint doesn't inject Service

    无法访问stackOverFlow以下附上了问题的解决:
    To have the Spring beans injected into your ConstraintValidator, you need a specific ConstraintValidatorFactory which should be passed at the initialization of the ValidatorFactory
    意思是:注入服务到我们自己的ConstraintValidator,需要在ValidatorFactory初始化的时候指定特定的ConstraintValidatorFactory,但是这里没有给出特定的Factory。

    为了节省大家的时间,我这里先附上问题的解决方式,就是在配置的时候加上如下配置。原因我在后续分析,有时间的朋友可以继续读完全文。

    解决方式是添加以下配置:

    validatorFactory = Validation.byProvider(HibernateValidator.class)
                    .configure()
                    .constraintValidatorFactory(new SpringConstraintValidatorFactory(autowireCapableBeanFactory)) # 解决问题的关键
                    .failFast(true)
                    .buildValidatorFactory();
    

    原因分析

    看到了问题的答案,接下来分析以下原因。
    (了解以下Validation背景,SPI,Springboot Bean加载方式会加深对于原因的理解)

    背景 Validation介绍

    JSR是Java Specification Requests的缩写,意思是Java 规范提案。关于数据校验这块,最新的是JSR380,也就是我们常说的Bean Validation 2.0。

    Bean Validation 2.0 是JSR第380号标准。该标准连接如下:

    Bean Validation是一个通过配置注解来验证参数的框架,它包含两部分Bean Validation API(规范)和Hibernate Validator(实现)

    参考:深入了解数据校验:Java Bean Validation 2.0 JSR303、JSR349、JSR380 Hibernate-Validation 6.x使用案例

    configure()

    回到上述问题解决的配置,我们看到前两行

    validatorFactory = Validation.byProvider(HibernateValidator.class)
                    .configure()
    

    第一行就是加载了provider
    核心的源码是:

    // validationProviderClass是入参,就是HibernateValidator.class
    this.validationProviderClass = validationProviderClass;
    

    第二行.configure()
    核心源码是:

    // if no resolver is given, simply instantiate the given provider
                if ( resolver == null ) {
                    U provider = run( NewProviderInstance.action( validationProviderClass ) );
                    return provider.createSpecializedConfiguration( state );
                }
    

    简化一下理解:
    run( NewProviderInstance.action( validationProviderClass ) )
    这里结合第一行,等价于 new HibernateValidator();

    provider.createSpecializedConfiguration的核心源码如下

    @Override
        public HibernateValidatorConfiguration createSpecializedConfiguration(BootstrapState state) {
            return HibernateValidatorConfiguration.class.cast( new ConfigurationImpl( this ) );
        }
    

    返回HibernateValidatorConfiguration, 通过cast做强制类型转换。
    这几个Configuration类的继承关系如下:


    Configuration类的继承关系

    ConfigurationImpl其中支持了Validation的各项配置,
    配置的面向接口编程得到实现。

    配置中和ConstraintValidator相关的几个关键代码:

    this.defaultConstraintValidatorFactory = new ConstraintValidatorFactoryImpl();
    
    public final void setConstraintValidatorFactory(ConstraintValidatorFactory constraintValidatorFactory) {
            this.constraintValidatorFactory = constraintValidatorFactory;
        }
    

    也就是说如果没有定义,就会通过new的方式创建一个。
    也可以通过set的方式设置一个。

    目前我用的版本中ConstraintValidatorFactory只有两个实现:
    Spring 和 hibernate的实现,而new的方式创建的就是hibernate的实现


    ConstraintValidatorFactory的实现类

    constraintValidatorFactory实现

    constraintValidatorFactory是加载自定义约束的工厂,前面已经介绍了,目前的配置只有两个实现Spring 和 hibernate,hibernate的实现是默认的配置。
    这两个实现的不同:
    先看hibernate的ConstraintValidatorFactoryImpl

    public class ConstraintValidatorFactoryImpl implements ConstraintValidatorFactory {
    
        @Override
        public final <T extends ConstraintValidator<?, ?>> T getInstance(Class<T> key) {
            return run( NewInstance.action( key, "ConstraintValidator" ) );
        }
    
        @Override
        public void releaseInstance(ConstraintValidator<?, ?> instance) {
            // noop
        }
    
        private <T> T run(PrivilegedAction<T> action) {
            return System.getSecurityManager() != null ? AccessController.doPrivileged( action ) : action.run();
        }
    }
    

    return run( NewInstance.action( key, "ConstraintValidator" ) );这个代码内部等同于
    clazz.newInstance();也就是key.newInstance(),注入的key就是用户自定义的ConstraintValidator,我们这个文章的例子相当于 new ProjectAuth();
    到这里大家应该理解了为什么注入的服务会是null。

    再看Spring中的实现:

    public class SpringConstraintValidatorFactory implements ConstraintValidatorFactory {
    
        private final AutowireCapableBeanFactory beanFactory;
    
    
        /**
         * Create a new SpringConstraintValidatorFactory for the given BeanFactory.
         * @param beanFactory the target BeanFactory
         */
        public SpringConstraintValidatorFactory(AutowireCapableBeanFactory beanFactory) {
            Assert.notNull(beanFactory, "BeanFactory must not be null");
            this.beanFactory = beanFactory;
        }
    
    
        @Override
        public <T extends ConstraintValidator<?, ?>> T getInstance(Class<T> key) {
            return this.beanFactory.createBean(key);
        }
    
        // Bean Validation 1.1 releaseInstance method
        public void releaseInstance(ConstraintValidator<?, ?> instance) {
            this.beanFactory.destroyBean(instance);
        }
    
    }
    

    获得实例的方式是:
    return this.beanFactory.createBean(key);
    通过这个方式获得ProjectAuth的实例是可以注入bean的依赖的(SpringBeanFactory会帮我们完成依赖的注入,原因不在这里介绍了。)

    前面所说的,configure()的返回对象支持配置ValidatorFactory,接口是setConstraintValidatorFactory。所以解决这个问题的方式,就是把SpringConstraintValidatorFactory通过配置设置进去即可。
    至此,问题解决了,解决问题的原因也从源码上得到了解答。

    一些心得

    • 解决问题方式网上有很多教程,但是读到了源码才知道所以然,否则就需要一遍遍按照网上的教程试错,非常浪费时间,积累读源码的能力是技术的必修课。
    • 另外读这个源码中,ValidationProvider的提供由META-INF/services/javax.validation.spi.ValidationProvider这个配置文件来表示。这是Java内置的SPI机制,java的服务注入常见的两个方式一个是ClassLoader(java/lang这个包下,系统接口通用性强),一个是ServiceLoader,spi的实现(java/util中的类,使用友好,但是注入的server要符合一定规范),更多细节这里不展开了。
    • 领域建模的模型,应该包含数据的校验,领域建模的背景知识可以参考https://www.jianshu.com/p/712a49baf468

    相关文章

      网友评论

        本文标题:Springboot HibernateValidator 中自

        本文链接:https://www.haomeiwen.com/subject/fqvhektx.html