Что вызывает "java.lang.IllegalStateException: ни BindingResult, ни обычный целевой объект для bean name" command ", доступный как атрибут запроса"?

Это должно быть обширным каноническим вопросом и ответом на эти вопросы.


Я пытаюсь написать веб-приложение Spring MVC, где пользователи могут добавлять имена фильмов в коллекцию в памяти. Он сконфигурирован так

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 <form> с одним текстовым вводом для имени Movie и кнопки отправки, которую я могу использовать для отправки запроса POST с помощью нового Movie. Почему вместо этого сервлет JSP не может отображать тег Spring <form:form>?

Ответ 1

Вы пытаетесь использовать тег формы Spring MVC.

Этот тег отображает тег form HTML и предоставляет путь привязки к внутренним тегам для привязки. Он помещает объект команды в PageContext так, чтобы к объекту команды можно было обращаться по внутренним тегам. [..]

Допустим, у нас есть объект домена, называемый User. Это JavaBean со свойствами, такими как firstName и lastName. Мы будем использовать его как объект поддержки формы нашего контроллера формы, который возвращает form.jsp.

Другими словами, Spring MVC будет извлекать объект команды и использовать его тип в качестве плана для связывания выражений path для внутренних тегов form, таких как input или checkbox, для визуализации элемента form HTML.

Этот объект команды также называется атрибутом модели и ее имя указано в form тега modelAttribute или commandName атрибутов. Вы опустили его в своем JSP

<form:form> 

Вы могли бы указать имя явно. Оба они эквивалентны.

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

Имя атрибута по умолчанию - это command (что вы видите в сообщении об ошибке). Атрибут model - это объект, обычно POJO или набор POJO, который ваше приложение поставляет в стек Spring MVC и который стек Spring MVC предоставляет вашему представлению (т.е. M к V в MVC).

Spring MVC собирает все атрибуты модели в ModelMap (все они имеют имена), а в случае JSP передает их атрибутам HttpServletRequest, где к ним имеют доступ теги JSP и выражения EL.

В вашем примере ваш @Controller обработчика @Controller который обрабатывает GET для пути /movies добавляет один атрибут модели

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

а затем index.jsp к index.jsp. Затем JSP пытается отобразить

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

Во время рендеринга FormTag (в действительности, InputTag) пытается найти атрибут model с именем command (имя атрибута по умолчанию), чтобы он мог создать элемент HTML <input> с атрибутом name построенным из выражения 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 Spring MVC и добавить его для вас (должен иметь доступный конструктор без параметров)

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

Альтернативно, @ModelAttribute аннотированный метод @Controller в свой класс @Controller

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

Обратите внимание, что Spring MVC вызовет этот метод и неявно добавит объект, возвращенный его атрибутам модели, для каждого запроса, обрабатываемого @Controller.

Возможно, вы уже догадались из этого описания, что тег Spring form больше подходит для рендеринга 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) который добавляет

предоставленный атрибут этой Map с использованием сгенерированного имени.

где общая конвенция

вернуть записать строчное короткое имя [атрибут] Class, в соответствии с JavaBeans собственности правила именования: Так, 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 конфигурации вы вообще пропустите метод @RequestMapping, атрибуты не будут добавлены. Это может случиться

  • если ваш URI-запрос HTTP напрямую обращается к вашим ресурсам JSP, например. потому что они доступны, т.е. вне WEB-INF или
  • если welcome-list вашего web.xml содержит ваш ресурс JSP, контейнер Servlet будет отображать его напрямую, полностью обходя стек Spring MVC

Так или иначе, вы хотите, чтобы ваш @Controller был вызван, чтобы атрибуты модели были добавлены соответствующим образом.

Что BindingResult имеет к этому отношение?

BindingResult - это контейнер для инициализации или проверки атрибутов модели. В документации Spring MVC

Параметры Errors или BindingResult должны следовать объекту модели, который привязан сразу, поскольку подпись метода может иметь более одного объекта модели, а Spring создаст отдельный экземпляр BindingResult для каждого из них [...]

