什么原因导致“java.lang.IllegalStateException:对于 bean 名称‘command’既没有 BindingResult 也没有普通目标对象作为请求属性可用”?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/42198050/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-11-03 06:26:10  来源:igfitidea点击:

What causes "java.lang.IllegalStateException: Neither BindingResult nor plain target object for bean name 'command' available as request attribute"?

javaspringjspspring-mvc

提问by Sotirios Delimanolis

This is meant to be an extensive canonical question & answer post for these types of questions.

这是针对这些类型问题的广泛规范问答帖子。



I'm trying to write a Spring MVC web application where users can add movie names to an in-memory collection. It's configured like so

我正在尝试编写一个 Spring MVC Web 应用程序,用户可以在其中将电影名称添加到内存中的集合中。它是这样配置的

public class Application extends AbstractAnnotationConfigDispatcherServletInitializer {
    protected Class<?>[] getRootConfigClasses() {
        return new Class<?>[] {};
    }
    protected Class<?>[] getServletConfigClasses() {
        return new Class<?>[] { SpringServletConfig.class };
    }
    protected String[] getServletMappings() {
        return new String[] { "/" };
    }
}

and

@Configuration
@ComponentScan("com.example")
public class SpringServletConfig extends WebMvcConfigurationSupport {
    @Bean
    public InternalResourceViewResolver resolver() {
        InternalResourceViewResolver vr = new InternalResourceViewResolver();
        vr.setPrefix("WEB-INF/jsps/");
        vr.setSuffix(".jsp");
        return vr;
    }
}

There's a single @Controllerclass in the com.examplepackage

有一个单一@Controller类在com.example

@Controller
public class MovieController {
    private final CopyOnWriteArrayList<Movie> movies = new CopyOnWriteArrayList<>();
    @RequestMapping(path = "/movies", method = RequestMethod.GET)
    public String homePage(Model model) {
        model.addAttribute("movies", movies);
        return "index";
    }
    @RequestMapping(path = "/movies", method = RequestMethod.POST)
    public String upload(@ModelAttribute("movie") Movie movie, BindingResult errors) {
        if (!errors.hasErrors()) {
            movies.add(movie);
        }
        return "redirect:/movies";
    }
    public static class Movie {
        private String filmName;
        public String getFilmName() {
            return filmName;
        }
        public void setFilmName(String filmName) {
            this.filmName = filmName;
        }
    }
}

WEB-INF/jsps/index.jspcontains

WEB-INF/jsps/index.jsp包含

<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form"%>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Movies</title>
</head>
<body>
    Current Movies:
    <c:forEach items="${movies}" var="movieItem">
        <ul>
            <li>${movieItem.filmName}</li>
        </ul>
    </c:forEach>
    <form:form>
        <div>Movie name:</div>
        <form:input path="filmName" type="text" id="name" />
        <input type="submit" value="Upload">
    </form:form>
</body>
</html>

The application is configured with context path /Example. When I send a GET request to

应用程序配置了上下文路径/Example。当我向

http://localhost:8080/Example/movies

the request fails, Spring MVC responds with a 500 status code, and reports the following exception and stack trace

请求失败,Spring MVC 响应 500 状态码,并报告以下异常和堆栈跟踪

