是什么原因导致“java.lang.IllegalStateException:无论是BiningResult还是Bean名称'command'的普通目标对象都不能用作请求属性”?

2022-09-01 06:49:57

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


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

包中有一个类@Controllercom.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.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>

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

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,其中包含单个文本输入,用于名称和提交按钮,我可以使用它来发送带有新.为什么 JSP servlet 无法呈现 Spring 的标签?<form>MovieMovie<form:form>


答案 1

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

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

假设我们有一个名为 的域对象。它是一个 JavaBean,具有诸如 和 之类的属性。我们将使用它作为表单控制器的表单支持对象,该对象返回 。UserfirstNamelastNameform.jsp

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

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

<form:form> 

您可以显式指定名称。这两者是等效的。

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

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

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

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

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

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

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

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

在呈现此属性时,FormTag(实际上是 InputTag)尝试查找名为(默认属性名称)的模型属性,以便它可以生成一个 HTML 元素,其中包含从表达式构造的属性和相应的属性值,即。的结果。command<input>namepathMovie#getFilmName()

由于它找不到它,它会引发您看到的异常

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

JSP 引擎捕获它并使用 500 状态代码进行响应。如果要利用 POJO 来正确构造窗体,可以使用Movie

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

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

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

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

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

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

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

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

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

常见错误

正如我们所看到的,查找默认命名的模型属性,或者使用 or 中指定的名称命名的模型属性。确保您使用的是正确的名称。FormTagcommandmodelAttributecommandName

ModelMap有一个 addAttribute(Object) 方法,该方法可以

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

一般惯例是

根据 JavaBeans 属性命名规则,返回 [属性的] 的未大写短名称:因此,变为 ; 变为 ; 成为Classcom.myapp.Productproductcom.myapp.MyProductmyProductcom.myapp.UKProductUKProduct

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

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

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

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

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

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

这有什么关系?BindingResult

绑定结果是用于初始化或验证模型属性的容器。春季MVC文档指出

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

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

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

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


答案 2

我试图将我的应用程序迁移到Spring5,并注意到同样的问题。这是由于不再支持“commandName”属性的那一刻引起的,我不得不使用“modelAttribute”来代替。