一尘不染

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

spring-mvc

我有一个通过JSR-303 bean验证的表单处理Spring MVC控制器@Valid

GETWhole-handler 的唯一作用是(几乎)与,相同POST,但是省略@Valid注释,以防止<form:errors ...>在第一个用户GET请求提交JSP 之前在JSP中显示错误。

我的问题是:

  • 如何清除GET干净的冗余方法?
  • 我怎样才能让一个POST-only @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";
}

阅读 324

收藏
2020-06-01

共1个答案

一尘不染

如果您查看spring如何解决传递给处理程序的参数,那么实现自己所需的功能并不难。默认情况下,spring将使用ModelAttributeMethodProcessor带有@ModelAttribute和简单类型注释的for参数。

只看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.

/**
 * 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"并告诉绑定程序验证其值。

编写自定义注释

首先要做的是编写一个新的注释,您的自定义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"

推出自己的 HandlerMethodArgumentResolver

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

  1. supportsParameter(final MethodParameter parameter)

告诉spring这个解析器支持参数用注释@ValidModifyingVerb

  1. bindRequestParameters(final WebDataBinder binder, final NativeWebRequest request)

将是获得对请求的引用并查找其请求方法的理想人选。它还使您有机会在不需要时省略绑定参数。

  1. 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在应用程序上下文配置中注册为参数解析器,然后就完成了。

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";
}
2020-06-01