java.lang.IllegalStateException: Neither BindingResult nor plain target object for bean name 'command' available as request attribute
    org.springframework.web.servlet.support.BindStatus.<init>(BindStatus.java:144)
    org.springframework.web.servlet.tags.form.AbstractDataBoundFormElementTag.getBindStatus(AbstractDataBoundFormElementTag.java:168)
    org.springframework.web.servlet.tags.form.AbstractDataBoundFormElementTag.getPropertyPath(AbstractDataBoundFormElementTag.java:188)
    org.springframework.web.servlet.tags.form.AbstractDataBoundFormElementTag.getName(AbstractDataBoundFormElementTag.java:154)
    org.springframework.web.servlet.tags.form.AbstractDataBoundFormElementTag.writeDefaultAttributes(AbstractDataBoundFormElementTag.java:117)
    org.springframework.web.servlet.tags.form.AbstractHtmlElementTag.writeDefaultAttributes(AbstractHtmlElementTag.java:422)
    org.springframework.web.servlet.tags.form.InputTag.writeTagContent(InputTag.java:142)
    org.springframework.web.servlet.tags.form.AbstractFormTag.doStartTagInternal(AbstractFormTag.java:84)
    org.springframework.web.servlet.tags.RequestContextAwareTag.doStartTag(RequestContextAwareTag.java:80)
    org.apache.jsp.WEB_002dINF.jsps.index_jsp._jspx_meth_form_005finput_005f0(index_jsp.java:267)
    org.apache.jsp.WEB_002dINF.jsps.index_jsp._jspx_meth_form_005fform_005f0(index_jsp.java:227)
    org.apache.jsp.WEB_002dINF.jsps.index_jsp._jspService(index_jsp.java:142)
    org.apache.jasper.runtime.HttpJspBase.service(HttpJspBase.java:70)
    javax.servlet.http.HttpServlet.service(HttpServlet.java:729)
    org.apache.jasper.servlet.JspServletWrapper.service(JspServletWrapper.java:438)
    org.apache.jasper.servlet.JspServlet.serviceJspFile(JspServlet.java:396)
    org.apache.jasper.servlet.JspServlet.service(JspServlet.java:340)
    javax.servlet.http.HttpServlet.service(HttpServlet.java:729)
    org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:52)
    org.springframework.web.servlet.view.InternalResourceView.renderMergedOutputModel(InternalResourceView.java:168)
    org.springframework.web.servlet.view.AbstractView.render(AbstractView.java:303)
    org.springframework.web.servlet.DispatcherServlet.render(DispatcherServlet.java:1257)
    org.springframework.web.servlet.DispatcherServlet.processDispatchResult(DispatcherServlet.java:1037)
    org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:980)
    org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:897)
    org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:970)
    org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:861)
    javax.servlet.http.HttpServlet.service(HttpServlet.java:622)
    org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:846)
    javax.servlet.http.HttpServlet.service(HttpServlet.java:729)
    org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:52)

I expected the JSP to generate an HTML <form>with a single text input, for a Moviename, and a submit button, that I can use to send a POST request with a new Movie. Why does the JSP servlet instead fail to render Spring's <form:form>tag?

我希望 JSP 生成一个<form>带有单个文本输入、Movie名称和提交按钮的 HTML ,我可以用它来发送带有新 .html 文件的 POST 请求Movie。为什么 JSP servlet 反而无法呈现 Spring 的<form:form>标记?

回答by Sotirios Delimanolis

You're trying to use Spring MVC's form tag.

您正在尝试使用Spring MVC 的表单标签

This tag renders an HTML formtag and exposes a binding path to inner tags for binding. It puts the command object in the PageContextso that the command object can be accessed by inner tags. [..]

Let's assume we have a domain object called User. It is a JavaBean with properties such as firstNameand lastName. We will use it as the form backing objectof our form controller which returns form.jsp.

该标签呈现一个 HTMLform标签并公开一个到内部标签的绑定路径以进行绑定。它将命令对象放在 中,PageContext以便内部标签可以访问命令对象。[..]

假设我们有一个名为 的域对象User。它与诸如属性一个JavaBeanfirstNamelastName。我们将使用它作为我们的表单控制器的 表单支持对象,它返回form.jsp.

In other words, Spring MVC will extract a command objectand use its type as a blueprint for binding pathexpressions for form's inner tags, like inputor checkbox, to render an HTML formelement.

换句话说,Spring MVC 将提取一个命令对象,并使用它的类型作为蓝图来绑定内部标记的path表达式form,例如inputcheckbox,以呈现 HTMLform元素。

This command objectis also called a model attribute and its name is specified in the formtag's modelAttributeor commandNameattributes. You've omitted it in your JSP

