是啥导致“java.lang.IllegalStateException:Bean 名称‘command’的 BindingResult 和普通目标对象都不能用作请求属性”?

Posted

技术标签:

【中文标题】是啥导致“java.lang.IllegalStateException:Bean 名称‘command’的 BindingResult 和普通目标对象都不能用作请求属性”?【英文标题】:What causes "java.lang.IllegalStateException: Neither BindingResult nor plain target object for bean name 'command' available as request attribute"?是什么导致“java.lang.IllegalStateException:Bean 名称‘command’的 BindingResult 和普通目标对象都不能用作请求属性”? 【发布时间】:2017-07-01 02:01:03 【问题描述】:

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


我正在尝试编写一个 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[]  "/" ;
    

@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;
    

com.example 包中有一个 @Controller

@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.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>

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

发送 GET 请求时
http://localhost:8080/Example/movies

请求失败,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)

我希望 JSP 生成带有单个文本输入的 HTML &lt;form&gt;Movie 名称和提交按钮,我可以使用它来发送带有新 Movie 的 POST 请求。为什么 JSP servlet 反而无法渲染 Spring 的 &lt;form:form&gt; 标签?

【问题讨论】:

如果你有 ModelAttribute all setup 那么你可能会遇到同样的问题,然后在不手动添加属性的情况下调用 model.clear() 是很愚蠢的。在我的情况下,我在异常消息中说我的 ModelAttribute 的名称而不是“命令”。 【参考方案1】:

您正在尝试使用Spring MVC's form tag。

这个标签呈现一个 HTML form 标签并暴露一个绑定路径到 用于绑定的内部标签。它将命令对象放在PageContext 以便命令对象可以通过内部标签访问。 [..]

假设我们有一个名为User 的域对象。它是一个 JavaBean 具有firstNamelastName 等属性。我们将使用它作为 我们的表单控制器的表单支持对象,它返回form.jsp

换句话说,Spring MVC 将提取一个命令对象,并使用它的类型作为蓝图,为form 的内部标签绑定path 表达式,如input 或@987654323 @,渲染 HTML form 元素。

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

<form:form> 

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

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

注意:Spring 5 已删除 commandName 属性,请参阅升级说明,here。

default attribute name is command(您在错误消息中看到的内容)。模型属性是一个对象,通常是 POJO 或 POJO 集合,您的应用程序提供给 Spring MVC 堆栈并且 Spring MVC 堆栈向您的视图公开(即 MVC 中的 M 到 V)。

Spring MVC 在 ModelMap 中收集所有模型属性(它们都有名称),并且在 JSP 的情况下,将它们传输到 HttpServletRequest 属性,其中 JSP 标记和 EL 表达式可以访问它们。

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

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

然后转发到index.jsp。然后这个 JSP 尝试渲染

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

在渲染这个时,FormTag(实际上是InputTag)尝试找到一个名为command(默认属性名称)的模型属性,以便它可以生成带有@987654365 的HTML &lt;input&gt; 元素@ 属性由path 表达式和相应的属性值构成,即。 Movie#getFilmName() 的结果。

既然找不到,就抛出你看到的异常

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

JSP 引擎捕获它并以 500 状态代码响应。如果您想利用Movie POJO 来简单地正确构建您的表单,您可以使用

显式添加模型属性
model.addAttribute("movie", new Movie());

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

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

或者,在 @Controller 类中包含 @ModelAttribute 注释方法

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

请注意,Spring MVC 将调用此方法,并为封闭的@Controller 处理的每个请求隐式地将返回的对象添加到其模型属性中。

您可能已经从这个描述中猜到,Spring 的form 标记更适合从现有对象呈现HTML &lt;form&gt;,并带有实际值。如果你想简单地创建一个空白&lt;form&gt;,可能更适合自己构建,不依赖任何模型属性。

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

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

常见错误

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

ModelMap 有一个 addAttribute(Object) 方法添加

使用 generated 名称为该 Map 提供的属性。

一般约定的地方

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

如果您正在使用此(或类似)方法,或者您正在使用代表模型属性的@RequestMapping supported return types 之一,请确保生成的名称符合您的预期。

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

    发送 HTTP GET 请求 DispatcherServlet选择@RequestMapping方法处理请求 Handler 方法生成一些模型属性并返回视图名称 DispatcherServletHttpServletRequest添加模型属性,将请求转发给视图名对应的JSP JSP 呈现响应

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

如果您的 HTTP 请求 URI 直接访问您的 JSP 资源,例如。因为它们是可访问的,即。在WEB-INF 之外,或 如果您的web.xmlwelcome-list 包含您的JSP 资源,则Servlet 容器将直接呈现它,完全绕过Spring MVC 堆栈

您希望以某种方式调用您的@Controller,以便适当地添加模型属性。

BindingResult 和这个有什么关系?

BindingResult 是用于初始化或验证模型属性的容器。 Spring MVC documentation 状态

ErrorsBindingResult 参数必须遵循模型对象 立即绑定,因为方法签名可能有 多个模型对象,Spring 将创建一个单独的模型对象 BindingResult 他们每个人的实例 [...]

也就是说,如果要使用BindingResult,则必须在@RequestMapping方法中遵循相应的模型属性参数

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

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

【讨论】:

我遇到了错误,即使我在 FormTag 中正确设置了 MethodAttribute,并且在控制器中为 GET 端点和 POST 端点设置了正确的方法。在 POST 上,我遇到了同样的错误消息,但事实证明这是一种自我伤害。我在模型上调用了 clear() ,它删除了响应重定向到相同表单时预期的属性。我删除了对 clear() 的调用以解决问题。 我在重构一个 SpringBoot 应用程序后遇到了这个错误,结果是由于视图模板中引用的某些类意外地超出了隐式 @ComponentScan 的范围。【参考方案2】:

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

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

【讨论】:

【参考方案3】:

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

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

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";

【讨论】:

【参考方案4】:

在我的例子中,它通过将modelAttribute="movie" 添加到表单标签,并将模型名称添加到属性中,例如&lt;form:input path="filmName" type="text" id="movie.name" /&gt;

【讨论】:

【参考方案5】:

我尝试将我的应用程序迁移到 Spring5 并注意到同样的问题。这是由于不再支持“commandName”属性而导致我不得不改用“modelAttribute”。

【讨论】:

【参考方案6】:

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

【讨论】:

以上是关于是啥导致“java.lang.IllegalStateException:Bean 名称‘command’的 BindingResult 和普通目标对象都不能用作请求属性”?的主要内容,如果未能解决你的问题,请参考以下文章

使用多租户时的 Spring Boot 范围问题

是啥导致 HttpHostConnectException?

是啥导致此 BadDeviceToken 响应?

是啥导致了这个推进错误?

是啥导致了 NSInvalidArgumentException?

是啥导致移动构造函数被删除