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.

Claudiu Muresan
  • 401
  • 6
  • 19
  • 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? – Biju Kunjummen Oct 01 '12 at 18:20
  • 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 => you we're right about having a root web application. – Claudiu Muresan Oct 02 '12 at 01:12
  • 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 | 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? – Claudiu Muresan Oct 02 '12 at 05:38

2 Answers2

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" />
...
Claudiu Muresan
  • 401
  • 6
  • 19
  • 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. – Tito Apr 24 '14 at 09:58
  • I had to "inject" the validator into the MethodValidationPostProcessor. – Claudiu Muresan Apr 25 '14 at 14:52
  • if bean LocalValidatorFactoryBean and MethodValidationPostProcessor defined in dispatcher-servlet.xml the @Validated will not work. – Bodil Mar 14 '17 at 03:57
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.

Claudiu Muresan
  • 401
  • 6
  • 19