31

我在理解 Spring 3 MVC 中的表单提交如何工作时遇到问题。

我想做的是创建一个控制器,它将获取用户名并将其显示给他。不知何故,我做到了,但我真的不明白它是如何工作的。所以..

我有一个看起来像这样的表格:

<form:form method="post" modelAttribute="person">
    <form:label path="firstName">First name</form:label>
    <form:input path="firstName" />
    <br />

    <form:label path="lastName">Last name</form:label>
    <form:input path="lastName" />
    <br />

    <input type="submit" value="Submit" />
</form:form>

我还有一个如下所示的控制器:

@Controller
public class HomeController {

    @RequestMapping(value = "/", method = RequestMethod.GET)
    public String showHelloPage(Model model) {
        model.addAttribute("person", new Person());
        return "home";
    }

    @RequestMapping(value = "/", method = RequestMethod.POST)
    public String sayHello(Person person, Model model) {
        model.addAttribute("person", person);
        return "home";
    }
}

为了向用户显示欢迎消息,我在 JSP 页面中使用以下代码:

<c:if test="${not empty person.firstName and not empty person.lastName}">
    Hello ${person.firstName} ${person.lastName}!
</c:if>

它可以工作(我省略了 XML 配置文件,因为它们与问题无关)。

我认为表单中的“modelAttribute”属性指向应该用输入值填充的bean变量(在它们的“路径”属性中设置)。但是看起来,它的工作方式非常不同。如果我删除线

model.addAttribute("person", new Person());

从“showHelloPage”方法我得到一个(常见的)异常“既不是 BindingResult 也不是......”。

此外,一开始,“sayHello”方法看起来像:

