java - MethodValidationInterceptor 和@Validated @ModelAttribute

标签 java spring spring-mvc spring-boot hibernate-validator

我有一个 Spring Boot 2 应用程序,我希望能够使用 Hibernate validator 验证 Controller 参数 - 我正在成功使用它。我将所有 Controller 都注释为 @Validated 并且我正在对请求参数使用验证,例如 @PathVariable @AssertUuid final String customerId - 到目前为止一切正常.

但是,我还希望能够从表单中验证 @ModelAttribute

@Controller
@PreAuthorize("hasRole('ADMIN')")
@RequestMapping(path = "/customers")
@Validated
public class CustomerController
{

    private final CustomerFacade customerFacade;

    public CustomerController(
        final CustomerFacade customerFacade
    )
    {
        this.customerFacade = customerFacade;
    }

    @GetMapping("/create")
    public ModelAndView create(
        final AccessToken accessToken
    )
    {
        return new ModelAndView("customer/create")
            .addObject("customer", new CreateCustomerRequest());
    }

    @PostMapping("/create")
    public ModelAndView handleCreate(
        final AccessToken accessToken,
        @Validated @ModelAttribute("customer") final CreateCustomerRequest customerValues,
        final BindingResult validation
    ) throws 
        UserDoesNotHaveAdminAccessException
    {
        if (validation.hasErrors()) {
            return new ModelAndView("customer/create")
                .addObject("customer", customerValues);
        }

        CustomerResult newCustomer = customerFacade.createCustomer(
            accessToken,
            customerValues.getName()
        );

        return new ModelAndView(new RedirectView("..."));
    }

    public static final class CreateCustomerRequest
    {

        @NotNull
        @NotBlank
        private String name;

        public CreateCustomerRequest(final String name)
        {
            this.name = name;
        }

        public CreateCustomerRequest()
        {
        }

        public String getName()
        {
            return name;
        }

    }

}

但这会导致 MethodValidationInterceptor 在我发送无效数据时抛出 ConstraintViolationException。这通常是有道理的,我希望在所有其他情况下都有这种行为,但在这种情况下,如您所见,我想使用 BindingResult 来处理验证错误 - 这在使用时是必要的表格。

有没有一种方法可以告诉 Spring 不要使用 MethodValidationInterceptor 验证这个特定参数,因为它已经由绑定(bind)程序验证并且我想以不同的方式处理它?<​​/p>

我一直在研究 spring 代码,它看起来并不是为协同工作而设计的。我有一些解决方法:

  • 从参数中删除 @Validated
    • 在 Controller 方法中显式调用 validator.validate() - 丑陋且危险(您可能会忘记调用它)
    • 创建另一个 AOP 拦截器,它将找到“成对”的 @ModelAttributeBindingResult 并在那里调用 validator ,强制全局验证

我是不是完全错了?我错过了什么吗?有没有更好的办法?

最佳答案

我想出了一个可以让我继续工作的解决方案,但我认为这个问题没有解决。

正如我在原始问题中暗示的那样,当未使用 @Validated@Valid 注释时,此方面会强制验证 @ModelAttribute

这意味着 ConstraintViolationException 不会因无效的 @ModelAttribute 而抛出,您可以在方法体中处理错误。

import com.google.common.collect.Iterators;
import com.google.common.collect.PeekingIterator;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.core.MethodParameter;
import org.springframework.validation.Errors;
import org.springframework.validation.Validator;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.ModelAttribute;

import javax.validation.Valid;
import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.IntStream;

@SuppressWarnings({"checkstyle:IllegalThrows"})
@Aspect
public class ControllerModelAttributeAutoValidatingAspect
{

    private final Validator validator;

    public ControllerModelAttributeAutoValidatingAspect(
        final Validator validator
    )
    {
        this.validator = validator;
    }

    @Around("execution(public * ((@org.springframework.web.bind.annotation.RequestMapping *)+).*(..)))")
    public Object proceed(final ProceedingJoinPoint pjp) throws Throwable
    {
        MethodSignature methodSignature = MethodSignature.class.cast(pjp.getSignature());
        List<MethodParameter> methodParameters = getMethodParameters(methodSignature);

        PeekingIterator<MethodParameter> parametersIterator = Iterators.peekingIterator(methodParameters.iterator());
        while (parametersIterator.hasNext()) {
            MethodParameter parameter = parametersIterator.next();
            if (!parameter.hasParameterAnnotation(ModelAttribute.class)) {
                // process only ModelAttribute arguments
                continue;
            }
            if (parameter.hasParameterAnnotation(Validated.class) || parameter.hasParameterAnnotation(Valid.class)) {
                // if the argument is annotated as validated, the binder already validated it
                continue;
            }

            MethodParameter nextParameter = parametersIterator.peek();
            if (!Errors.class.isAssignableFrom(nextParameter.getParameterType())) {
                // the Errors argument has to be right after the  ModelAttribute argument to form a pair
                continue;
            }

            Object target = pjp.getArgs()[methodParameters.indexOf(parameter)];
            Errors errors = Errors.class.cast(pjp.getArgs()[methodParameters.indexOf(nextParameter)]);
            validator.validate(target, errors);
        }

        return pjp.proceed();
    }

    private List<MethodParameter> getMethodParameters(final MethodSignature methodSignature)
    {
        return IntStream.range(0, methodSignature.getParameterNames().length)
            .mapToObj(i -> new MethodParameter(methodSignature.getMethod(), i))
            .collect(Collectors.toList());
    }

}

现在您可以像往常一样继续在 Controller 方法中使用验证注解,与此同时,最终的 BindingResult 验证 将按预期工作。

@PostMapping("/create")
public ModelAndView handleCreate(
    final AccessToken accessToken,
    @ModelAttribute("customer") final CreateCustomerRequest customerValues,
    final BindingResult validation
)

关于java - MethodValidationInterceptor 和@Validated @ModelAttribute,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/50532039/

相关文章:

java - 卡在 JBOSS Ticket Monster 添加 Arquillian 中

java - 是否可以在 Servlet 3.0 环境中在外部 Jar 和 War 之间共享相同的 MVC?

java - Kafka 消费者手册提交偏移量

spring - 错误 405 请求方法 'POST' 不支持 Spring Security

java - 如何在jsp上显示服务器生成的Jfreechart图像

java - 线程 "something thread"java.lang.OutOfMemoryError : Java heap space. 中的异常我该怎么办?

java - JComponent PaintComponent 未出现在面板上

java - NPE 在我的休息舱服务中使用带有 Spring 的 Jersey

java - 使用 @PropertySource 和外部 JSON 文件进行 Spring 属性配置

java - Json对象和Spring Controller 客户端发送的请求在语法上不正确(错误的请求)