Другими словами, если вы хотите использовать BindingResult, он должен следовать соответствующему параметру атрибута модели в методе @RequestMapping

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

Объекты BindingResult также считаются атрибутами модели. Spring MVC использует простое соглашение об именах для управления ими, что позволяет легко найти соответствующий регулярный атрибут модели. Поскольку BindingResult содержит больше данных об атрибуте модели (например, ошибки проверки), FormTag пытается связать с ним сначала. Однако, поскольку они идут рука об руку, маловероятно, что один будет существовать без другого.

Ответ 2

Чтобы сделать вещи простыми с тегом формы, просто добавьте "commandName", которое является ужасным именем для того, что он действительно ищет... он хочет объект, который вы назвали в аннотации MdelAttribute. Итак, в этом случае commandName = "movie".

Это спасет вас, читая долгое объяснение друзей.

Ответ 3

В моем случае это сработало путем добавления modelAttribute="movie" в тег формы и добавления имени модели к атрибуту, что-то вроде <form:input path="filmName" type="text" id="movie.name"/>

Ответ 4

Ни BindingResult, ни простой целевой объект для имени компонента "команда" не доступны в качестве атрибута запроса

В этой статье мы обсуждаем ни Spring, ни BindingResult, ни простой целевой объект для имени компонента "команда", доступный в качестве атрибута запроса. Это частое исключение в любом веб-проекте Spring mvc.

Давайте посмотрим, когда это исключение происходит с примером:

Класс EmployeeController:

@Controller
@RequestMapping("/employee")
public class EmployeeController {
  @Autowired
  EmployeeRepository employeeRepository;
  @GetMapping(value="/add")
  public String getAddEmployeePage(Model model) {
    model.addAttribute("employee", new Employee());
    return "employees/add";
  }
}

Страница add.jsp:

<form:form action="${updateUrl}" method="post">

Теперь внимательно прочитайте Exception StackTrace.

java.lang.IllegalStateException: Neither BindingResult nor plain target object for bean name 'command' available as request attribute
  at org.springframework.web.servlet.support.BindStatus.<init>(BindStatus.java:153)
  at org.springframework.web.servlet.tags.form.AbstractDataBoundFormElementTag.getBindStatus(AbstractDataBoundFormElementTag.java:173)
  at org.springframework.web.servlet.tags.form.AbstractDataBoundFormElementTag.getPropertyPath(AbstractDataBoundFormElementTag.java:193)
  at org.springframework.web.servlet.tags.form.AbstractDataBoundFormElementTag.getName(AbstractDataBoundFormElementTag.java:159)
  at org.springframework.web.servlet.tags.form.AbstractDataBoundFormElementTag.writeDefaultAttributes(AbstractDataBoundFormElementTag.java:118)
  at org.springframework.web.servlet.tags.form.AbstractHtmlElementTag.writeDefaultAttributes(AbstractHtmlElementTag.java:422)
  at org.springframework.web.servlet.tags.form.InputTag.writeTagContent(InputTag.java:345)
  at org.springframework.web.servlet.tags.form.AbstractFormTag.doStartTagInternal(AbstractFormTag.java:86)
  at org.springframework.web.servlet.tags.RequestContextAwareTag.doStartTag(RequestContextAwareTag.java:83)
  at org.apache.jsp.WEB_002dINF.views.employees.add_jsp._jspx_meth_form_005finput_005f0(add_jsp.java:420)
  at org.apache.jsp.WEB_002dINF.views.employees.add_jsp._jspService(add_jsp.java:240)
  at org.apache.jasper.runtime.HttpJspBase.service(HttpJspBase.java:70)
  at javax.servlet.http.HttpServlet.service(HttpServlet.java:741)
  at org.apache.jasper.servlet.JspServletWrapper.service(JspServletWrapper.java:444)
  at org.apache.jasper.servlet.JspServlet.serviceJspFile(JspServlet.java:386)
  at org.apache.jasper.servlet.JspServlet.service(JspServlet.java:330)
  at javax.servlet.http.HttpServlet.service(HttpServlet.java:741)
  at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:231)
  at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
  at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:53)
  at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
  at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
  at org.apache.catalina.core.ApplicationDispatcher.invoke(ApplicationDispatcher.java:712)
  at org.apache.catalina.core.ApplicationDispatcher.processRequest(ApplicationDispatcher.java:459)
  at org.apache.catalina.core.ApplicationDispatcher.doForward(ApplicationDispatcher.java:384)
  at org.apache.catalina.core.ApplicationDispatcher.forward(ApplicationDispatcher.java:312)
  at org.springframework.web.servlet.view.InternalResourceView.renderMergedOutputModel(InternalResourceView.java:170)
  at org.springframework.web.servlet.view.AbstractView.render(AbstractView.java:314)
  at org.springframework.web.servlet.DispatcherServlet.render(DispatcherServlet.java:1325)
  at org.springframework.web.servlet.DispatcherServlet.processDispatchResult(DispatcherServlet.java:1069)
  at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1008)
  at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:925)
  at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:974)
  at org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:866)
  at javax.servlet.http.HttpServlet.service(HttpServlet.java:634)
  at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:851)
  at javax.servlet.http.HttpServlet.service(HttpServlet.java:741)
  at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:231)
  at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
  at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:53)
  at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
  at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
  at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:199)
  at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:96)
  at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:494)
  at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:137)
  at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:92)
  at org.apache.catalina.valves.AbstractAccessLogValve.invoke(AbstractAccessLogValve.java:651)
  at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:87)
  at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:343)
  at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:407)
  at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:66)
  at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:754)
  at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1376)
  at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49)
  at java.util.concurrent.ThreadPoolExecutor.runWorker(Unknown Source)
  at java.util.concurrent.ThreadPoolExecutor$Worker.run(Unknown Source)
  at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)

