我有一个通过JSR-303 bean验证的表单处理Spring MVC控制器@Valid。
@Valid
GETWhole-handler 的唯一作用是(几乎)与,相同POST,但是省略@Valid注释,以防止<form:errors ...>在第一个用户GET请求提交JSP 之前在JSP中显示错误。
GET
POST
<form:errors ...>
我的问题是:
这是我的示例代码:
@RequestMapping( value = "/account/register" , method = RequestMethod.GET ) public String registerGet( @ModelAttribute( "registerForm" ) RegisterForm registerForm ) { return "account/register"; } @RequestMapping( value = "/account/register" , method = RequestMethod.POST ) public String registerPost( @ModelAttribute( "registerForm" ) @Valid RegisterForm registerForm , BindingResult result , RedirectAttributes redirectAttributes ) { ... ADD USER HERE IF !result.hasErrors() ... return "account/register"; }
如果您查看spring如何解决传递给处理程序的参数,那么实现自己所需的功能并不难。默认情况下,spring将使用ModelAttributeMethodProcessor带有@ModelAttribute和简单类型注释的for参数。
ModelAttributeMethodProcessor
@ModelAttribute
只看ModelAttributeMethodProcessor.supportsParameter()方法实现。
ModelAttributeMethodProcessor.supportsParameter()
/** * @return true if the parameter is annotated with {@link ModelAttribute} * or in default resolution mode also if it is not a simple type. */ @Override public boolean supportsParameter(MethodParameter parameter) { if (parameter.hasParameterAnnotation(ModelAttribute.class)) { return true; } else if (this.annotationNotRequired) { return !BeanUtils.isSimpleProperty(parameter.getParameterType()); } else { return false; } }
ModelAttributeMethodProcessor如果@Valid找到了注释,它还负责启动验证。它以一种有趣的方式执行此操作,以使代码无需@Valid在类路径上即可进行编译。幸运的是,这很容易利用您的优势。
提取ModelAttributeMethodProcessor.validateIfApplicable() method.
ModelAttributeMethodProcessor.validateIfApplicable() method.
/** * Validate the model attribute if applicable. * <p>The default implementation checks for {@code @javax.validation.Valid}. * @param binder the DataBinder to be used * @param parameter the method parameter */ protected void validateIfApplicable(WebDataBinder binder, MethodParameter parameter) { Annotation[] annotations = parameter.getParameterAnnotations(); for (Annotation annot : annotations) { if (annot.annotationType().getSimpleName().startsWith("Valid")) { Object hints = AnnotationUtils.getValue(annot); binder.validate(hints instanceof Object[] ? (Object[]) hints : new Object[] {hints}); break; } } }
您可能已经注意到,它只是检查在参数开头是否存在注释,"Valid"并告诉绑定程序验证其值。
"Valid"
首先要做的是编写一个新的注释,您的自定义HandlerMethodArgumentResolver将支持该注释。
HandlerMethodArgumentResolver
//** Validates only when the request method is a modifying verb e.g. POST/PUT/PATCH/DELETE */ @Target({ PARAMETER }) @Retention(RUNTIME) public @interface ValidModifyingVerb {}
请注意,注释的名称故意以开头"Valid"。
最简单的方法是扩展ModelAttributeMethodProcessor和修改其行为。由于该resolveArgument()方法是 最终 方法,因此无法覆盖它。我们可以做的是重写以下三种方法:
resolveArgument()
supportsParameter(final MethodParameter parameter)
告诉spring这个解析器支持参数用注释@ValidModifyingVerb。
@ValidModifyingVerb
bindRequestParameters(final WebDataBinder binder, final NativeWebRequest request)
将是获得对请求的引用并查找其请求方法的理想人选。它还使您有机会在不需要时省略绑定参数。
validateIfApplicable(final WebDataBinder binder, final MethodParameter parameter)
使您有机会省略验证。如果您需要验证,由于您自己的注释开头"Valid"很好,因此它将被自动提取。
实现以上内容将导致类似于此的类。
public class ValidModifyingVerbMethodArgumentResolver extends ModelAttributeMethodProcessor { private String requestMethod; /** * @param annotationNotRequired if "true", non-simple method arguments and * return values are considered model attributes with or without a * {@code @ModelAttribute} annotation. */ public ValidModifyingVerbMethodArgumentResolver(final boolean annotationNotRequired) { super(annotationNotRequired); } @Override public boolean supportsParameter(final MethodParameter parameter) { return super.supportsParameter(parameter) && parameter.hasParameterAnnotation(ValidModifyingVerb.class); } @Override protected void bindRequestParameters(final WebDataBinder binder, final NativeWebRequest request) { HttpServletRequest servletRequest = request.getNativeRequest(HttpServletRequest.class); requestMethod = servletRequest.getMethod(); if (isModifyingMethod(requestMethod)) { ((ServletRequestDataBinder) binder).bind(servletRequest); } } @Override protected void validateIfApplicable(final WebDataBinder binder, final MethodParameter parameter) { if (isModifyingMethod(requestMethod)) { super.validateIfApplicable(binder, parameter); } } private boolean isModifyingMethod(String method) { return !"GET".equals(method); } }
剩下的唯一事情就是ValidModifyingVerbMethodArgumentResolver在应用程序上下文配置中注册为参数解析器,然后就完成了。
ValidModifyingVerbMethodArgumentResolver
public class ApplicationConfiguration extends WebMvcConfigurerAdapter { @Override public void addArgumentResolvers(final List<HandlerMethodArgumentResolver> argumentResolvers) { argumentResolvers.add(new ValidModifyingVerbMethodArgumentResolver(true)); } }
现在可以将控制器中的处理程序方法简化为:
@RequestMapping("/account/register") public String registerPost( @ValidModifyingVerb RegisterForm registerForm, BindingResult result, RedirectAttributes redirectAttributes) { //... ADD USER HERE IF !result.hasErrors() ... return "account/register"; }