自动装配在自定义约束验证器中给出 Null 值

Autowired gives Null value in Custom Constraint validator

我是 Spring 的新手,我已经在 SO 上查看了一些关于所问问题的答案。以下是链接:

Spring 3.1 Autowiring does not work inside custom constraint validator

Autowiring a service into a validator

Autowired Repository is Null in Custom Constraint Validator

我有一个 Spring 项目,我想在其中使用 Hibernate Validator 进行对象验证。根据我在网上阅读的内容和一些论坛,我尝试按如下方式注入验证器:

@Bean
  public Validator validator() {
    return new LocalValidatorFactoryBean().getValidator();
  }

但无论我在哪里使用

@Autowired
Validator validator;

它采用 Spring 的验证器实现而不是 Hibernate 的验证器。我无法弄清楚如何准确地注入 Hibernate 验证器并简单地将它自动连接到其他 类 所以我使用了一个便宜的技巧,现在我的 Java 配置看起来像这样

@Bean
      public Validator validator() {
        // ValidatorImpl is Hibernate's implementation of the Validator
        return new ValidatorImpl();
      }

(如果有人能给我指明正确的方向,避免以这种 Hacky 方式获取 Hibernate Validator,我将不胜感激)

但让我们来谈谈这里的主要问题:

这是自定义验证定义

@Target( { METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER } )
@Retention(RUNTIME)
@Constraint(validatedBy = EmployeeValidator.class)
@Documented
public @interface EmployeeValidation {
String message() default "{constraints.employeeConstraints}";
public abstract Class<?>[] groups() default {};
public abstract Class<? extends Payload>[] payload() default {};
}

我的自定义验证器

public class EmployeeValidator implements ConstraintValidator<EmployeeValidation , Object> {

@Autowired
private EmployeeService employeeService;

@Override
public void initialize(EmployeeValidation constraintAnnotation) {
//do Something
 }

@Override
public boolean isValid(String type, ConstraintValidatorContext context) {
    return false;
}
}

在上面的自定义约束验证器中,我得到 employeeService null。我知道 ConstraintValidator 的任何实现都不会在 Spring 启动时实例化,但我认为添加 ValidatorImpl() 实际上会解决这个问题。但它没有。

现在我被一个非常棘手的解决方法困住了,我不想继续使用这样的代码。有人可以帮我解决我的情况吗?

P.S。这些是我在 Java 配置文件中的导入:

import org.hibernate.validator.HibernateValidator; 
import org.springframework.beans.factory.annotation.Autowired; 
import org.springframework.context.MessageSource; 
import org.springframework.context.annotation.Bean; 
import org.springframework.context.annotation.ComponentScan; 
import org.springframework.context.annotation.Configuration; 
import org.springframework.context.annotation.PropertySource; 
import org.springframework.context.support.ReloadableResourceBundleMessageSource; 
import org.springframework.core.env.Environment; 
import org.springframework.validation.Validator; 
import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean; 
import org.springframework.web.servlet.LocaleResolver; 
import org.springframework.web.servlet.ViewResolver; 
import org.springframework.web.servlet.config.annotation.EnableWebMvc; 
import org.springframework.web.servlet.config.annotation.InterceptorRegistry; 
import org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport; 
import org.springframework.web.servlet.i18n.CookieLocaleResolver; 
import org.springframework.web.servlet.i18n.LocaleChangeInterceptor; 
import org.springframework.web.servlet.view.InternalResourceViewResolver; 

在你的 bean 定义中

@Bean
public Validator validator() {
    return new LocalValidatorFactoryBean().getValidator();
}

方法定义中Validator的类型是什么?你应该确保它 returns javax.validation.Validator, 而不是 Validator 来自 Spring.

让 Spring bootstrap 验证器也会导致将 SpringConstraintValidatorFactory 传递给 Hibernate 验证器,这将在约束验证器中启用依赖注入。

您可以使用两种方法解决此问题:

  • 尝试使用 Spring 在您的验证器上注入服务。

  • 手动初始化它覆盖验证器的初始化方法。

前段时间我遇到了同样的问题,最后我决定使用第二个选项来避免大量问题。

