使用 Spring 的 JSR-303 约束验证器中的依赖注入失败

Posted

技术标签:

【中文标题】使用 Spring 的 JSR-303 约束验证器中的依赖注入失败【英文标题】:Dependency Injection in JSR-303 Constraint Validator with Spring fails 【发布时间】:2015-02-16 01:29:30 【问题描述】:

我遇到了与here 和here 相同的问题,但还没有找到解决方案。

所以我的示例测试项目将显示整个相关配置和代码:

约束注解:

@Target( ElementType.METHOD, ElementType.FIELD )
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = FooValidator.class)
public @interface FooValid 

    String message();

    Class<?>[] groups() default ;

    Class<? extends Payload>[] payload() default ;

带注释的 PoJo:

public class Foo 

    @FooValid(message = "Test failed")
    private Integer test;
    [...]

带有@Validated 的注解服务:

@Service
@Validated
public class FooService 

    private final Test test;

    @Autowired
    public FooService(final Test test) 
        this.test = test;
    

    public void foo(@Valid final Foo foo) 
        this.test.test(foo);
    

JSR-303 约束验证器:

public class FooValidator implements ConstraintValidator<FooValid, Integer> 

    @Autowired
    private ValidationService validationService;

    @Override
    public void initialize(final FooValid constraintAnnotation) 
        // TODO Auto-generated method stub

    

    @Override
    public boolean isValid(final Integer value, final ConstraintValidatorContext context) 
        // this.validationService is always NULL!
        Assert.notNull(this.validationService, "the validationService must not be null");
        return false;
    


注入的验证服务:

@Service
public class ValidationService 

    public void test(final Foo foo) 
        System.out.println(foo);
    

Spring boot 应用及配置:

@Configuration
@ComponentScan
@EnableAutoConfiguration
public class Application 

    public static void main(final String[] args) 
        final ConfigurableApplicationContext context = SpringApplication.run(Application.class, args);
        final FooService service = context.getBean(FooService.class);
        service.foo(new Foo());
    

    @Bean
    public static LocalValidatorFactoryBean validatorFactory() 
        return new LocalValidatorFactoryBean();
    

    @Bean
    public static MethodValidationPostProcessor validationPostProcessor() 
        return new MethodValidationPostProcessor();
    


相关的maven pom:

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>1.1.9.RELEASE</version>
    <relativePath/>
</parent>

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>
    <dependency>
        <groupId>org.hibernate</groupId>
        <artifactId>hibernate-validator</artifactId>
    </dependency>
</dependencies>

<properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <start-class>demo.Application</start-class>
    <java.version>1.7</java.version>
</properties>

<build>
    <plugins>
        <plugin>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId>
        </plugin>
    </plugins>
</build>

我正在使用 LocalValidatorFactoryBean 和默认的 SpringConstraintValidatorFactory。 但是为什么依赖注入在 ConstraintValidator 中不起作用并且 ValidationService 无法自动装配?

顺便说一句,如果我不在服务中使用 @Validated,请在 spring 或 javax Validator 接口对面注入并手动调用“validator.validate”依赖注入将起作用。但我不想在每个服务中手动调用 validate 方法。

非常感谢您的帮助:)

【问题讨论】:

【参考方案1】:

我在 Spring Boot 环境中遇到了同样的问题,我发现 Hibernate 内部实现代替了配置的 Spring 实现。当应用程序启动时,调试器发现了 Spring 的工厂,但后来在运行时出现了 Hibernate 的工厂。经过一番调试,我得出的结论是 MethodValidationPostProcessor 得到了内部的。因此我配置如下:

@Bean
public Validator validator() 
    return new LocalValidatorFactoryBean();


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

注意验证器的设置器 - 它完成了工作。

【讨论】:

我在一分钟前刚刚测试过它,它只适用于return new MethodValidationPostProcessor () 当然,为什么我一开始没有想到这一点。 当使用 MethodValidationPostProcessor 时,这确实可以解决问题。不幸的是,文档中没有提到这一点,但是如果您考虑一下,它就很有意义。但是,我会将来自 spring 上下文的 Validator 注入到 methodValidationPostProcessor 中,以便拥有相同的验证器实例,而不是调用 validator() 方法。我会编辑你的答案。 是否可以只为某些方法/类替换默认实现(Hibernate vs Spring)?【参考方案2】:

这对我有用。我不得不使用@Inject 标签。

    public class FooValidator implements ConstraintValidator<FooValid, Integer> 
        private ValidationService validationService;
        @Inject
        public FooValidator(ValidationService validationService)
            this.validationService = validationService;
        
        @Override
        public void initialize(final FooValid constraintAnnotation) 
            // TODO Auto-generated method stub
        
        @Override
        public boolean isValid(final Integer value, final ConstraintValidatorContext context) 
            // this.validationService is always NULL!
            Assert.notNull(this.validationService, "the validationService must not be null");
            return false;
        

【讨论】:

【参考方案3】:

我遇到了同样的问题。出现问题是因为 Hibernate 正在查找和应用验证器而不是 Spring。所以在配置Hibernate的时候需要设置验证模式为NONE

@Bean(name="entityManagerFactory")
public LocalContainerEntityManagerFactoryBean
    localContainerEntityManagerFactoryBean(DataSource dataSource) 
    LocalContainerEntityManagerFactoryBean lcemfb =
        new LocalContainerEntityManagerFactoryBean();
    lcemfb.setDataSource(dataSource);
    lcemfb.setJpaVendorAdapter(new HibernateJpaVendorAdapter());
    lcemfb.setValidationMode(ValidationMode.NONE);
    // Continue configuration...

如果您已经配置了 LocalValidationFactoryBean,Spring 将选择任何带有 @Component 注释的验证器并自动装配它们。

【讨论】:

感谢您的回复!不,这行不通。在我上面的示例项目中,没有休眠 LocalContainerEntityManagerFactoryBean。同样用 @Component 注释验证器也无济于事:(你还有其他想法吗?这就是上面的全部代码...... 您可以像我一样使用自定义 EntityManagerFactory 在代码中禁用 Hibernate 验证(请参阅 Spring Boot here 的说明)。或者您可以通过在application.properties file 中设置spring.jpa.properties.javax.persistence.validation.mode=none 来禁用休眠验证(参见说明here)。

以上是关于使用 Spring 的 JSR-303 约束验证器中的依赖注入失败的主要内容,如果未能解决你的问题,请参考以下文章

Java Hibernate Validator JSR-303验证

使用 JSR 303 在 JSF 中使用内联消息进行跨字段验证

使用JSR303框架对数据进行验证-spring框架

如果违反 JSR 303 验证约束,如何强制通过 p:ajax 设置 p:inputText 值?

06数据验证

Spring/Spring boot JSR-303验证框架 之 hibernate-validator