命令对象也称为模型属性,其名称在form标签的modelAttributecommandName属性中指定。你在你的 JSP 中省略了它

<form:form> 

You could've specified a name explicitly. Both of these are equivalent.

您可以明确指定一个名称。这两者是等价的。

<form:form modelAttribute="some-example-name">
<form:form commandName="some-example-name">

The default attribute name is command(what you see in error message). A model attribute is an object, typically a POJO or collection of POJOs, that your application supplies to the Spring MVC stack and which the Spring MVC stack exposes to your view (ie. the M to the V in MVC).

默认属性名称是command(你在错误信息看)。模型属性是一个对象,通常是一个 POJO 或 POJO 的集合,您的应用程序将其提供给 Spring MVC 堆栈,并且 Spring MVC 堆栈将其公开给您的视图(即 MVC 中的 M 到 V)。

Spring MVC collects all model attributes in a ModelMap(they all have names) and, in the case of JSPs, transfers them to the HttpServletRequestattributes, where JSP tags and EL expressions have access to them.

Spring MVC 收集所有模型属性ModelMap(它们都有名称),对于 JSP,将它们传输到HttpServletRequest属性,在那里 JSP 标记和 EL 表达式可以访问它们。

In your example, your @Controllerhandler method which handles a GETto the path /moviesadds a single model attribute

在您的示例中,@Controller处理GET路径的处理程序方法/movies添加了一个模型属性

model.addAttribute("movies", movies); // not named 'command'

and then forwards to the index.jsp. This JSP then tries to render

然后转发到index.jsp. 这个 JSP 然后尝试呈现

<form:form>
    ...
    <form:input path="name" type="text" id="name" />
    ...
</form:form>

While rendering this, FormTag(in reality, the InputTag) tries to find a model attribute named command(the default attribute name) so that it can produce an HTML <input>element with a nameattribute constructed from the pathexpression and the corresponding property value, ie. the result of Movie#getFilmName().

在渲染这个时,FormTag(实际上,InputTag)试图找到一个名为command(默认属性名称)的模型属性,以便它可以生成一个 HTML<input>元素,该元素具有namepath表达式和相应的属性值构造的属性,即。的结果Movie#getFilmName()

Since it cannot find it, it throws the exception you see

由于找不到它,它会抛出您看到的异常

java.lang.IllegalStateException: Neither BindingResult nor plain target object for bean name 'command' available as request attribute

The JSP engine catches it and responds with a 500 status code. If you want to take advantage of a MoviePOJO to simply construct your form correctly, you can add a model attribute explicitly with

JSP 引擎捕获它并以 500 状态代码响应。如果您想利用MoviePOJO 来简单地正确构建您的表单,您可以使用以下命令显式添加模型属性

model.addAttribute("movie", new Movie());

or have Spring MVC create and add one for you (must have an accessible parameterless constructor)

或者让 Spring MVC 为你创建并添加一个(必须有一个可访问的无参数构造函数)

@RequestMapping(path = "/movies", method = RequestMethod.GET)
public String homePage(@ModelAttribute("command") Movie movie, Model model) {...}

Alternatively, include a @ModelAttributeannotated method in your @Controllerclass

或者,@ModelAttribute在您的@Controller类中包含一个带注释的方法

@ModelAttribute("command")
public Movie defaultInstance() {
    Movie movie = new Movie();
    movie.setFilmName("Rocky II");
    return movie;
}

Note that Spring MVC will call this method and implicitly add the object returned to its model attributes for each request handled by the enclosing @Controller.

请注意,Spring MVC 将调用此方法,并将返回的对象隐式添加到由封闭@Controller.

You may have guessed from this description that Spring's formtag is more suited for rendering an HTML <form>from an existing object, with actual values. If you want to simply create a blank <form>, it may be more appropriate to construct it yourself and not rely on any model attributes.

您可能已经从这个描述中猜到,Spring 的form标签更适合<form>从现有对象呈现带有实际值的 HTML 。如果你想简单地创建一个 blank <form>,自己构建它可能更合适,而不依赖于任何模型属性。