(...)
public String sayHello(@ModelAttribute("person") Person person, Model model) {
(...)

我的意思是,它有“ModelAttribute”注释。我添加了它,因为在我阅读的教程中,它总是存在的。但是在我删除它之后,一切都运行良好,就像以前一样。

所以我的问题是-“ModelAttribute”注释有什么用?是否有某种方式可以省略表单中的“modelAttribute”属性?第二部分,使表单自动将输入值绑定到正确 bean 的属性(将被声明为方法参数)的方式(可能是一些注释)是什么?无需在发送表单之前添加一个空 bean(我现在必须这样做)。

感谢您的回复(这不是 Spring 文档的链接,因为我已经阅读过它)。

4

1 回答 1

40

@ModelAttribute例中的注解用于标识 Spring 应作为模型属性添加的对象。模型属性是对属性的抽象HttpServletRequest。基本上,它们是由某个键标识的对象,这些键将进入HttpServletRequest属性。您可以通过手动添加属性Model#addAttribute(String, Object)、使用@ModelAttribute注释方法或使用注释方法参数来执行此操作@ModelAttribute

您需要了解的是 Spring 如何解析您的处理程序方法参数并注入参数。它使用HandlerMethodArgumentResolver接口来执行此操作。有许多实现类(参见 javadoc),每个类都有责任通过反射resolveArgument()将 Spring 将使用的参数返回给您的处理程序方法。只有当方法返回特定参数时, invoke()Spring才会调用该方法。resolveArgument()HandlerMethodArgumentResolver supportsParameter()true

这里有问题的HandlerMethodArgumentResolver实现是ServletModelAttributeMethodProcessorModelAttributeMethodProcessor哪个州延伸

解析带有@ModelAttribute 注释的方法参数,并处理带有@ModelAttribute 注释的方法的返回值。

Spring(3.2)将注册这个HandlerMethodArgumentResolver和其他

private List<HandlerMethodArgumentResolver> getDefaultArgumentResolvers() {
        List<HandlerMethodArgumentResolver> resolvers = new ArrayList<HandlerMethodArgumentResolver>();

    // Annotation-based argument resolution
    resolvers.add(new RequestParamMethodArgumentResolver(getBeanFactory(), false));
    resolvers.add(new RequestParamMapMethodArgumentResolver());
    resolvers.add(new PathVariableMethodArgumentResolver());
    resolvers.add(new ServletModelAttributeMethodProcessor(false));
    resolvers.add(new RequestResponseBodyMethodProcessor(getMessageConverters()));
    resolvers.add(new RequestPartMethodArgumentResolver(getMessageConverters()));
    resolvers.add(new RequestHeaderMethodArgumentResolver(getBeanFactory()));
    resolvers.add(new RequestHeaderMapMethodArgumentResolver());
    resolvers.add(new ServletCookieValueMethodArgumentResolver(getBeanFactory()));
    resolvers.add(new ExpressionValueMethodArgumentResolver(getBeanFactory()));

    // Type-based argument resolution
    resolvers.add(new ServletRequestMethodArgumentResolver());
    resolvers.add(new ServletResponseMethodArgumentResolver());
    resolvers.add(new HttpEntityMethodProcessor(getMessageConverters()));
    resolvers.add(new RedirectAttributesMethodArgumentResolver());
    resolvers.add(new ModelMethodProcessor());
    resolvers.add(new MapMethodProcessor());
    resolvers.add(new ErrorsMethodArgumentResolver());
    resolvers.add(new SessionStatusMethodArgumentResolver());
    resolvers.add(new UriComponentsBuilderMethodArgumentResolver());

    // Custom arguments
    if (getCustomArgumentResolvers() != null) {
        resolvers.addAll(getCustomArgumentResolvers());
    }

    // Catch-all
    resolvers.add(new RequestParamMethodArgumentResolver(getBeanFactory(), true));
    resolvers.add(new ServletModelAttributeMethodProcessor(true));

    return resolvers;
}

当 Spring 需要调用您的处理程序方法时,它将遍历参数类型和上面的列表并使用第一个supportsParameter().

请注意,ServletModelAttributeMethodProcessor添加了两个实例(一个在//catch all注释之后)。TheModelAttributeMethodProcessor有一个annotationNotRequired字段告诉它是否应该查找@ModelAttribute。第一个实例必须寻找@ModelAttribute,第二个不需要。Spring 这样做是为了让您可以注册自己的HandlerMethodArgumentResolver实例,请参阅// Custom arguments评论。


具体来说

@RequestMapping(value = "/", method = RequestMethod.POST)
public String sayHello(Person person, Model model) {
    model.addAttribute("person", person);
    return "home";
}

在这种情况下,您的Person参数是否被注释并不重要。AModelAttributeMethodProcessor将解决它并绑定表单字段,即。请求参数,到实例的字段。您甚至不需要将其添加到 中,model因为ModelAttributeMethodProcessor该类将处理该问题。

在你的showHelloPage()方法中

model.addAttribute("person", new Person());

<form>taglib需要。这就是它解析其input字段的方式。


所以我的问题是-“ModelAttribute”注释有什么用?

将指定的参数(或方法返回值)自动添加到模型中。

是否有某种方式可以省略表单中的“modelAttribute”属性?

不,form绑定在 中查找对象Model并将其字段绑定到 htmlinput元素。

第二部分,使表单自动将输入值绑定到正确 bean 的属性(将被声明为方法参数)的方式(可能是一些注释)是什么?无需在发送表单之前添加一个空 bean(我现在必须这样做)。

Spring<form>标记锁定到模型属性对象并使用其字段来创建inputlabel元素。只要对象最终出现在模型中,它并不重要。如果它找不到具有您指定的名称(键)的模型属性,它会抛出异常,如您所见。

 <form:form method="post" modelAttribute="person">

提供空 bean 的替代方法是自己创建 html。Spring<form>所做的只是使用 bean 的字段名称来创建一个input元素。所以这

<form:form method="post" modelAttribute="person">
    <form:label path="firstName">First name</form:label>
    <form:input path="firstName" />

创建类似的东西

<form method="post" action="[some action url]">
    <label for="firstName">First name<label>
    <input type="text" name="firstName" value="[whatever value firstName field had]" />
    ...

Spring 使用属性将请求参数绑定到实例字段name

于 2013-09-22T14:12:41.827 回答