Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to: Spring get rid of @Validate for automatic Controller validation?

I know about @Valid annotation to instruct spring to validate for example a Controller argument according to JSR-303 in such this example:

@GetMapping("/test")
public TestDTO testDTO(@Valid TestDTO testDTO){
        return testDTO;
}

But I would like to be able to configure Spring in some way to enable validation in all my controllers without specify explicitly the @Valid annotation.

Is that possible in any way? Some Spring configuration? Making use of AOP?...

like image 413
Gerard Bosch Avatar asked Jun 23 '17 12:06

Gerard Bosch


2 Answers

I have finally came across with a working solution which may be not the optimal from the point of view of Spring configuration (as I said I'm Spring beginner).

The idea was to modify the argument resolvers (the ones that implement HandlerMethodArgumentResolver), replacing the argument resolver associated to arguments with a @RequestBody annotation. Creating an inherited class from the default one (which is RequestResponseBodyMethodProcessor) and overriding a method in the class hierarchy which efectively determines if perform a validation or not (based in the presence of @Valid, @Validated, @ValidXxxxxx annotations as the default behaviour), making to always validate with no further check.

So here is the code (I'm using Java 8 BTW):

Extend RequestResponseBodyMethodProcessor to define validation strategy (in this case, always validate):

public class MyRequestResponseBodyMethodProcessor extends RequestResponseBodyMethodProcessor {

    public MyRequestResponseBodyMethodProcessor(List<HttpMessageConverter<?>> converters) {
        super(converters);
    }

    @Override
    protected void validateIfApplicable(WebDataBinder binder, MethodParameter parameter) {
        binder.validate();      // always validating @RequestMapping annotated parameters ;)
    }
}

Define a @Configuration class where to replace default argument resolver:

@Configuration
public class MyValidationAdapterConfigurer {

    @Autowired
    private RequestMappingHandlerAdapter requestMappingHandlerAdapter;

    // Injecting your own resolver
    @Autowired
    private RequestResponseBodyMethodProcessor requestResponseBodyMethodProcessor;


    @PostConstruct
    public void init() {

        // Don't know why but, removing the target resolver and adding the injected one to the end does not work!
        // Must be something related with the resolvers ordering. So just replacing the target in the same position.
        final List<HandlerMethodArgumentResolver> mangledResolvers = requestMappingHandlerAdapter.getArgumentResolvers().stream()
            .map(resolver -> resolver.getClass().equals(RequestResponseBodyMethodProcessor.class) ?
                requestResponseBodyMethodProcessor: resolver)
            .collect(Collectors.toList());

        requestMappingHandlerAdapter.setArgumentResolvers(mangledResolvers);
    }

}

Finally configure Spring to deliver your customized Bean in your Application configuration class:

@Configuration
@PropertySource("classpath:api.properties")
public class MyRestApiConfiguration {

    @Bean
    @Autowired
    RequestResponseBodyMethodProcessor requestResponseBodyMethodProcessor(List<HttpMessageConverter<?>> converters) {
        return new MyRequestResponseBodyMethodProcessor(converters);
    }

}
like image 181
Gerard Bosch Avatar answered Sep 28 '22 07:09

Gerard Bosch


unfortunately there is no "legal" way to do it.

Furthermore @Valid is not enough. You need also a BindingResult method parameter to be able to check the validation result: bindingResult.hasErrors()

If you don't want to use BindingResult, you could write your own Validator and throw an Exception in case of invalid input.

like image 45
dieter Avatar answered Sep 28 '22 07:09

dieter