java - MethodValidationInterceptor 和 @Validated @ModelAttribute
问题描述
我有一个 Spring Boot 2 应用程序,我希望能够使用 Hibernate 验证器验证控制器参数 - 我正在成功使用它。我将所有控制器都注释为@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
,因为它已经被活页夹验证了,我想以不同的方式处理它?
我一直在研究 spring 代码,它看起来并不是为了协同工作而设计的。我有一些想法如何解决这个问题:
@Validated
从参数 中删除- 在控制器方法中显式调用
validator.validate()
- 丑陋且危险(您可能忘记调用它) - 创建另一个 AOP 拦截器,它将找到 and 的“对”
@ModelAttribute
并BindingResult
在那里调用验证器,强制全局验证
- 在控制器方法中显式调用
我会完全错了吗?我错过了什么吗?有没有更好的办法?
解决方案
我想出了一个可以让我继续工作的解决方案,但我认为这个问题还没有解决。
正如我在原始问题中所暗示的那样,这个方面强制验证@ModelAttribute
它何时没有用@Validated
or注释@Valid
。
这意味着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());
}
}
现在,您可以像往常一样在控制器方法中继续使用验证注释,同时final BindingResult validation
按预期工作。
@PostMapping("/create")
public ModelAndView handleCreate(
final AccessToken accessToken,
@ModelAttribute("customer") final CreateCustomerRequest customerValues,
final BindingResult validation
)
推荐阅读
- google-cloud-shell - 带有 ?authuser=1 参数的谷歌云外壳不断加载
- list - 给定位置可以采用的元素,生成所有可能列表的列表的函数
- flask - flask sqlalchemy filter_by 给出运行时错误
- apache-kafka - Spring Kafka Stream - 未确认的消息,没有错误
- java - 在 GET 请求正文中将数据传输到 Java 应用程序(servlet)
- python - 我正在尝试在同一图中绘制不同温度的黑体?
- javascript - 模板未显示在我的 Laravel 6.6 项目中使用 Vue.js
- c# - Java.Lang.IllegalArgumentException:无效位图格式在启动时间歇性
- vue.js - 将 Flask 与 Nuxtjs 结合用于 SSR
- mysql - 如何在 MySQL 中使用 insert 和 update 执行 case 语句?