<form method="post" action="${pageContext.request.contextPath}/movies">
    <input name="filmName" type="text" />
    <input type="submit" value="Upload" />
</form>

On the receiving side, your POSThandler method, will still be able to extract the filmNameinput value and use it to initialize a Movieobject.

在接收方,您的POST处理程序方法仍将能够提取filmName输入值并使用它来初始化Movie对象。

Common Errors

常见错误

As we've seen, FormTaglooks for a model attribute named commandby default or with the name specified in either modelAttributeor commandName. Make sure you're using the right name.

正如我们所见,FormTag查找command默认命名的模型属性或在modelAttribute或 中指定的名称commandName。确保您使用正确的名称。

ModelMaphas a addAttribute(Object)method which adds

ModelMap有一种addAttribute(Object)方法可以添加

the supplied attribute to this Mapusing a generatedname.

Map使用生成的名称为此提供的属性。

where the general convention is to

一般约定在哪里

return the uncapitalized short name of the [attribute's] Class, according to JavaBeans property naming rules: So, com.myapp.Productbecomes product; com.myapp.MyProductbecomes myProduct; com.myapp.UKProductbecomes UKProduct

Class根据 JavaBeans 属性命名规则,返回 [attribute's] 的非大写短名称: 所以,com.myapp.Product变成 product; com.myapp.MyProduct变成myProductcom.myapp.UKProduct变成UKProduct

If you're using this (or a similar) method or if you're using one of the @RequestMappingsupported return typesthat represents a model attribute, make sure the generated name is what you expect.

如果您正在使用此(或类似)方法,或者您正在使用代表模型属性的@RequestMapping受支持返回类型之一,请确保生成的名称是您所期望的。

Another common error is to bypass your @Controllermethod altogether. A typical Spring MVC application follows this pattern:

另一个常见错误是@Controller完全绕过您的方法。典型的 Spring MVC 应用程序遵循以下模式:

  1. Send HTTP GET request
  2. DispatcherServletselects @RequestMappingmethod to handle request
  3. Handler method generates some model attributes and returns view name
  4. DispatcherServletadds model attributes to HttpServletRequestand forwards request to JSP corresponding to view name
  5. JSP renders response
  1. 发送 HTTP GET 请求
  2. DispatcherServlet选择@RequestMapping处理请求的方法
  3. Handler 方法生成一些模型属性并返回视图名称
  4. DispatcherServlet添加模型属性HttpServletRequest并将请求转发到与视图名称对应的 JSP
  5. JSP 呈现响应

If, by some misconfiguration, you skip the @RequestMappingmethod altogether, the attributes will not have been added. This can happen

如果由于某些错误配置,您@RequestMapping完全跳过该方法,则不会添加属性。这可能发生

  • if your HTTP request URI accesses your JSP resources directly, eg. because they are accessible, ie. outside WEB-INF, or
  • if the welcome-listof your web.xmlcontains your JSP resource, the Servlet container will render it directly, bypassing the Spring MVC stack entirely
  • 如果您的 HTTP 请求 URI 直接访问您的 JSP 资源,例如。因为它们是可访问的,即。外面WEB-INF, 或
  • 如果welcome-list您的web.xml包含您的 JSP 资源,则 Servlet 容器将直接呈现它,完全绕过 Spring MVC 堆栈

One way or another, you want your @Controllerto be invoked so that the model attributes are added appropriately.

以一种或另一种方式,您希望您@Controller被调用,以便适当地添加模型属性。

What does BindingResulthave to do with this?

什么是BindingResult与此有关?

A BindingResultis a container for initialization or validation of model attributes. The Spring MVC documentationstates

ABindingResult是用于初始化或验证模型属性的容器。在Spring MVC的文档状态

The Errorsor BindingResultparameters have to follow the model object that is being bound immediately as the method signature might have more than one model object and Spring will create a separate BindingResultinstance for each of them [...]

ErrorsBindingResult参数必须遵循被立即绑定方法签名可能有不止一个模型对象和Spring将创建一个单独的模型对象 BindingResult为他们每个人的情况下[...]

In other words, if you want to use BindingResult, it has to follow the corresponding model attribute parameter in a @RequestMappingmethod

换句话说,如果要使用BindingResult,必须在@RequestMapping方法中跟随对应的模型属性参数

@RequestMapping(path = "/movies", method = RequestMethod.POST)
public String upload(@ModelAttribute("movie") Movie movie, BindingResult errors) {

BindingResultobjects are also considered model attributes. Spring MVC uses a simple naming convention to manage them, making it easy to find a corresponding regular model attribute. Since the BindingResultcontains more data about the model attribute (eg. validation errors), the FormTagattempts to bind to it first. However, since they go hand in hand, it's unlikely one will exist without the other.

BindingResult对象也被视为模型属性。Spring MVC 使用简单的命名约定来管理它们,从而可以轻松找到相应的常规模型属性。由于BindingResult包含有关模型属性的更多数据(例如验证错误),FormTag因此首先尝试绑定到它。然而,由于它们齐头并进,一个没有另一个就不太可能存在。

回答by Dotard

I had this error on a screen with multiple forms that do a search. Each form posts to its own controller method with results shown on same screen.

我在具有多个搜索表单的屏幕上出现此错误。每个表单发布到它自己的控制器方法,结果显示在同一屏幕上。

Problem: I missed adding the other two forms as model attributes in each controller method causing that error when screen renders with results.

问题:我错过了在每个控制器方法中添加其他两个表单作为模型属性,当屏幕呈现结果时导致该错误。

Form1 -> bound to Bean1 (bean1) -> Posting to /action1
Form2 -> bound to Bean2 (bean2) -> Posting to /action2
Form3 -> bound to Bean3 (bean2) -> Posting to /action3
@PostMapping
public String blah(@ModelAttribute("bean1") Bean1 bean, Model model){
// do something with bean object

// do not miss adding other 2 beans as model attributes like below. 
model.addAttribute("bean2", new Bean2()); 
model.addAttribute("bean3", new Bean3());
return "screen";
}

@PostMapping
public String blahBlah(@ModelAttribute("bean2") Bean2 bean, Model model){
// do something with bean object
// do not miss adding other 2 beans as model attributes like below. 
model.addAttribute("bean1", new Bean1()); 
model.addAttribute("bean3", new Bean3());
return "screen";
}

@PostMapping
public String blahBlahBlah(@ModelAttribute("bean3") Bean3 bean, Model model){
// do something with bean object
// do not miss adding other 2 beans as model attributes like below. 
model.addAttribute("bean1", new Bean1()); 
model.addAttribute("bean2", new Bean2());
return "screen";
}

回答by Pasha Utt

To make things simple with the form tag just add a "commandName" which is a horrible name for what it is actually looking for...it wants the object you named in the MdelAttribute annotation. So in this case commandName="movie".

为了使表单标签变得简单,只需添加一个“commandName”,这对于它实际要查找的内容来说是一个可怕的名称……它需要您在 MdelAttribute 注释中命名的对象。所以在这种情况下,commandName="movie"。

That'll save you reading long winded explanations friend.

这将节省您阅读冗长的解释的朋友。

回答by Pedro Madrid

In my case, it worked by adding modelAttribute="movie"to the form tag, and prepending the model name to the attribute, something like <form:input path="filmName" type="text" id="movie.name" />

在我的情况下,它通过添加modelAttribute="movie"到表单标签,并将模型名称添加到属性中来工作,例如<form:input path="filmName" type="text" id="movie.name" />

回答by shaqer74

Updating from Spring version 3 to Spring version 5, produces the same error. All answers were satisfied already in my code. Adding the annotation @ControllerAdvicesolved the problem for me.

从 Spring 版本 3 更新到 Spring 版本 5,会产生相同的错误。我的代码中已经满足了所有答案。添加注释@ControllerAdvice为我解决了问题。