Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Spring MVC - lookup validators automatically

Suppose I have a sample entity class like this:

public class Address {
    ...
}

and a corresponding validator:

@Component
public AddressValidator implements Validator {

    @Override
    public boolean supports(Class<?> entityClass) {
        return entityClass.equals(Address.class);
    }

    @Override
    public void validate(Object obj, Errors errors) {
        ...
    }
}

When I use a controller like the following, everything works:

@RestController
@RequestMapping("/addresses")
public class AddressController {

    @Autowired
    private AddressValidator validator;

    @InitBinder
    protected void initBinder(WebDataBinder binder) {
        binder.setValidator(validator);
    }

    @RequestMapping(method=POST)
    public Long addNewAddress(@Valid @RequestBody Address address) {
        ...
    }
}

However, if I omit the validator registering part (i.e. the following), validation is not performed.

@Autowired
private AddressValidator validator;

@InitBinder
protected void initBinder(WebDataBinder binder) {
    binder.setValidator(validator);
}

Having to register validators manually seems pointless. Can I instruct Spring to look up validators automatically (similar to how controllers are looked up)?

It's a Spring Boot based application.

like image 843
Dušan Rychnovský Avatar asked Mar 07 '15 14:03

Dušan Rychnovský


2 Answers

You can use my example from gist or below. The idea is to have a main CompositeValidator that will be a holder of all your Validator or SmartValidator instances.

It supports hints and can be also integrate with Hibernate Annotation Validator (LocalValidatorFactoryBean). And also it's possible to have more that one validator per specific Model.


CompositeValidator.java

@Component
public class CompositeValidator implements SmartValidator {
    @Autowired
    private List<Validator> validators = Collections.emptyList();

    @PostConstruct
    public void init() {
        Collections.sort(validators, AnnotationAwareOrderComparator.INSTANCE);
    }

    @Override
    public boolean supports(Class<?> clazz) {
         for (Validator validator : validators) {
            if (validator.supports(clazz)) {
                return true;
            }
        }
        return false;
    }

    @Override
    public void validate(Object target, Errors errors) {
        validate(target, errors, javax.validation.groups.Default.class);
    }

    @Override
    public void validate(Object target, Errors errors, Object... validationHints) {
        Class<?> clazz = target.getClass();

        for (Validator validator : validators) {
            if (validator.supports(clazz)) {
                if (validator instanceof SmartValidator) {
                    ((SmartValidator) validator).validate(target, errors, validationHints);
                } else {
                    validator.validate(target, errors);
                }
            }
        }
    }
}

SomeController.java

@Controller
@RequestMapping("/my/resources")
public class SomeController {

    @RequestMapping(method = RequestMethod.POST)
    public Object save(
            @Validated(javax.validation.groups.Default.class) // this interface descriptor (class) is used by default
            @RequestBody MyResource myResource
    ) { return null; }
}

Java Config

 @Configuration
    public class WebConfig {
        /** used for Annotation based validation, it can be created by spring automaticaly and you don't do it manualy */
        // @Bean
        // public Validator jsr303Validator() {
        //   LocalValidatorFactoryBean validator = new LocalValidatorFactoryBean();
        //   // validator.setValidationMessageSource(...);
        //   return validator;
        // }

        @Bean
        public WebMvcConfigurerAdapter webMvcConfigurerAdapter() {
            return new WebMvcConfigurerAdapter() {
                @Autowired
                private CompositeValidator validator;

                @Override
                public Validator getValidator() {
                    return validator;
                }
            }
        }

Or XML Config

<!-- used for Annotation based validation, it can be created by spring automaticaly and you don't do it manualy -->
<!--<bean id="jsr303Validator" class="org.springframework.validation.beanvalidation.LocalValidatorFactoryBean">-->
<!--  <property name="validationMessageSource" ref="messageSource"/>-->
<!--</bean>-->

<mvc:annotation-driven validator="compositeValidator">
  //...
</mvc:annotation-driven>
like image 184
Pianov Avatar answered Oct 09 '22 05:10

Pianov


You can configure global Validator.

http://docs.spring.io/spring/docs/current/spring-framework-reference/html/validation.html#validation-mvc

If you are using Java based spring configuration with WebMvcConfigurationSupport, you can override getValidator()

/**
 * Override this method to provide a custom {@link Validator}.
 */
protected Validator getValidator() {
    return null;
}

You may call setValidator(Validator) on the global WebBindingInitializer. This allows you to configure a Validator instance across all @Controller classes. This can be achieved easily by using the Spring MVC namespace:

<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:mvc="http://www.springframework.org/schema/mvc"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="
        http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/mvc
        http://www.springframework.org/schema/mvc/spring-mvc.xsd">

    <mvc:annotation-driven validator="globalValidator"/>

</beans>
like image 34
mavarazy Avatar answered Oct 09 '22 06:10

mavarazy