【问题标题】:How does Spring MVC resolve and validate handler method parameters?Spring MVC 如何解析和验证处理程序方法参数?
【发布时间】:2016-01-08 07:18:48
【问题描述】:

我是 Spring MVC 的新手,我已经导入了一个与服务器端验证相关的教程项目,但我对它的工作原理有些怀疑。

所以我有一个名为 login.jsp 的登录页面,其中包含此登录表单:

<form:form action="${pageContext.request.contextPath}/login" commandName="user" method="post">

    <table>

        <tr>
            <td><label>Enter Username : </label></td>
            <td><form:input type="text" path="username" name="username" />
                <br> <form:errors path="username" style="color:red;"></form:errors>
            </td>
        </tr>

        <tr>
            <td><label>Enter Password : </label></td>
            <td><form:input type="password" path="password" name="password" />
                <br> <form:errors path="password" style="color:red;"></form:errors>
            </td>
        </tr>

        <tr>
            <td>&nbsp</td>
            <td align="center"><input type="submit" value="Login" /></td>
        </tr>

    </table>

</form:form>

我认为使用从模型中检索到的 commandName="user" 属性指定的对象(如果我的断言错误,请纠正我)来存储用户插入的用户名和密码。

这个 commandName="user" 是这个 User 类的一个实例:

import javax.validation.constraints.Size;
import org.hibernate.validator.constraints.NotBlank;

public class User {

    @NotBlank(message="Username can not be blank")
    private String username;

    @Size(min=6,message="Password must be atleast 6 characters long")
    private String password;

    private String gender;
    private String vehicle;
    private String country;
    private String image;

    ...............................................
    ...............................................
    GETTER AND SETTER METHODS
    ...............................................
    ...............................................
}

如您所见,usernamepassword 上声明了 @NotBlank@Size 验证注解强> 领域。

这里的第一个疑问:与 2 used library javax.validationorg.hibernate.validator 到底有什么区别?

为什么在教程中同时使用两者?我可以只使用休眠验证器库来做同样的事情吗? (我认为我可以使用 Hibernate 验证器指定字符串的有效长度,或者不)?

因此,当登录表单被提交时,它会向 /login 资源生成 HttpRequest,该资源由该方法处理并声明为控制器类:

@RequestMapping(value="/login" , method=RequestMethod.POST)
public String do_login(HttpServletRequest req , Model md , HttpSession session , @Valid User user, BindingResult br)
{
    try
    {
        //System.out.println(br.getAllErrors().size());

        String username = req.getParameter("username");
        String password = req.getParameter("password");

        System.out.println("Username and pasword are : "+username +"  "+ password);
        if(br.getAllErrors().size() > 0){
            System.out.println("Server side validation takes place....");
        }
        else{
        Login_Model lm = new Login_Model();
        String message = lm.do_login_process(username, password);

        if(message.equals("login success"))
        {
            session.setAttribute("username", username);
            return "redirect:/myprofile";
        }
        else
        {
            md.addAttribute("error_msg", message);
        }
        }
        return "login";
    }
    catch(Exception e)
    {
        return "login";
    }
}

好的,现在我对这个方法有以下疑惑:

1) 将这个对象作为输入参数:@Valid User user。 谁传给它?我认为这可能取决于我在表单中指定 commandName="user" 的事实,因此 Spring 会自动执行此操作。对吗?

2) 据我了解,@Valid 注释会自动调用验证过程。它是怎么发生的?与 Spring 提供的 AOP 功能有关吗?要不然是啥?为什么这个 @Valid 注释只与 javax.validation 库有关,而与 Hibernate 验证器无关(所以这个 @Valid 注释也验证用 Hibernate 验证器注解注解的字段?为什么?)

3) 据我了解,如果用户在登录表单中插入错误的值,我可以通过 BindingResult br 输入参数获取此错误。使用调试器,我可以看到该对象包含由定义到 User 模型对象中的注释定义的错误消息。具体是如何工作的?

Tnx

【问题讨论】:

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


【解决方案1】:

这里是第一个疑问:javax.validationorg.hibernate.validator 使用的 2 个库之间到底有什么区别?

javax.validation 来自 JSR-303 API。例如,您可以在 this Maven dependency 中查看 API 类。

Hibernate validator 是 JSR 303 的实现之一(实际上是参考实现),因此它实现了所有 API,但添加了自己的扩展,例如您提到的 @NotBlank 注释。 JSR 303 的其他实现例如是Apache BVal


它将这个对象作为输入参数:@Valid User user。谁将它传递给处理程序方法?

Spring MVC 中处理程序方法的值由接口HandlerMethodArgumentResolver 的实现提供。在您的情况下,将调用以解析User 参数的实现可能是ServletModelAttributeMethodProcessor。您可以查看这些类的源代码和 JavaDocs 以了解它们在内部是如何工作的。


@Valid 注解会自动调用验证过程。这是怎么发生的?它使用AOP吗?为什么这个@Valid 注释只与javax.validation 库相关,而与Hibernate 验证器无关(所以这个@Valid 注释也验证用Hibernate 验证器注释注释的字段?)

