Spring 中的 Hibernate Validator 使用不同的 ConstraintValidatorManager 启动

Hibernate Validator in Spring Boot using different ConstraintValidatorManager

我遇到了之前在 Spring Boot vs. Hibernate Validation 中提到的问题,其中自定义约束验证器中的依赖项自动装配不起作用。从我自己的调试中,我注意到当实体级验证发生时,与 Hibernate 为表单提交执行 bean 验证时相比,Hibernate 加载不同的 ConstraintValidatorManager。后者工作正常,前者导致自定义约束验证器的依赖项为空。似乎 Hibernate 从根上下文加载一个管理器,从 servlet 上下文加载一个管理器。这可以解释 Hibernate 不知道自定义约束验证器中自动装配的依赖项的存在。但是,如果这是真的,我不明白发生了什么,或者如何让 Hibernate/JPA 知道 Spring 上下文和它的 beans。

我希望有人能指出我正确的方向?我已经尝试了以下所有答案,还有更多(例如不同的库版本、配置方法、通过 utils class 加载静态 bean 等):

Inject Repository inside ConstraintValidator with Spring 4 and message interpolation configuration

此外,我已经多次阅读 Spring 引导的参考指南,但运气不佳。有几个案例提到他们的 Hibernate 验证工作正常,既适用于常规 bean 提交,也适用于实体持久化期间。不幸的是,我似乎无法检索到他们使用的确切 (Java) 配置,但他们似乎使用的是默认配置。我开始怀疑这是否是一个特定的 Spring 引导问题(尽管它被声明为 Spring 验证和 Hibernate 验证的组合应该开箱即用)。

添加像下面这样的 bean 并不能解决问题(默认工厂是 SpringConstraintValidatorFactory ofcourse):

@Bean
public LocalValidatorFactoryBean validator()
{
    LocalValidatorFactoryBean bean = new LocalValidatorFactoryBean();
    bean.setValidationMessageSource(messageSource());
    return bean;
}

也不包含 Hibernate 验证器的 bean 定义:

加载和注入所需的 bean 有许多不同的方法,但是如果 Hibernate 根本不知道上下文中加载的 bean(因为它使用的是不同的上下文?),如何进行?

提前致谢。

更新:Gradle 文件

buildscript {
    ext {
        springBootVersion = '2.1.5.RELEASE'
    }
    repositories {
        mavenCentral()
    }
    dependencies {
        classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}")
    }
}

apply plugin: 'java'
apply plugin: 'eclipse'
apply plugin: 'org.springframework.boot'
apply plugin: 'io.spring.dependency-management'

group = '<hidden>'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = 1.8

repositories {
    mavenCentral()
    jcenter()
}

dependencies {
    implementation('org.springframework.boot:spring-boot-starter-web')
    implementation('org.springframework.boot:spring-boot-starter-tomcat:2.1.5.RELEASE')

    implementation('org.springframework.boot:spring-boot-starter-thymeleaf')
    implementation('org.springframework.boot:spring-boot-starter-security')
    implementation('org.springframework.boot:spring-boot-starter-data-jpa')
    implementation('org.springframework.boot:spring-boot-starter-mail')
    implementation('org.springframework.session:spring-session-core')

    annotationProcessor('org.springframework.boot:spring-boot-configuration-processor')

    implementation('org.postgresql:postgresql')

    // https://mvnrepository.com/artifact/org.jboss.aerogear/aerogear-otp-java
    implementation('org.jboss.aerogear:aerogear-otp-java:1.0.0')

    implementation('com.github.mkopylec:recaptcha-spring-boot-starter:2.2.0')
    implementation('nz.net.ultraq.thymeleaf:thymeleaf-layout-dialect:2.0.5')
    implementation('org.thymeleaf.extras:thymeleaf-extras-springsecurity3:3.0.4.RELEASE')

    implementation('javax.enterprise:cdi-api:2.0')

    runtimeOnly('org.springframework.boot:spring-boot-devtools')

    testImplementation('org.springframework.boot:spring-boot-starter-test')
    testImplementation('org.springframework.security:spring-security-test')
    testImplementation 'org.mockito:mockito-core:2.27.0'
}

有一种方法可以通过设置 javax.persistence.validation.factory

来告诉 Hibernate 使用相同的验证器
@Configuration
@Lazy
class SpringValidatorConfiguration {

    @Bean
    @Lazy
    public HibernatePropertiesCustomizer hibernatePropertiesCustomizer(final Validator validator) {
        return new HibernatePropertiesCustomizer() {

            @Override
            public void customize(Map<String, Object> hibernateProperties) {
                hibernateProperties.put("javax.persistence.validation.factory", validator);
            }
        };
    }
}

这样就一切正常了。

关于修复,只是为 are/were 处理此类问题的其他人总结更多 extensive/integrated 答案,我的配置现在包含所有这些 bean:

@Bean
public LocalValidatorFactoryBean validator()
{
    LocalValidatorFactoryBean validatorFactory = new LocalValidatorFactoryBean();
    validatorFactory.setValidationMessageSource(messageSource());
    return validatorFactory;
}

@Bean
public MessageSource messageSource()
{
    ReloadableResourceBundleMessageSource messageSource = new ReloadableResourceBundleMessageSource();
    messageSource.setBasename("classpath:messages");
    messageSource.setDefaultEncoding("UTF-8");
    return messageSource;
}

@Bean
public MethodValidationPostProcessor methodValidationPostProcessor() 
{
    MethodValidationPostProcessor methodValidationPostProcessor = new MethodValidationPostProcessor();
    methodValidationPostProcessor.setValidator(validator());
    return methodValidationPostProcessor;
}

@Bean
public HibernatePropertiesCustomizer hibernatePropertiesCustomizer()
{
    return properties ->
    {
        properties.put("javax.persistence.validation.factory", validator());
        // Add more properties here such as validation groups (see comment for SO example)
    };
}

有关添加休眠验证组以梳理不同 life-cycle 事件(例如 bean 与实体)的验证的示例,请参阅