Мы проиллюстрируем возможные причины этой проблемы и доступные решения.

Причина исключения: java.lang.IllegalStateException: ни BindingResult, ни простой целевой объект для имени компонента "команда" не доступны в качестве атрибута запроса.

Самая частая причина этого исключения заключается в том, что мы не связывали наш объект имени команды в jsp с тегом form: form.

Как мы видим из приведенного выше класса EmployeeController, у нас есть один метод getAddEmployeePage (Model model), в котором мы связали данные employee в объекте модели с ключевым именем employee, а затем вызываем страницу add.jsp. на java.lang.Thread.run (неизвестный источник)

model.addAttribute("employee", new Employee());
return "employees/add";
<form:form action="${updateUrl}" method="post">

В приведенном выше теге "form: form" мы не взяли ни одного атрибута "commandName" или "modelAttribute", и именно об этом говорится в трассировке стека исключений, что он не может найти какой-либо атрибут команды на странице add.jsp.

Решение: Если мы изменим тег form: form с помощью приведенного ниже кода, он будет работать правильно.

<form:form action="${updateUrl}" method="post" modelAttribute="employee">

Если мы запустим этот код после внесения определенных изменений, тогда наш проект будет работать без каких-либо исключений. Нажмите на ссылку, чтобы получить дополнительную информацию: https://www.solutionfactory.in/posts/Neither-BindingResult-nor-plain-target-object-for-bean-name-command-available-as-request-attribute

Ответ 5

У меня была эта ошибка на экране с несколькими формами, которые выполняют поиск. Каждая форма отправляет свой собственный метод контроллера с результатами, отображаемыми на том же экране.

Проблема: я пропустил добавление двух других форм в качестве атрибутов модели в каждом методе контроллера, что вызывало эту ошибку при отображении экрана с результатами.

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

Ответ 6

Добавьте атрибут модели (название модели, которую вы указали в контроллере), чтобы связать вашу форму. Вы должны добавить следующие атрибуты в свою форму

<form:form modelAttribute="movies" method="post">

Ответ 7

Обновление с Spring версии 3 до версии Spring версии 5 приводит к той же ошибке. Все ответы были удовлетворены уже в моем коде. Добавление аннотации @ControllerAdvice решило проблему для меня.