程序员

Springboot HibernateValidator 中自

2020-09-07  本文已影响0人  Wayne维基

问题背景

服务原来自定义了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通过配置设置进去即可。
至此,问题解决了,解决问题的原因也从源码上得到了解答。

一些心得

上一篇下一篇

猜你喜欢

热点阅读