【问题标题】:javax.validation.ValidationException: HV000064: Unable to instantiate ConstraintValidatorjavax.validation.ValidationException: HV000064: 无法实例化 ConstraintValidator
【发布时间】:2021-10-31 01:26:00
【问题描述】:

我关注了this blog 的帖子并尝试实现自定义验证器来验证复合主键约束,但它失败了:

javax.validation.ValidationException: HV000064: Unable to instantiate ConstraintValidator: com.directory.domain.model.validators.StorePoolValidator.
    at com.directory.domain.repositories.StorePoolRepositoryTest.shouldReturnPoolByStore(StorePoolRepositoryTest.java:30)
Caused by: java.lang.NoSuchMethodException: com.directory.domain.model.validators.StorePoolValidator.<init>()
    at com.directory.domain.repositories.StorePoolRepositoryTest.shouldReturnPoolByStore(StorePoolRepositoryTest.java:30)

这里是验证器的注解接口代码:

import javax.validation.Constraint;
import javax.validation.Payload;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;

import static java.lang.annotation.ElementType.FIELD;
import static java.lang.annotation.RetentionPolicy.RUNTIME;

@Retention(RUNTIME)
@Target({FIELD})
@Constraint(validatedBy = StorePoolValidator.class)
public @interface UniqueStorePoolConstraint {
    String message() default "Store pool validation failed";

    Class<?>[] groups() default {};

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

这里是验证器类:

import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
import java.util.List;

public class StorePoolValidator implements ConstraintValidator<UniqueStorePoolConstraint, StorePoolId> {

    private StorePoolRepository storePoolRepository;

    public StorePoolValidator(StorePoolRepository storePoolRepository) {
        this.storePoolRepository = storePoolRepository;
    }

    @Override
    public void initialize(UniqueStorePoolConstraint constraintAnnotation) {

    }

    @Override
    public boolean isValid(StorePoolId id, ConstraintValidatorContext context) {
        final List<StorePool> storePools = storePoolRepository.findAllByStoreNumber(id.getThirdNumber());

        return storePools.isEmpty();
    }
}

这里是实体类:

@Entity
@Getter
@Setter
@Builder
@AllArgsConstructor
@Table(name = "STORE_POOLS")
public class StorePool implements Serializable {

    public StorePool() {
    }

    @EmbeddedId
    @UniqueStorePoolConstraint
    private StorePoolId id;
}

及其主键类:

import javax.persistence.Column;
import javax.persistence.Embeddable;
import javax.validation.constraints.NotNull;
import java.io.Serializable;

@Getter
@Setter
@Builder
@ToString
@Embeddable
@EqualsAndHashCode
@AllArgsConstructor
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class StorePoolId implements Serializable {

    @Column(name = "pool", nullable = false)
    @NotNull
    private Integer pool;

    @Column(name = "third_number", nullable = false)
    @NotNull
    private Integer thirdNumber;
}

我尝试运行以下测试:

@RunWith(SpringRunner.class)
@DataJpaTest
public class StorePoolRepositoryTest {

    @Autowired
    private StorePoolRepository storePoolRepository;

    @Autowired
    private TestEntityManager entityManager;

    @Test
    public void shouldReturnPoolByStore() {
        final StorePool storePool = StorePoolBuilder.buildStorePool(1, 1);
        entityManager.persist(storePool);
        entityManager.flush();

        final List<StorePool> storePools = storePoolRepository.findAllByStoreNumber(1);

        assertThat(storePools).containsExactly(storePool);
    }
}

我错过了什么?谢谢。

【问题讨论】:

  • 嗨,我也有同样的问题。你找到解决办法了吗?
  • @Shahin:抱歉,我无法再访问代码源了。您可以尝试实施乔纳森的最新建议

标签: spring-boot


【解决方案1】:

抱歉,讨论晚了。

首先创建一个ContextProvider,它是你需要的bean的提供者。

@Component
public class ContextProvider implements ApplicationContextAware {

    private static ApplicationContext applicationContext;

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        ContextProvider.applicationContext = applicationContext;
    }

    public static Object getBean(Class cls) {
        return ContextProvider.applicationContext.getBean(cls);
    }

}

现在你可以像这样拥有 bean:

ContextProvider.getBean(StorePoolRepository.class);

这解决了 NPE,您可以使用任何组件进行验证。

在您的情况下 StorePoolValidator

@Service
public class StorePoolValidator implements ConstraintValidator<UniqueStorePoolConstraint, StorePoolId> {

    private StorePoolRepository storePoolRepository;

