是什么导致“java.lang.IllegalStateException:BindingResult和bean name’命令的普通目标对象’都不可用作请求属性”?

对于这些类型的问题,这是一个广泛的规范问答post。


我正在尝试编写一个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 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包含

      Movies   Current Movies:  
  • ${movieItem.filmName}
Movie name:

应用程序配置了上下文路径/Example 。 当我发送GET请求时

 http://localhost:8080/Example/movies 

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

 java.lang.IllegalStateException: Neither BindingResult nor plain target object for bean name 'command' available as request attribute org.springframework.web.servlet.support.BindStatus.(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

,一个Movie名称和一个提交按钮,我可以用它来发送一个带有新Movie的POST请求。 为什么JSP servlet无法呈现Spring的

标记?

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

此标记呈现HTML form标记,并公开内部标记的绑定路径以进行绑定。 它将命令对象放在PageContext以便内部标记可以访问命令对象。 [..]

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

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

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

  

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

   

默认属性名称是command (您在错误消息中看到的内容)。 模型属性是一个对象,通常是POJO或POJO集合,应用程序提供给Spring MVC堆栈,Spring MVC堆栈暴露给您的视图(即MVC中的M到V)。

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

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

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

然后转发到index.jsp 。 然后,此JSP尝试呈现

  ...  ...  

渲染时, FormTag (实际上是InputTag )尝试查找名为command的模型属性(默认属性名称),以便它可以生成一个HTML 元素,其中name属性由path表达式和相应的属性构成价值,即。 Movie#getFilmName()的结果。

由于它无法找到它,它会抛出您看到的exception

 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

。 如果您只想创建一个空白

,可能更适合自己构建它而不依赖于任何模型属性。

 

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

常见错误

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

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

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

一般惯例所在的地方

根据JavaBeans属性命名规则返回[attribute’s] Class的非大写短名称:因此, com.myapp.Product成为product ; com.myapp.MyProduct成为myProduct ; com.myapp.UKProduct成为UKProduct

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

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

  1. 发送HTTP GET请求
  2. DispatcherServlet选择@RequestMapping方法来处理请求
  3. Handler方法生成一些模型属性并返回视图名称
  4. DispatcherServlet将模型属性添加到HttpServletRequest ,并将请求转发到与视图名称对应的JSP
  5. JSP呈现响应

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

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

无论如何,您希望调用@Controller以便适当地添加模型属性。

BindingResult与此有什么关系?

BindingResult是用于初始化或validation模型属性的容器。 Spring MVC文档指出

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

换句话说,如果你想使用BindingResult ,它必须遵循@RequestMapping方法中相应的模型属性参数

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

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

要使用form标签简单,只需添加一个“commandName”,它实际上是一个可怕的名称…它想要你在MdelAttribute注释中命名的对象。 所以在这种情况下commandName =“movie”。

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

在我的例子中,它通过将modelAttribute="movie"添加到表单标记,并将模型名称添加到属性,类似于

添加模型属性(您在控制器中指定的模型的名称)以关联您的表单。 您必须向表单添加以下属性