Spring 3.1 Autowiring does not work inside custom constraint validator
Asked Answered
R

2

11

I have an issue with bean autowiring inside a custom constraint validator. A constraint validator instance is not given using Spring's LocalValidatorFactoryBean. The JSR-303 provider is hibernate-validator 4.2.0.Final.

Spring configuration excerpt :

<!-- JSR 303 validation -->
<bean id="validator" class="org.springframework.validation.beanvalidation.LocalValidatorFactoryBean" />
<bean class="org.springframework.validation.beanvalidation.MethodValidationPostProcessor"/> 

Custom Constraint Validator:

import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;

import org.springframework.beans.factory.annotation.Autowired;

import com.model.Subject;
import com.services.SomeTypeService;

public class ReadOnlyValidator implements ConstraintValidator<ReadOnly, String> {

@Autowired
private SomeTypeService someTypeService;

@Override
public void initialize(ReadOnly constraintAnnotation) { }

@Override
public boolean isValid(String type, ConstraintValidatorContext context) {
    try {
         if (null != type) {
                        return !someTypeService.isReadonly(type);
                     }
    } catch (Exception e) {
        e.printStackTrace();
    }

    return false;
}
}

Annotation:

import static java.lang.annotation.ElementType.*;
import static java.lang.annotation.RetentionPolicy.*;

import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;

import javax.validation.Constraint;
import javax.validation.Payload;

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

SomeService:

 @Validated
 public interface SomeService {
    ...
    public void updateType(@ReadOnly String type) throws SomeException;
    ...
 }

SomeServiceImpl :

 @Service
 public class SomeServiceImpl implements SomeService {
  ...
    public void updateType(String type) throws SomeException {
     // do something
    }
  ...
 }

SomeTypeService is another @Service annotated bean that does not depend on SomeService ...

The problem is that I get an NPE as autowiring does not work; someone else is managing the custom validator instances and not Spring ...

Thanks in advance for any type of advice.

Rios answered 1/10, 2012 at 15:28 Comment(3)
Question Claudiu - Do you have two different application contexts - one declared through ContextLoaderListener and one through DispatcherServlet, since you have @validated at the level of service methods, you probably need to declare a LocalValidatorFactoryBean in the root web application context also?Perdure
Well I did find some anomalies related to the number of LocalValidatorFactoryBean instances. The declaration that I gave in the "question" description is located in a service-content.xml => this is one instance. There is another instance created automatically when using <mvc:annotation-driven/> => you we're right about having a root web application.Rios
After verifying that only one LocalValidatorFactoryBean instance is created, I've moved to verify also how many instances of ReadOnlyValidator are created. Using jmap -histo:live <PID> | grep "ReadOnlyValidator", I've noticed that only one instance is created but only when the updateType service method is called for the first time and not when the LocalValidatorFactBean is initialized. I think the hibernate validation engine simply creates its own constraint validator instance. How would I "force" LocalValidatorFactoryBean to manage ReadOnlyValidator instance so that bean wiring can be used?Rios
R
10

Found the problem. The same "validator" bean reference must be used by MethodValidationPostProcessor and mvc:annotation-driven declaration:

service-context.xml

<!-- JSR 303 validation -->
<bean id="validator" class="org.springframework.validation.beanvalidation.LocalValidatorFactoryBean"/>
<bean class="org.springframework.validation.beanvalidation.MethodValidationPostProcessor">
    <property name="validator" ref="validator"/>
</bean>
    ...

dispatcher-servlet.xml

...
<mvc:annotation-driven validator="validator" />
...
Rios answered 2/10, 2012 at 8:25 Comment(3)
Claudiu, does this mean that we need to register every new validator with the MethodValidationPostProcessor. Do you know how to implement this with a Java Config. I am expiriencing the same problem. The only difference is that i am autowiring an Interface and that i am using Java Config and not xml.Malcolmmalcom
I had to "inject" the validator into the MethodValidationPostProcessor.Rios
if bean LocalValidatorFactoryBean and MethodValidationPostProcessor defined in dispatcher-servlet.xml the @Validated will not work.Inestimable
R
4

Tito, please check if you can use something like below for Java config:

@Configuration
public class SampleConfig {
  ...

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

  @Bean
  public MethodValidationPostProcessor mvpp() {
    MethodValidationPostProcessor mvpp = new MethodValidationPostProcessor();
    mvpp.setValidator(validator());

    return mvpp;
  }

  ...
}

To reuse the validator bean in a different configuration bean, you can use the @Import annotation.

Rios answered 25/4, 2014 at 14:57 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.