【问题标题】:ConstraintValidator dependency injection leads to ValidationException when being validated at class levelConstraintValidator 依赖注入在类级别验证时会导致 ValidationException
【发布时间】:2019-06-27 13:05:26
【问题描述】:

ConstraintValidator 中使用依赖注入时遇到了意外行为,该ConstraintValidator 正在类级别进行评估。

实体类:

@Entity
@ValidDemoEntity
public class DemoEntity {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

}

验证注解:

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = {DemoEntityValidator.class})
public @interface ValidDemoEntity {

    String message() default "{some.demo.validator.message}";
    Class<?>[] groups() default {};
    Class<? extends Payload>[] payload() default {};

}

验证器:

public class DemoEntityValidator implements ConstraintValidator<ValidDemoEntity, DemoEntity> {

    private DemoEntityRepository demoEntityRepository;

    public DemoEntityValidator(DemoEntityRepository demoEntityRepository) {
        this.demoEntityRepository = demoEntityRepository;
    }

    @Override
    public void initialize(ValidDemoEntity constraintAnnotation) {

    }

    @Override
    public boolean isValid(DemoEntity demoEntity, ConstraintValidatorContext constraintValidatorContext) {
        return true;
    }
}

测试类:

@SpringBootTest
public class ValidatorInstantiationTest {

    private Validator validator;

    @Before
    public void setUp() throws Exception {
        ValidatorFactory validatorFactory = Validation.buildDefaultValidatorFactory();
        validator = validatorFactory.getValidator();
    }

    @Test
    public void shouldInitiateAndCallDemoEntityValidator() {
        DemoEntity demoEntity = new DemoEntity();
        validator.validate(demoEntity);
    }

}

验证实体会导致:

javax.validation.ValidationException: HV000064: Unable to instantiate ConstraintValidator: com.example.demo.DemoEntityValidator.

并进一步向下堆栈跟踪:

Caused by: java.lang.NoSuchMethodException: com.example.demo.DemoEntityValidator.<init>()

这表明Hibernate 试图启动该类,而不是让 Spring 来处理它。

奇怪的是,依赖注入对于应用于字段级别的验证工作正常。

代码位于GitHub

【问题讨论】:

    标签: spring hibernate bean-validation hibernate-validator


    【解决方案1】:

    异常表示没有默认构造函数,因为 Hibernate Validator 尝试实例化您的验证器。

    你必须使用 Spring。

    1 使您的验证器成为 Spring Bean:

    @Component
    public class DemoEntityValidator implements ConstraintValidator<ValidDemoEntity, DemoEntity> {
    

    2 注入 Spring 提供的验证器并使用 SpringRunner 执行您的测试:

    @SpringBootTest
    @RunWith(SpringRunner.class)
    public class ValidatorInstantiationTest {
    
        @Autowired
        private Validator validator;
    
        @Test
        public void shouldInitiateAndCallDemoEntityValidator() {
            DemoEntity demoEntity = new DemoEntity();
            validator.validate(demoEntity);
        }
    
    }
    

    【讨论】:

      【解决方案2】:

      1 使您的验证器成为 Spring Bean

      site 声明:

      Spring 框架会自动检测所有实现 ConstraintValidator 接口的类。框架实例化它们并连接所有依赖项,就像该类是一个常规 Spring bean。

      这显然适用于在字段级别应用的验证。

      不过我已经更新了代码。

      DemoEntityValidator 现在是 Spring 组件:

      @Component
      public class DemoEntityValidator implements ConstraintValidator<ValidDemoEntity, DemoEntity>
      

      我已将测试更改为:

      @SpringBootTest
      @RunWith(SpringRunner.class)
      public class ValidatorInstantiationTest {
      
          @Autowired
          private DemoEntityRepository demoEntityRepository;
      
          @Test
          public void shouldInitiateAndCallDemoEntityValidator() {
              DemoEntity demoEntity = new DemoEntity();
              demoEntityRepository.save(demoEntity);
          }
      
      }
      

      为了使用例更清晰,但测试仍然导致相同的异常。

      【讨论】:

        【解决方案3】:

        向类DemoEntityValidator添加一个空的构造函数会禁用错误。

        【讨论】:

        • 确实如此,但没有注入 DemoEntityRepository。
        【解决方案4】:

        我想你的答案就在这里:

        https://docs.spring.io/spring/docs/current/spring-framework-reference/core.html#validation-beanvalidation-spring

        你需要在你的配置类中声明一个LocalValidatorFactoryBean,它才会起作用。

        来自文档:

        默认情况下,LocalValidatorFactoryBean 配置一个 使用 Spring 创建的 SpringConstraintValidatorFactory ConstraintValidator 实例。这让您的自定义 ConstraintValidators 像其他任何依赖注入一样受益于依赖注入 春豆。

        还有一个来自同一个地方的例子:

        import javax.validation.ConstraintValidator;
        
        public class MyConstraintValidator implements ConstraintValidator {
        
            @Autowired;
            private Foo aDependency;
        
            ...
        }
        

        这就是我在 @Configuration 带注释的类中声明该 bean 的方式:

          /**
           * Provides auto-wiring capabilities for validators Checkout: https://docs.spring.io/spring/docs/current/spring-framework-reference/core.html#validation-beanvalidation-spring
           */
          @Bean
          public LocalValidatorFactoryBean validatorFactoryBean() {
            LocalValidatorFactoryBean bean = new LocalValidatorFactoryBean();
            bean.setValidationMessageSource(validationMessageSource());
            return bean;
          }
        

        【讨论】:

        • 我似乎遇到了同样的问题,添加 LocalValidatorFactoryBean 似乎并不能解决问题。就像 Hibernate 在单元测试中运行时拒绝使用 Spring Validator Factory。
        【解决方案5】:

        您的验证器类没有问题。我通过对测试配置进行两项更改来完成这项工作:

        1.使用 Spring 运行测试

        为了让 Spring 管理您的 bean,您需要使用设置 Spring 的测试运行器来运行您的测试。您可以使用 junit 的@RunWith-annotation 指定测试运行器类:

        @RunWith(SpringRunner.class)
        @SpringBootTest
        public class ValidatorInstantiationTest { ... }
        

        2。注入 Spring 托管验证器 bean

        由于您使用的是 Spring Boot,因此您可以注入一个 Spring 托管验证器——它已经配置好了。这样,Spring 将处理您的 DemoEntityValidator 的启动。

        @RunWith(SpringRunner.class)
        @SpringBootTest
        public class ValidatorInstantiationTest {
        
            @Autowired
            private Validator validator;
        
            ...
        }
        

        这就是我们所需要的。您应该使用 @Component 或类似名称来注释您的 DemoEntityValidator。


        请注意,您需要为 Spring 提供数据源,因为 SpringRunner 将根据您的 Spring Boot 设置设置上下文(我猜它包括 spring-boot-starter-data-jpa 在您的情况下)。最简单的方法就是在类路径上放置一个内存数据库,例如 h2

        【讨论】:

          猜你喜欢
          • 2013-06-09
          • 1970-01-01
          • 1970-01-01
          • 2022-07-07
          • 2017-10-01
          • 1970-01-01
          • 2017-02-10
          • 2015-01-01
          • 1970-01-01
          相关资源
          最近更新 更多