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

这是一个广泛的规范问题&回答这些类型的问题.

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

应用程序配置了上下文路径/示例.当我发送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< form>使用单个文本输入,电影名称和提交按钮,我可以用它来发送带有新电影的POST请求.为什么JSP servlet无法呈现Spring的< form:form>标签?

最佳答案
你正试图使用​​Spring MVC’s form tag.

This tag renders an HTML form tag and exposes a binding path to
inner tags for binding. It puts the command object in the PageContext
so 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 firstName and lastName. We will use it as the
form backing object of our form controller which returns form.jsp.

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

此命令对象也称为模型属性,其名称在表单标记的modelAttribute或commandName属性中指定.你在JSP中省略了它

<form:form> 

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

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

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

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

在您的示例中,处理路径/影片GET的@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(默认属性名称)的模型属性,以便它可以生成HTML< input>具有根据路径表达式和相应属性值构造的name属性的元素,即. 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的表单标记更适合呈现HTML< form>来自现有对象,具有实际值.如果您只想创建一个空白< form>,那么自己构建它并不依赖于任何模型属性可能更合适.

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

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

常见错误

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

ModelMap有一个addAttribute(Object)方法,它增加了

the supplied attribute to this Map using a 07009 name.

一般惯例的地方

return the uncapitalized short name of the [attribute’s] Class, according to
JavaBeans property naming rules: So, com.myapp.Product becomes
product; com.myapp.MyProduct becomes myProduct; com.myapp.UKProduct
becomes UKProduct

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

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

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

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

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

不管怎样,您希望调用@Controller,以便适当地添加模型属性.

BindingResult与此有什么关系?

BindingResult是用于初始化或验证模型属性的容器. Spring MVC documentation声明

The Errors or BindingResult parameters 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
BindingResult instance for each of them […]

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

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

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

转载注明原文:是什么导致“java.lang.IllegalStateException:BindingResult和bean name’命令的普通目标对象’都不可用作请求属性”? - 代码日志