自动装配在自定义约束验证器中给出 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 按以下方式工作:
- 您的对象将由您选择的库验证
- 您的自定义验证器将能够使用 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());
}
我是 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 按以下方式工作:
- 您的对象将由您选择的库验证
- 您的自定义验证器将能够使用 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());
}