验证过程由ModelAttributeMethodProcessor 调用,这是前面提到的ServletModelAttributeMethodProcessor 类继承自。它包含以下方法:

protected void validateIfApplicable(WebDataBinder binder, MethodParameter parameter) {
    Annotation[] annotations = parameter.getParameterAnnotations();
    for (Annotation ann : annotations) {
        if (ann.annotationType().getSimpleName().startsWith("Valid")) {
            Object hints = AnnotationUtils.getValue(ann);
            binder.validate(hints instanceof Object[] ? (Object[]) hints : new Object[] {hints});
            break;
        }
    }
}

如果您仔细观察,您会看到以下表达式的条件:

ann.annotationType().getSimpleName().startsWith("Valid")

这意味着如果参数有任何以Valid 开头的注释,Spring 将调用验证。它可能是支持验证组的 JSR 303 的 @Valid 或 Spring 的 @Validated。它甚至可以是您的自定义注解,只要它的名称以 Valid 开头。


据我了解,如果用户在登录表单中插入错误值,我可以从 BindingResult 处理程序参数中获取此错误。使用调试器,我可以看到该对象包含由定义到 User 模型对象中的注释定义的错误消息。具体是如何工作的?

让我们回到ModelAttributeMethodProcessor 类。在其resolveArgument 方法中有如下代码:

WebDataBinder binder = binderFactory.createBinder(webRequest, attribute, name);

这将创建WebDataBinder 的实例,在前面提到的validateIfApplicable 方法中调用验证。验证本身会填充BindingResult,然后通过ErrorsMethodArgumentResolver 类将其提供给控制器处理程序方法,该类再次实现HandlerMethodArgumentResolver


TLDR:您在这个问题中提出的很多问题都可以追溯到HandlerMethodArgumentResolver 的各种实现。我建议浏览这些类并使用调试器逐步完成它们以更好地理解它们。

【讨论】:

  • Tnx 这么多。完美的解释。只有另一个问题。我在哪里可以找到这样的深层框架解释?除了官方的在线文档,还有其他有效的书籍吗?
  • @AndreaNobili 恐怕官方文档没有包含对这些低级概念的深入解释。我学习它的方法是浏览框架代码(“静态”或使用调试器单步执行)并阅读组件的 JavaDocs。
  • 所以我想我会听从你的建议......在我的业余时间......因为在工作中我总是很忙可以做这样的事情:-(
【解决方案2】:

与 2 used library 到底有什么不同 javax.validation 和 org.hibernate.validator?

Bean Validation 是一个 Java 规范,它允许您通过注解表达对对象模型的约束,允许您以可扩展的方式编写自定义约束。 这只是一个规范,根本没有实现。 Hibernate Validator 是这个规范的一个实现。 Hibernate Validator 完全实现了 Bean Validation,它也有一些 hibernate 独有的特性。简单地说,javax.validation 是规范部分,org.hibernate.validator 是休眠的独特功能。

我可以只使用休眠验证器库来做同样的事情吗? (一世 认为我可以使用指定字符串的有效长度 Hibernate 验证器,是否)?

是的,您可以,但最好坚持使用 javax.validation 命名空间,并且只使用供应商特定命名空间的独特功能,例如 org.hibernate.validator。这种方法的好处是您可以将 Bean Validation 实现从 Hibernate Validator 切换到其他东西只需最少的更改

为什么在教程中同时使用两者?

因为@NotBlank 约束在 Bean Validation 规范中不可用,仅由 Hibernate Validator 提供。

将这个对象作为输入参数:@Valid User user。传给谁 它?我认为这可能取决于以下事实,即我的形式 指定 commandName="user" 所以 Spring 会自动执行此操作。是吗 对吗?

可能你有 GET 控制器用于 login url,它返回一个空的 User 对象,而该对象又与诸如 &lt;form:input type="text" path="username" name="username" /&gt; 之类的弹簧标签绑定。当用户填写表单并提交时,spring 会收集这些标签值并创建User 的实例并将其传递给控制器​​。

为什么这个@Valid注解只和javax.validation有关 库而不是 Hibernate 验证器(所以这个 @Valid 注释也验证用 Hibernate 验证器注释的字段 注解?为什么?)

已经解释过了,规范/实现的故事!

【讨论】:

    【解决方案3】:
    1. 正确,称为属性绑定
    2. Spring 3+ 支持通过 @Validannotation 验证 JSR303Bean,如果类路径上有任何 JSR 303 验证器框架。 Hibernate Validator 是 JSR 303 参考实现之一

    3. 任何违反约束的行为都会在 BindingResult 对象中暴露为错误,因此您必须像您所拥有的那样检查控制器方法中的违反情况

      if(br.getAllErrors().size() > 0){ System.out.println("服务器端验证发生......"); }

    总结,Spring MVC 将在 binding its properties 之后使用来自使用 Spring 的表单标签的 JSP 表单的输入验证由 @Valid 注释注释的模型对象。任何违反约束的行为都将作为 BindingResult 对象中的错误公开 here is a good post

    【讨论】:

      猜你喜欢
      • 2012-12-01
      • 2011-08-03
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-12-08
      • 2019-04-22
      相关资源
      最近更新 更多