    @Override
    public void initialize(UniqueStorePoolConstraint constraintAnnotation) {
         this.storePoolRepository = (StorePoolRepository) ContextProvider.getBean(StorePoolRepository.class) 
    }

    @Override
    public boolean isValid(StorePoolId id, ConstraintValidatorContext context) {
        final List<StorePool> storePools = storePoolRepository.findAllByStoreNumber(id.getThirdNumber());

        return storePools.isEmpty();
    }
}

就是这样。

【讨论】:

    【解决方案2】:

    需要添加一个默认为空的构造函数StorePoolValidator才能初始化验证器。

    public StorePoolValidator() {
    }
    

    更新

    为了使用存储库,您可以将验证器添加为服务。那么答案是这样的:

    @Service
    public class StorePoolValidator implements ConstraintValidator<UniqueStorePoolConstraint, StorePoolId> {
    
        @Autowired
        private StorePoolRepository storePoolRepository;
    
        @Override
        public void initialize(UniqueStorePoolConstraint constraintAnnotation) {
    
        }
    
        @Override
        public boolean isValid(StorePoolId id, ConstraintValidatorContext context) {
            final List<StorePool> storePools = storePoolRepository.findAllByStoreNumber(id.getThirdNumber());
    
            return storePools.isEmpty();
        }
    }
    

    【讨论】:

    • 之前的错误已经消失,但是我在isValid 方法中有一个NPE -> StorePoolValidator 类中的storePoolRepository 为空:(
    • @belgoros 是的,我回答了你的第一个问题,所以第二个问题我也更新了我的答案,所以请告诉我进展如何,亲切的问候。
    • @johntatn-jox 感谢您的回复。不幸的是,没有任何变化,仍然是相同的 NPE,无论我是否使用 @Service 注释 StorePoolValidator,自动连接 StorePoolRepository 或像以前一样使用空构造函数:Caused by: java.lang.NullPointerException... at com...directory.validators.StorePoolValidator.isValid
    • 测试中确定,添加@Autowired私有ApplicationContext上下文; ,所以请告诉我,如果没有,您能否分享一个包含此代码的基本仓库,以便我为您提供帮助?
    • 不,它没有解决问题,即使在测试中自动装配 ApplicationContext 看起来很奇怪。我将链接放到我关注的博客文章中以尝试使其正常工作。
    【解决方案3】:

    我遇到了基本相同的问题,但使用 Mock 测试时,我是这样解决的:

    1. 这是我的自定义约束验证的样子:

      界面:

      @Target(ElementType.FIELD)
      @Retention(RetentionPolicy.RUNTIME)
      @Constraint(validatedBy = UniqueUsernameValidator.class)
      public @interface UniqueUsername {
      
       String message() default "This username is in use";
      
       Class<?>[] groups() default {};
      
       Class<? extends Payload>[] payload() default {};
      
      }
      

      实施:

       @RequiredArgsConstructor
       public class UniqueUsernameValidator implements ConstraintValidator<UniqueUsername, String> {
      
           private final UserRepository repository;
      
           @Override
           public boolean isValid(String username, ConstraintValidatorContext constraintValidatorContext) {
               return !repository.existsById(username);
           }
      }
      
    2. @SpringBootTest注释你的测试类

    3. 通过Spring配置设置MockMvc:

      @ExtendWith(MockitoExtension.class)
      @SpringBootTest
      public class RegistrationTest {
      
          @Autowired
          private WebApplicationContext wac;
      
          private MockMvc mockMvc;
      
          @BeforeEach
          void setUp() {
              this.mockMvc = MockMvcBuilders
                      .webAppContextSetup(wac)
                      .alwaysDo(print())
                      .build();
          }
      
          // your tests
      
      }
      

    【讨论】:

    • 谢谢。有没有办法为 MockMvc.standalone() 做到这一点
    【解决方案4】:

    Spring 框架自动检测所有实现ConstraintValidator 接口的类。该框架将它们实例化并连接所有依赖项,就像该类是常规 Spring bean 一样。所以你不需要放@Service@Autowired 注释。 More details here.

    【讨论】:

    • 浏览这个 Doloszewski 的博客让我产生了同样的想法,但存储库为我打破了它。目前我遇到了同样的错误,当我摆脱回购时一切都很好。 @belgoros 似乎也有同样的问题
    猜你喜欢
    • 1970-01-01
    • 2022-01-03
    • 2022-07-07
    • 2016-06-09
    • 2020-10-26
    • 2016-11-22
    • 2016-02-29
    • 2017-01-29
    • 2020-08-30
    相关资源
    最近更新 更多