¿Qué causa “java.lang.IllegalStateException: ni BindingResult ni objeto de destino simple para el nombre de bean ‘comando’ disponible como atributo de solicitud”?

Esto pretende ser una extensa publicación canónica de preguntas y respuestas para este tipo de preguntas.


Intento escribir una aplicación web Spring MVC donde los usuarios pueden agregar nombres de películas a una colección en memoria. Está configurado como tal

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[] { "/" }; } } 

y

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

Hay una sola clase @Controller en el paquete com.example

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

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

La aplicación está configurada con la ruta de contexto /Example . Cuando envío una solicitud GET a

 http://localhost:8080/Example/movies 

la solicitud falla, Spring MVC responde con un código de estado de 500 e informa la siguiente excepción y el seguimiento de la stack

 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) 

Esperaba que el JSP generara un

HTML con una sola entrada de texto, para un nombre de Movie , y un botón de enviar, que puedo usar para enviar una solicitud POST con una Movie nueva. ¿Por qué el servlet JSP en cambio no puede representar la etiqueta

Spring?

Está intentando usar la etiqueta de formulario de Spring MVC .

Esta etiqueta representa una etiqueta de form HTML y expone una ruta de enlace a las tags internas para el enlace. Pone el objeto de comando en el PageContext para que se pueda acceder al objeto de comando mediante tags internas. [..]

Supongamos que tenemos un objeto de dominio llamado User . Es un JavaBean con propiedades como firstName y lastName . Lo usaremos como el objeto de respaldo de formulario de nuestro controlador de formulario que devuelve form.jsp .

En otras palabras, Spring MVC extraerá un objeto de comando y usará su tipo como un modelo para vincular expresiones de path para las tags internas del form , como input o checkbox , para representar un elemento de form HTML.

Este objeto de comando también se denomina atributo de modelo y su nombre se especifica en los atributos modelAttribute o modelAttribute la etiqueta de modelAttribute . Lo has omitido en tu JSP

  

Podrías haber especificado un nombre explícitamente. Ambos son equivalentes.

   

El nombre de atributo predeterminado es command (lo que se ve en el mensaje de error). Un atributo de modelo es un objeto, generalmente un POJO o colección de POJO, que su aplicación suministra a la stack Spring MVC y que la stack Spring MVC expone a su vista (es decir, la M a la V en MVC).

Spring MVC recostack todos los atributos del modelo en un ModelMap (todos tienen nombres) y, en el caso de los JSP, los transfiere a los atributos HttpServletRequest , donde las tags JSP y las expresiones EL tienen acceso a ellos.

En su ejemplo, su método de controlador @Controller que maneja un GET a la ruta /movies agrega un solo atributo de modelo

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

y luego reenvía al index.jsp . Esta JSP luego intenta renderizar

  ...  ...  

Al procesar esto, FormTag (en realidad, InputTag ) intenta encontrar un atributo de modelo llamado command (el nombre de atributo predeterminado) para que pueda producir un elemento HTML con un atributo de name construido a partir de la expresión de path y la propiedad correspondiente valor, es decir. el resultado de Movie#getFilmName() .

Como no puede encontrarlo, arroja la excepción que ves

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

El motor JSP lo detecta y responde con un código de estado de 500. Si desea aprovechar un Movie POJO para simplemente construir su formulario correctamente, puede agregar un atributo de modelo explícitamente con

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

o haga que Spring MVC cree y agregue uno para usted (debe tener un constructor sin parámetros accesible)

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

Alternativamente, incluya un método anotado @ModelAttribute en su clase @Controller

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

Tenga en cuenta que Spring MVC invocará este método y agregará implícitamente el objeto devuelto a sus atributos de modelo para cada solicitud manejada por el @Controller .

Puede haber adivinado a partir de esta descripción que la etiqueta de form de Spring es más adecuada para representar un HTML

de un objeto existente, con valores reales. Si desea simplemente crear un

blanco, puede ser más apropiado construirlo usted mismo y no depender de ningún atributo del modelo.

 

En el lado receptor, su método de manejo POST , aún podrá extraer el valor de entrada filmName y usarlo para inicializar un objeto Movie .

Errores comunes

Como hemos visto, FormTag busca un atributo de modelo llamado command de forma predeterminada o con el nombre especificado en modelAttribute o modelAttribute . Asegúrate de estar usando el nombre correcto.

ModelMap tiene un addAttribute(Object) que agrega

el atributo suministrado a este Map usando un nombre generado .

donde la convención general es

devuelve el nombre abreviado sin capitalizar de la Class [del atributo], de acuerdo con las reglas de denominación de propiedad de JavaBeans: Entonces, com.myapp.Product convierte en product ; com.myapp.MyProduct convierte en myProduct ; com.myapp.UKProduct convierte en UKProduct

Si está utilizando este método (o uno similar) o si está utilizando uno de los tipos de devolución soportados @RequestMapping que representa un atributo de modelo, asegúrese de que el nombre generado sea el que espera.

Otro error común es omitir su método @Controller completo. Una aplicación Spring MVC típica sigue este patrón:

  1. Enviar solicitud HTTP GET
  2. DispatcherServlet selecciona el método @RequestMapping para manejar la solicitud
  3. El método Handler genera algunos atributos del modelo y devuelve el nombre de la vista
  4. DispatcherServlet agrega atributos de modelo a HttpServletRequest y reenvía la solicitud a JSP correspondiente al nombre de vista
  5. JSP representa la respuesta

Si, por alguna configuración incorrecta, omite por completo el método @RequestMapping , los atributos no se agregarán. Esto puede suceder

  • si su URI de solicitud HTTP accede a sus recursos JSP directamente, ej. porque son accesibles, es decir. fuera de WEB-INF , o
  • si la welcome-list de welcome-list de su web.xml contiene su recurso JSP, el contenedor Servlet lo procesará directamente, omitiendo por completo la stack Spring MVC

De una forma u otra, desea que se invoque su @Controller para que los atributos del modelo se agreguen de manera adecuada.

¿ BindingResult tiene que ver BindingResult con esto?

Un BindingResult es un contenedor para la inicialización o validación de los atributos del modelo. La documentación de Spring MVC indica

Los parámetros Errors o BindingResult deben seguir al objeto modelo que se está vinculando inmediatamente ya que la firma del método puede tener más de un objeto modelo y Spring creará una instancia BindingResult separada para cada uno de ellos […]

En otras palabras, si desea usar BindingResult , debe seguir el parámetro de atributo de modelo correspondiente en un método @RequestMapping

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

BindingResult objetos BindingResult también se consideran atributos de modelo. Spring MVC usa una convención de nomenclatura simple para administrarlos, lo que facilita encontrar el atributo de modelo regular correspondiente. Dado que BindingResult contiene más datos sobre el atributo del modelo (por ejemplo, errores de validación), el FormTag intenta vincularse primero. Sin embargo, dado que van de la mano, es poco probable que exista uno sin el otro.

Para simplificar las cosas con la etiqueta de formulario, simplemente agregue un “nombre de comando” que es un nombre horrible para lo que realmente está buscando … quiere el objeto que nombró en la anotación de MdelAttribute. Entonces en este caso commandName = “movie”.

Eso te ahorrará leer explicaciones largas y animadas amigo.

En mi caso, funcionó agregando modelAttribute="movie" a la etiqueta del formulario, y anteponiendo el nombre del modelo al atributo, algo así como