java - examples - spring mvc jsp
¿Qué causa "java.lang.IllegalStateException: ni BindingResult ni el objeto de destino simple para el nombre de comando ''comando'' disponible como atributo de solicitud"? (6)
Esto está destinado a ser una extensa publicación de preguntas y respuestas canónicas para este tipo de preguntas.
Estoy tratando de escribir una aplicación web Spring MVC donde los usuarios pueden agregar nombres de películas a una colección en memoria. Está configurado así
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<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
contiene
<%@ 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>
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 500 e informa la siguiente excepción y el seguimiento de la pila
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)
Esperaba que el JSP generara un HTML
<form>
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 nueva
Movie
.
¿Por qué el servlet JSP no puede representar la etiqueta Spring
<form:form>
?
Agregue el atributo de modelo (nombre del modelo que especificó en el controlador) para relacionar su formulario. Debe agregar los siguientes atributos a su formulario
<form:form modelAttribute="movies" method="post">
En mi caso, funcionó agregando
modelAttribute="movie"
a la etiqueta del formulario y anteponiendo el nombre del modelo al atributo, algo así como
<form:input path="filmName" type="text" id="movie.name" />
Estás tratando de usar la etiqueta de formulario Spring MVC .
Esta etiqueta representa una etiqueta de
form
HTML y expone una ruta de enlace a las etiquetas internas para el enlace. Coloca el objeto de comando en elPageContext
para que las etiquetas internas puedan acceder al objeto de comando. [..]Supongamos que tenemos un objeto de dominio llamado
User
. Es un JavaBean con propiedades comofirstName
ylastName
. Lo usaremos como el objeto de respaldo de formulario de nuestro controlador de formulario que devuelveform.jsp
.
En otras palabras, Spring MVC extraerá un
objeto de comando
y usará su tipo como modelo para vincular expresiones de
path
para etiquetas internas de
form
, como
input
o
checkbox
, para representar un elemento de
form
HTML.
Este
objeto de comando
también se denomina atributo modelo y su nombre se especifica en los atributos
modelAttribute
o
modelAttribute
la etiqueta de
modelAttribute
.
Lo has omitido en tu JSP
<form:form>
Podrías haber especificado un nombre explícitamente. Ambos son equivalentes.
<form:form modelAttribute="some-example-name">
<form:form commandName="some-example-name">
El
nombre de atributo predeterminado es
command
(lo que ve en el mensaje de error).
Un atributo modelo es un objeto, generalmente un POJO o una colección de POJO, que su aplicación suministra a la pila Spring MVC y que la pila Spring MVC expone a su vista (es decir, la M a la V en MVC).
Spring MVC recopila 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 etiquetas 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 atributo de modelo único
model.addAttribute("movies", movies); // not named ''command''
y luego reenviar a
index.jsp
.
Este JSP luego intenta renderizar
<form:form>
...
<form:input path="name" type="text" id="name" />
...
</form:form>
Al procesar esto,
FormTag
(en realidad,
InputTag
) intenta encontrar un atributo modelo llamado
command
(el nombre de atributo predeterminado) para que pueda producir un elemento HTML
<input>
con un atributo de
name
construido a partir de la expresión de
path
y la propiedad correspondiente valor, es decir.
El resultado de la
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 atrapa y responde con un código de estado 500.
Si desea aprovechar un POJO de
Movie
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 llamará a 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
<form>
HTML desde un objeto existente, con valores reales.
Si desea simplemente crear un
<form>
blanco, puede ser más apropiado construirlo usted mismo y no confiar en ningún atributo del modelo.
<form method="post" action="${pageContext.request.contextPath}/movies">
<input name="filmName" type="text" />
<input type="submit" value="Upload" />
</form>
En el lado de recepción, su método de controlador
POST
aún podrá extraer el valor de entrada de
filmName
y usarlo para inicializar un objeto
Movie
.
Errores comunes
Como hemos visto,
FormTag
busca un atributo de modelo llamado
command
por defecto 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 proporcionado a este
Map
utilizando un nombre generated .
donde la convención general es
devuelve el nombre corto no capitalizado de la
Class
[atributo], de acuerdo con las reglas de denominación de propiedades JavaBeans: Entonces,com.myapp.Product
convierte enproduct
;com.myapp.MyProduct
convierte enmyProduct
;com.myapp.UKProduct
convierte enUKProduct
Si está utilizando este método (o uno similar) o si está utilizando uno de los
tipos de retorno admitidos de
@RequestMapping
que representa un atributo de modelo, asegúrese de que el nombre generado sea el que espera.
Otro error común es omitir el método
@Controller
completo.
Una aplicación típica de Spring MVC sigue este patrón:
- Enviar solicitud HTTP GET
-
DispatcherServlet
selecciona el método@RequestMapping
para manejar la solicitud - El método del controlador genera algunos atributos del modelo y devuelve el nombre de la vista
-
DispatcherServlet
agrega atributos de modelo aHttpServletRequest
y reenvía la solicitud a JSP correspondiente al nombre de la vista - JSP rinde respuesta
Si, por alguna mala configuración, omite el método
@RequestMapping
completo, los atributos no se habrán agregado.
Esto puede suceder
-
si su URI de solicitud HTTP accede a sus recursos JSP directamente, por ejemplo.
porque son accesibles, es decir.
fuera de
WEB-INF
, o -
Si la
welcome-list
dewelcome-list
de suweb.xml
contiene su recurso JSP, el contenedor de Servlet lo representará directamente, omitiendo por completo la pila Spring MVC
De una forma u otra, desea que se invoque su
@Controller
para que los atributos del modelo se agreguen adecuadamente.
¿
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
dice
Los parámetros
Errors
oBindingResult
deben seguir el objeto modelo que se está vinculando de inmediato, ya que la firma del método puede tener más de un objeto modelo y Spring creará una instanciaBindingResult
separada para cada uno de ellos [...]
En otras palabras, si desea utilizar
BindingResult
, debe seguir el parámetro de atributo del 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 del modelo.
Spring MVC utiliza una convención de nomenclatura simple para administrarlos, lo que facilita encontrar un atributo de modelo regular correspondiente.
Dado que
BindingResult
contiene más datos sobre el atributo del modelo (p. Ej., Errores de validación), el
FormTag
intenta
FormTag
primero.
Sin embargo, dado que van de la mano, es poco probable que exista uno sin el otro.
La actualización de Spring versión 3 a Spring versión 5 produce el mismo error.
Todas las respuestas fueron satisfechas ya en mi código.
Agregar la anotación
@ControllerAdvice
resolvió el problema para mí.
Para simplificar las cosas con la etiqueta de formulario, simplemente agregue un "commandName" que es un nombre horrible para lo que realmente está buscando ... quiere el objeto que nombró en la anotación MdelAttribute. Entonces en este caso commandName = "movie".
Eso te ahorrará leer largas explicaciones sin aliento amigo.
Tuve este error en una pantalla con múltiples formas que hacen una búsqueda. Cada formulario se publica en su propio método de controlador con resultados que se muestran en la misma pantalla.
Problema: omití agregar las otras dos formas como atributos de modelo en cada método de controlador, lo que causó ese error cuando la pantalla se muestra con resultados.
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";
}