Autowired 在自定义约束验证器中给出 Null 值

Posted

技术标签:

【中文标题】Autowired 在自定义约束验证器中给出 Null 值【英文标题】:Autowired gives Null value in Custom Constraint validator 【发布时间】:2016-10-23 19:29:07 【问题描述】:

我对 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 Config 看起来像这样

@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 为空。我知道在 Spring 启动时不会实例化 ConstraintValidator 的任何实现,但我认为添加 ValidatorImpl() 实际上会解决这个问题。但它没有。

现在我遇到了一个非常老套的解决方法,我不想继续使用这样的代码。有人可以帮我解决我的情况。

附:这些是我在 Java Config 文件中的导入:

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; 

【问题讨论】:

你是否想要休眠验证器? ValidatorImpl 长什么样子? @brainstorm 是的,我想要 Hibernate Validator,即 ValidatorImpl——这就是 Hibernate 的 Validator 实现的名称。我不确定“ValidatorImpl 的外观如何”是什么意思。它是一个实现接口 Validator 并遵守其蓝图的具体类。 你能展示你的导入语句吗?是spring-boot应用吗? @brainstorm 我只是想获得 Hibernate Validator,而不是将其实际硬编码到我的 Java Config 中。所以基本上无论我在哪里使用以下代码:@Autowired Validator validator; 我希望实现是 Hibernate 的 Validator 接口的实现,而不是 Spring 的 @brainstorm 不,它不是 Spring-boot 应用程序。您希望我从哪个班级发布导入语句?我可以编辑问题。 【参考方案1】:

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

@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 的工作方式如下:

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

它是如何工作的: Hibernate 的 ConstraintValidatorFactory 不会初始化任何 ConstraintValidators 除非它们被调用,但 SpringConstraintValidatorFactory 通过将 AutowireCapableBeanFactory 赋予它来初始化。

编辑

正如@shabyasaschi 在其中一个 cmets 中提到的,要注入 autowireCapableBeanFactory,您可以将方法签名更改为:

Validator validator(final AutowireCapableBeanFactory autowireCapableBeanFactory) 

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

public AutowireCapableBeanFactory getAutowireCapableBeanFactory() 
        return autowireCapableBeanFactory;


public void setAutowireCapableBeanFactory(AutowireCapableBeanFactory autowireCapableBeanFactory) 
        this.autowireCapableBeanFactory = autowireCapableBeanFactory;

【讨论】:

谢谢。有用!但是,方法签名应该用于自动装配依赖项:public Validator validator(final AutowireCapableBeanFactory autowireCapableBeanFactory) @shabyasachi 我很高兴它帮助了你 :) 这就像一个脑筋急转弯!! @NickDiv 参数 autowireCapableBeanFactory 是什么?我应该从哪里拿那个?谢谢 它对我不起作用:/ ...你能发布你的完整代码吗? @Mouley 我没有那个项目了,但是如果你可以打开一个新问题来描述到底发生了什么或你看到的任何错误,我和其他 SO 成员可以看看【参考方案2】:

您可以通过两种方法解决此问题:

尝试使用 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 注入您需要的服务,但不是以通常的方式。 希望这会有所帮助。

【讨论】:

我很感激你的努力,我相信这肯定会奏效,但同样,这是一种可以以更清洁的方式完成的事情的hacky方式。我已经看到@Autowired 为人们工作(在论坛上)所以我知道肯定有一些方法可以做到这一点。只需要弄清楚。再次感谢您的尝试。 嗯,在验证器中获取 Spring bean 有点笨拙,但也不错。请,如果您找到解决方案,您介意更新这个问题吗?正如我所说,我无法仅使用@autowired 来使其工作,谢谢。 在将 JpaRepository 自动连接到 ContraintValidator 时会导致 *** 异常【参考方案3】:

在你的 bean 定义中

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

方法定义中Validator的类型是什么?您应该确保它从 Spring 返回 javax.validation.Validator不是 Validator

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

【讨论】:

我的 Validator 配置文件中只有一个导入,我还在返回类型中显式尝试了 javax.validation.Validator。 你试过调试吗?在LocalValidatorFactoryBean 中,您应该逐步执行将 Spring 的约束验证器工厂传递给引导验证器的代码。 但它一开始并没有使用 Hibernate 的验证器。当我从 Java 配置中显式设置 Hibernate 的验证器时,它采用了 Hibernate 的 ConstraintValidatorFactoryImpl (这很明显),我已经调试并尝试了比我在这里发布的更多的选项。我确实找到了一个解决方案,但我已将其发布为答案。我非常感谢您尝试帮助其他程序员。谢谢。 嗯,好的;还是很奇怪,LocalValidatorFactoryBean 默认应该使用SpringConstraintValidatorFactory 我认为这里有一些错误的沟通。 LocalValidatorFactoryBean 确实使用 SpringConstraintValidatorFactory。问题是我无法让 LocalValidatorFactryBean 向我返回 HIbernate 的实现,所以我从 Config 中手动将其插入,但这样做给了我 ConstraintValidatorFactoryImpl 作为我的 ConstraintValidator 提供程序,这再次给我带来了问题。所以我决定也覆盖它。所以这就是我正在做的事情,我要求默认使用 Spring 的 Constraintfactory 的 Hibernate 验证器。【参考方案4】:

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

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

【讨论】:

【参考方案5】:

考虑一下。在约束验证器类中使用 @Autowired 应该没有问题。这意味着有问题。

这个问题已经在各种平台上报告过,我还没有看到好的解决方案。不过,我已经看到了一些解决方法。

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

您可以针对这种情况执行以下操作

你可以使用dto来携带验证注解,将dto类的属性复制到你的实体类中,然后再保存到数据库中。您的情况可能不同,但解决方法应该相同。

以下是有效的代码说明

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());

【讨论】:

【参考方案6】:
private XXXService xxxService = SpringContextHolder.getBean(XXXService.class);

【讨论】:

恭喜您的第一篇文章!虽然这段代码 sn-p 可以解决问题,但including an explanation 确实有助于提高帖子的质量。请记住,您也是在为将来的读者回答问题,而这些人可能不知道您的代码建议的原因。【参考方案7】:

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

public class EmployeeValidator implements ConstraintValidator<EmployeeValidation , Object> 


    private EmployeeService employeeService;

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

    ...

【讨论】:

不知道这对你有什么用,但在这里它给了我“原因:java.lang.NoSuchMethodException:training.edit.jpa.validator.CompanyValidator.()”必须成为它出现的默认构造函数。

以上是关于Autowired 在自定义约束验证器中给出 Null 值的主要内容,如果未能解决你的问题,请参考以下文章

为啥我不能在自定义字段验证器中返回全局消息键?

在自定义 UIView 类 Iphone 上应用自动布局约束。

在自定义tableviewcells中添加约束的最佳位置在哪里?

我在自定义 UITableViewCell 中以编程方式设置布局约束时遇到问题

在自定义表格视图单元格问题中水平/垂直约束

在自定义验证器中使用标准验证器进行 Grails 验证