Rossen Stoyanchev opened SPR-11900 and commented

The logic in MethodValidationInterceptor is pretty useful. However it is currently not easy to use if not in the context of AOP method interception. I'd like to be able to use it for invoking an @MVC HandlerMethod with the ability to validate method arguments first and then the method return value as a separate step.


Affects: 4.0.5

Issue Links: - #15017 Random results for JSR-303 method constraint validation on generically typed methods - #18007 @Validated support on Iterables (and implementors) - #19182 Validate values in top-level Map parameters

10 votes, 12 watchers

Comment From: spring-projects-issues

Juergen Hoeller commented

As per our discussion today, we're going to revisit this from a slightly different perspective: Calling a Bean Validation provider for individual values to validate, given a set of constraint annotations... Those may come from a specific method parameter but also from a field or the like.

Reusing the method validation feature in Bean Validation 1.1 is not going to get us there since it doesn't allow for the individual validation of parameters, just for validating all parameters at once. This proves to be problematic with our existing support for @Valid beans, as well as with finding out which exact parameter failed to validate (since no parameter index is being exposed by BV 1.1, just the failing value).

Juergen

Comment From: spring-projects-issues

Juergen Hoeller commented

Looking at the inner APIs of Hibernate Validator 5, there doesn't seem to be an easy way to validate a single value within the context of a specific method parameter at all. Ideally, we'd either get such an API or even a super-plain API that just takes a value to validate plus an array of annotations, independent from the source of those annotations...

Ironically, Hibernate Validator 4.3 did provide a proprietary validateParameter method for an individual parameter. However, that API is deprecated there, so nothing to base a new Spring feature on if there's no equivalent in Hibernate Validator 5. If we get some such API in Hibernate Validator 5, we could support the Hibernate Validator 4.3 variant as a fallback.

Juergen

Comment From: spring-projects-issues

Baron Roberts commented

I was able to achieve controller parameter validation using the forExecutables() method on the JSR 303 validator within an aspect. Since Juergen raised the question of available validation API's in Hibernate Validator 5, I thought you folks might find this useful.

First I created a custom annotation that will be placed on the controller methods I wish to be validated. I could have accomplished the validation without the use of the annotation and applied the advice to all controller methods but I wanted the ability to be more selective. Here is the code for the annotation:

package com.cthing.validation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * Marks a controller method whose parameters are to be validated using
 * Java and Hibernate validators.
 */
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface ValidateParams {
}

The heart of the solution is the validation aspect. I apply the advice to any controller method that has the above annotation. Using a JSR 303 ValidatorFactory, I obtain an ExecutableValidator and use its validateParameters() method to validate the method parameters provided by the aspect. Here is the code for the aspect:

package com.cthing.controller;

import java.lang.reflect.Method;
import java.util.Set;
import javax.inject.Inject;
import javax.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException;
import javax.validation.ValidatorFactory;
import javax.validation.executable.ExecutableValidator;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;

/**
 * Performs validation on controller parameters annotated with {@link com.cthing.validation.ValidateParams}.
 */
@Aspect
@Component
public class ValidateParamsAdvice {

    private final ValidatorFactory validatorFactory;


    @Inject
    public ValidateParamsAdvice(final ValidatorFactory jsr303ValidationFactory) {
        this.validatorFactory = jsr303ValidationFactory;
    }

    /**
     * Validates controller method parameters.
     *
     * @param joinPoint  Matched method join point
     */
    @Before("execution(public * com.cthing.controller.*.*(..)) && @annotation(com.cthing.validation.ValidateParams)")
    public void validateMethodParams(final JoinPoint joinPoint) {
        final Object controller = joinPoint.getThis();
        final Method controllerMethod = ((MethodSignature)joinPoint.getSignature()).getMethod();
        final Object[] params = joinPoint.getArgs();

        final ExecutableValidator executableValidator = this.validatorFactory.getValidator().forExecutables();
        final Set<ConstraintViolation<Object>> violations =
                executableValidator.validateParameters(controller, controllerMethod, params);
        if (!violations.isEmpty()) {
            throw new ConstraintViolationException(violations);
        }
    }
}

Comment From: spring-projects-issues

Sam Brannen commented

Thanks for sharing, Baron!

The API provided via ExecutableValidator does indeed look promising.

Comment From: spring-projects-issues

Baron Roberts commented

Any update on this issue?

Comment From: spring-projects-issues

Juergen Hoeller commented

The problem with the Bean Validation 1.1 ExecutableValidator API is that it has only been designed for validating all parameters of a given method/constructor at once. What we need for our MVC argument handling is an API for validating a single parameter value as an individual step, picking up the constraint metadata from a specified method/constructor... and I'm still not seeing this anywhere in Hibernate Validator 5.x.

I suppose we need to get in touch with the HV team and ask for introducing such a validateValue variant for method/constructor use in Hibernate Validator 5.3, since this request doesn't seem to be a bad fit with the current issues planned there (https://hibernate.atlassian.net/projects/HV/versions/21451).

Comment From: spring-projects-issues

Filip Panovski commented

Hi, has there been any update on this issue? Is it still pending Hibernate updates, or has it been postponed indefinitely? Definitely a very nice feature that would make Controller code more readable and streamilined. I unfortunately cannot open the Hibernate project ticket linked in the last comment, so I am not sure what the status there is.

Comment From: jhoeller

Superseded by #30645.