如您所指,您必须在验证器上定义一个初始化方法,然后您可以使用 ServiceUtils 获取所需的服务 bean:

@Autowired
private EmployeeService employeeService;

@Override
public void initialize(EmployeeValidation constraintAnnotation) {
  //Use an utility service to get Spring beans
  employeeService = ServiceUtils.getEmployeeService();
}

而 ServiceUtils 是一个普通的 Spring bean,它在静态方法中使用了对自身的静态引用。

@Component
public class ServiceUtils {
  private static ServiceUtils instance;

  @Autowired
  private EmployeeService employeeService;

  /* Post constructor */

  @PostConstruct
  public void fillInstance() {
    instance = this;
  }

  /*static methods */

  public static EmployeeService getEmployeeService) {
    return instance.employeeService;
  }
}

所以您正在使用 Spring 来注入您需要的服务,但不是以通常的方式。 希望这有帮助。

我希望解决方案对某人有所帮助:

@Bean
public Validator validator () {

    ValidatorFactory validatorFactory = Validation.byProvider( HibernateValidator.class )
        .configure().constraintValidatorFactory(new SpringConstraintValidatorFactory(autowireCapableBeanFactory))
        .buildValidatorFactory();
    Validator validator = validatorFactory.getValidator();

    return validator;
}

使用 SpringConstraintValidatorFactory 初始化验证器,以便注入工作并提供验证器实现 Hibernate.class 按以下方式工作:

  1. 您的对象将由您选择的库验证
  2. 您的自定义验证器将能够使用 Spring 的功能,同时让 Hibernate 执行验证。

工作原理: Hibernate 的 ConstraintValidatorFactory 不会初始化任何 ConstraintValidators 除非它们被调用但是 SpringConstraintValidatorFactory 通过给它 AutowireCapableBeanFactory 来初始化。

编辑

如@shabyasaschi 的评论之一所述,要注入 autowireCapableBeanFactory,您可以将方法签名更改为:

Validator validator(final AutowireCapableBeanFactory autowireCapableBeanFactory) {

或者在配置文件中为其添加getter和setter如下:

public AutowireCapableBeanFactory getAutowireCapableBeanFactory() {
        return autowireCapableBeanFactory;
}

public void setAutowireCapableBeanFactory(AutowireCapableBeanFactory autowireCapableBeanFactory) {
        this.autowireCapableBeanFactory = autowireCapableBeanFactory;
}
private XXXService xxxService = SpringContextHolder.getBean(XXXService.class);

这对我有用。对于现在搜索的人

public class EmployeeValidator implements ConstraintValidator<EmployeeValidation , Object> {


    private EmployeeService employeeService;

    public EmployeeValidator(EmployeeService employeeService){
        this.employeeService = employeeService;
    }

    ...
}

您的代码没有任何问题,这取决于您如何创建 ValidatorFactory。 创建一个 bean 并让 Spring 处理它。

@Bean
public ValidatorFactory validatorFactory(){
    return Validation.buildDefaultValidatorFactory();
}

想一想。在约束验证器 class 中使用 @Autowired 应该没有问题。这意味着出了点问题。

这个问题在各个平台上都有反映,目前还没有很好的解决办法。不过,我已经看到了一些解决方法。

这是我找到的。 您可能会注意到验证发生了两次。第一次它应该工作,但第二次你得到一个空相关的错误消息。问题应该是您正在验证的实体或 class 在您的控制器中被使用了两次。例如,您可能想要验证实体 class 并尝试在控制器方法中的同一方法中同时保存它。当您尝试保存实体时,它会尝试再次验证对象,这次 @Autowired 对象将为空。

以下是您可以针对此场景执行的操作

你可以使用dto携带验证注解,将dtoclass的属性复制到你的实体class中,然后再存入数据库。你的场景可能不同,但解决方法应该是一样的。

下面是有效代码的说明

public ResponseEntity<InstitutionModel> create(@Valid @RequestBody InstitutionDto institutiondto) {
    Institution institution = new Institution();
    BeanUtils.copyProperties(institutiondto, institution);
    return Optional.of(this.institutionService.save(institution)).map(institutionModelAssembler::toModel)
            .map(ResponseEntity::ok).orElse(ResponseEntity.notFound().build());
}