Nick Div
Nick Div

Reputation: 5628

Autowired gives Null value in Custom Constraint validator

I am totally new to Spring and I have looked in to a few answers on SO for the asked problem. Here are the links:

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

I have a Spring project in which I want to use Hibernate Validator for an object validation. Based on what I read online and a few forums I tried to inject validator as follows:

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

But wherever I was using

@Autowired
Validator validator;

It was taking Spring's validator implementation instead of the Hibernate's validator. I couldn't figure out how to exactly inject Hibernate validator and simply Autowire it across other classes so I used a cheap trick, now my Java Config looks like this

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

(I would really appreciate if someone can actually point me into the right direction on how to avoid getting Hibernate Validator in this Hacky way)

But lets come to the main issue here:

Here is custom validation definition

@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 {};
}

My Custom Validator

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;
}
}

In the above Custom Constraint Validator I get the employeeService null. I know that any implementations of ConstraintValidator are not instantiated when Spring is starting up but I thought adding the ValidatorImpl() will actually fix that. But it didn't.

Now I am stuck with a really hacky workaround and I do not want to continue with a code like that. Can someone please help me with my situation.

P.S. These are my imports in the Java Config file:

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; 

Upvotes: 20

Views: 24859

Answers (8)

viespring
viespring

Reputation: 41

You have to manually do it without any deps or Bean

class Util{
  public static Validator validator(){
    return Validation.buildDefaultValidatorFactory().getValidator();
  }
}

Upvotes: 0

glouis
glouis

Reputation: 1

Think about it. There should have been no issue with using the @Autowired inside a Constraint validator class. This means that something is wrong.

This issue has been reported on various platform and I have not seen a good solution. I have seen some workaround though.

Here is what I found. You may notice that the validation is happening twice. the first time it should work but the second time you got a null related error message. The problem should be that the entity or the class that you is being validated is being used twice in your controller. For example, you may want validate the entity class and try to save it at the same time in the same method in the controller method. when you try to save the entity, it will try to validate the object again and this time the @Autowired object will be null.

Here is what you can do for this scenario

You can use dto to carry the validation annotation and copy the property of the dto class to your entity class before you save it into the database. your scenario may be different but the solution approach should be the same.

Below is an illustration of code that works

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

Upvotes: 0

Kumar Shanoo
Kumar Shanoo

Reputation: 135

There is nothing wrong with your code It depends how are you creating your ValidatorFactory. Create a bean and let Spring handle it.

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

Upvotes: 1

Nick Div
Nick Div

Reputation: 5628

I hope the solution will help someone:

@Bean
public Validator validator () {

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

    return validator;
}

Initializing the validator with SpringConstraintValidatorFactory so that injection works and providing the validator implementation to be Hibernate.class works in the following manner:

  1. Your objects will be validated by the library of your choice
  2. Your custom validators will be able to use Spring's functionality while having validation to be executed by Hibernate.

How it works: Hibernate's ConstraintValidatorFactory does not initialize any ConstraintValidators unless they are called but SpringConstraintValidatorFactory does by giving AutowireCapableBeanFactory to it.

EDIT

As mentioned in one of the comments by @shabyasaschi To inject autowireCapableBeanFactory you can change the method signature as:

Validator validator(final AutowireCapableBeanFactory autowireCapableBeanFactory) {

or add getter and setter for it in the config file as follows:

public AutowireCapableBeanFactory getAutowireCapableBeanFactory() {
        return autowireCapableBeanFactory;
}

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

Upvotes: 28

ykembayev
ykembayev

Reputation: 106

Thats worked for me. For guys who search at now

public class EmployeeValidator implements ConstraintValidator<EmployeeValidation , Object> {


    private EmployeeService employeeService;

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

    ...
}

Upvotes: -4

user4441649
user4441649

Reputation: 19

private XXXService xxxService = SpringContextHolder.getBean(XXXService.class);

Upvotes: -1

Ricardo Vila
Ricardo Vila

Reputation: 1632

You can fix this with two aproaches:

  • Try to inject Services on your validator using Spring.

  • Initialize it manually overriding Validator's initialize method.

I had the same problem time ago and finally i decided to use second option avoiding tons of problems.

As you point you must define one initialize method on your validator and there you can use a ServiceUtils to get the service bean you need:

@Autowired
private EmployeeService employeeService;

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

And ServiceUtils is a normal Spring bean with a static reference to itself used in the static methods.

@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;
  }
}

So you are using Spring to inject the services you need but not in the usual way. Hope this helps.

Upvotes: 4

Gunnar
Gunnar

Reputation: 18970

In your bean definition

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

What's the type of Validator in the method definition? You should make sure it returns javax.validation.Validator, not Validator from Spring.

Letting Spring bootstrap the validator will it also cause to pass a SpringConstraintValidatorFactory to Hibernate Validator which will enable dependency injection within constraint validators.

Upvotes: 2

Related Questions