Autowired gives Null value in Custom Constraint validator

17,141

Solution 1

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

Solution 2

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.

Solution 3

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.

Share:
17,141

Related videos on Youtube

Nick Div
Author by

Nick Div

SOreadytohelp

Updated on September 15, 2022

Comments

  • Nick Div
    Nick Div over 1 year

    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; 
    
  • Nick Div
    Nick Div almost 8 years
    I have only one import in my config file for Validator, I also tried javax.validation.Validator explicitly in the return type.
  • Gunnar
    Gunnar almost 8 years
    Have you tried debugging it? In LocalValidatorFactoryBean you should step through the code passing Spring's constraint validator factory to the bootstrapped validator.
  • Nick Div
    Nick Div almost 8 years
    But it was not using Hibernate's validator in the first place. And when I explicitly set Hibernate's validator from the Java config it was taking Hibernate's ConstraintValidatorFactoryImpl (which is obvious) I have debugged and tried more options than I have posted here. I did a find a solution though which I have posted as an answer. I do appreciate your attempt to help a fellow programmer. Thanks.
  • Gunnar
    Gunnar almost 8 years
    Hum, ok; Still odd, LocalValidatorFactoryBean should be using SpringConstraintValidatorFactory by default.
  • Nick Div
    Nick Div almost 8 years
    I think there is some mis-communication here. LocalValidatorFactoryBean does use SpringConstraintValidatorFactory. Problem was that I was not able to make LocalValidatorFactryBean return me the implementation of HIbernate so I manually plugged it in from the Config but doing that gave me ConstraintValidatorFactoryImpl as my ConstraintValidator provider which again caused issues for me. So then I decided to override that as well. So here is what I am doing, I am asking for Hibernate validator by default which uses Constraintfactory of Spring.
  • shabyasachi
    shabyasachi over 7 years
    Thanks. It works! However, the method signature should be for autowiring the dependency: public Validator validator(final AutowireCapableBeanFactory autowireCapableBeanFactory) {
  • Nick Div
    Nick Div almost 7 years
    @shabyasachi I am glad it helped you :) This was like a brain teaser!!
  • drenda
    drenda over 6 years
    @NickDiv what's the parameter autowireCapableBeanFactory? From where should I take that? Thanks
  • robinCTS
    robinCTS over 6 years
    Congratulations on your first post! While this code snippet may solve the question, including an explanation really helps to improve the quality of your post. Remember that you are also answering the question for readers in the future, and those people might not know the reasons for your code suggestion.
  • Mssm
    Mssm almost 5 years
    It didn't work for me :/ ... can you post full code of yours ?
  • Nick Div
    Nick Div almost 5 years
    @Mouley I do not have that project anymore but if you can open a new question describing what exactly is happening or any errors that you see, me and other SO members can take a look
  • Mssm
    Mssm almost 5 years
    @Nick Div I opened a question here => stackoverflow.com/questions/56782292/…
  • Nick Div
    Nick Div almost 5 years
    @Mouley I see that you already found a solution. That's great.
  • Mssm
    Mssm almost 5 years
    @NickDiv yeah but unfortunatly this causes another problem... stackoverflow.com/questions/56798777/… ... looks like findBy is validating again and again resulting to a stackoverflowexception
  • user3235738
    user3235738 over 4 years
    No idea how that worked for you, but here it gives me "Caused by: java.lang.NoSuchMethodException: training.edit.jpa.validator.CompanyValidator.<init>()" The has to be a default constructor it appears.
  • codesalsa
    codesalsa over 4 years
    Check this option dolszewski.com/spring/custom-validation-annotation-in-spring This works fine so no need for anything special. Maybe some new Spring change that was not available when this question was posted.
  • ganaraj
    ganaraj almost 4 years
    This solution did not work with Spring Boot 2. The repository gets autowired everywhere else except in the ContraintValidator of a Custom Validator invoked manually.
  • Nick Div
    Nick Div almost 4 years
    @ganaraj I am guessing this was for an older spring version. I believe the link in the previous comment might help you.
  • Nick Div
    Nick Div over 3 years
    @CoderJammer I am glad I was able to help :)
  • dushkin
    dushkin over 3 years
    @ganaraj Hi, I would really appreciate if you could share if you find any solution. It drives me crazy!!!
  • dushkin
    dushkin over 3 years
    @NickDiv Thanks Nick for the solution. I am a Spring novice. I don't understand where and how should I use validator bean? Where should I put it? Should I explicitly call it? Thanks!
  • Nick Div
    Nick Div over 3 years
    @dushkin You can place/inject the Validator Bean inside your Custom Constraint Validators just like how you Autowire dependencies in your other beans. For e.g. a repository is Autowired into a service bean
  • ganaraj
    ganaraj over 3 years
    @dushkin I never found a solution for this. Worked around this by defining my constraint under a group and calling the Group Validator before persisting. (Set<ConstraintViolation<User>> violations = validator.validate(user);)