如何通过提供仅POST @Valid来删除多余的Spring MVC方法?

Kin*_*cka 2 java spring jsp spring-mvc bean-validation

我有一个表单处理Spring MVC控制器,带有JSR-303 bean验证@Valid.

整个处理GET程序的唯一目的是(几乎)相同POST,但省略@Valid注释以防止在提交表单之前<form:errors ...>在第一个用户GET请求中显示JSP中的错误.

我的问题是:

  • 如何GET以干净的方式删除冗余方法?
  • 我怎么能POST只做一个@Valid

这是我的示例代码:

@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";
}
Run Code Online (Sandbox Code Playgroud)

Bar*_*art 5

如果看看spring如何解析传递给处理程序的参数,那么实现自己完全正如你想要的那样并不是很困难.默认情况下,spring将使用ModelAttributeMethodProcessor带有注释的for参数@ModelAttribute和简单类型.

只需看看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;
    }
}
Run Code Online (Sandbox Code Playgroud)

ModelAttributeMethodProcessor如果@Valid找到注释,还负责验证.它以一种有趣的方式实现这一点,使代码编译而不@Valid在类路径上.幸运的是,这使您可以轻松利用自己的优势.

提取 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;
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

正如您可能已经注意到的那样,它只是检查参数上是否存在注释,并指示"Valid"绑定器验证它的值.

编写自定义注释

首先要做的是编写一个自定义HandlerMethodArgumentResolver将支持的新注释.

//** Validates only when the request method is a modifying verb e.g. POST/PUT/PATCH/DELETE */
@Target({ PARAMETER })
@Retention(RUNTIME)
public @interface ValidModifyingVerb {}
Run Code Online (Sandbox Code Playgroud)

请注意,注释的名称是故意以"Valid".

推出自己的 HandlerMethodArgumentResolver

最简单的方法是扩展ModelAttributeMethodProcessor和修改它的行为.由于该resolveArgument()方法是最终的,因此无法覆盖它.我们可以做的是覆盖以下三种方法:

  1. supportsParameter(final MethodParameter parameter):

    告诉Spring这个解析器支持带有注释的参数@ValidModifyingVerb.

  2. bindRequestParameters(final WebDataBinder binder, final NativeWebRequest request):

    将是获得对请求的引用并查找其请求方法的完美候选者.它还为您提供了在我们不需要它时省略绑定参数的机会.

  3. 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);
    }
}
Run Code Online (Sandbox Code Playgroud)

剩下的唯一事情就是ValidModifyingVerbMethodArgumentResolver在应用程序上下文配置中注册为参数解析器,然后就完成了.

public class ApplicationConfiguration extends WebMvcConfigurerAdapter {
    @Override
    public void addArgumentResolvers(final List<HandlerMethodArgumentResolver> argumentResolvers) {
        argumentResolvers.add(new ValidModifyingVerbMethodArgumentResolver(true));
    }
}
Run Code Online (Sandbox Code Playgroud)

控制器中的处理程序方法现在可以简化为:

@RequestMapping("/account/register")
public String registerPost(
        @ValidModifyingVerb RegisterForm registerForm,
        BindingResult result,
        RedirectAttributes redirectAttributes) {

    //... ADD USER HERE IF !result.hasErrors() ...

    return "account/register";
}
Run Code Online (Sandbox Code Playgroud)