Dies ist ein umfassender kanonischer Frage- und Antwortposten für diese Art von Fragen.
Ich versuche, eine Spring MVC-Webanwendung zu schreiben, in der Benutzer einer In-Memory-Sammlung Filmnamen hinzufügen können. Es ist so konfiguriert
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[] { "/" };
}
}
und
@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;
}
}
Es gibt eine einzige @Controller
-Klasse im com.example
-Paket
@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
enthält
<%@ 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>
Die Anwendung wird mit dem Kontextpfad /Example
konfiguriert. Wenn ich eine GET-Anfrage an sende
http://localhost:8080/Example/movies
die Anforderung schlägt fehl. Spring MVC antwortet mit einem Statuscode 500 und meldet die folgende Ausnahme und die Stapelablaufverfolgung
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)
Ich habe erwartet, dass die JSP einen HTML <form>
mit einer einzelnen Texteingabe für einen Movie
-Namen und einen Senden-Button generiert, mit dem ich eine POST -Anforderung mit einem neuen Movie
senden kann. Warum kann das JSP-Servlet nicht das <form:form>
-Tag von Spring darstellen?
Sie versuchen, Spring MVCs Formular-Tag zu verwenden.
Dieses Tag rendert ein HTML
form
-Tag und macht einen Bindungspfad für innere Tags zum Binden verfügbar. Es fügt das Befehlsobjekt in dasPageContext
ein, damit innere Tags auf das Befehlsobjekt zugreifen können. [..]Nehmen wir an, wir haben ein Domain-Objekt mit dem Namen
User
. Es ist eine JavaBean mit Eigenschaften wiefirstName
undlastName
. Wir werden es als Formular-Backing-Objekt unseres Formular-Controllers verwenden, dasform.jsp
Zurückgibt.
Mit anderen Worten, Spring MVC extrahiert ein Befehlsobjekt und verwendet seinen Typ als Vorlage zum Binden von path
Ausdrücken für form
s innere Tags, wie z. B. input
oder checkbox
, um ein HTML form
-Element zu rendern.
Dieses Befehlsobjekt wird auch als Modellattribut bezeichnet und sein Name wird in den Attributen form
des Tags modelAttribute
oder commandName
angegeben. Sie haben es in Ihrer JSP weggelassen
<form:form>
Sie könnten einen Namen explizit angegeben haben. Beide sind gleichwertig.
<form:form modelAttribute="some-example-name">
<form:form commandName="some-example-name">
Der Standardattributname ist command
(was Sie in der Fehlermeldung sehen). Ein Modellattribut ist ein Objekt, normalerweise ein POJO oder eine Sammlung von POJOs, das Ihre Anwendung dem Spring MVC-Stapel zur Verfügung stellt und das der Spring MVC-Stapel Ihrer Ansicht zur Verfügung stellt (dh das M zum V in MVC).
Spring MVC sammelt alle Modellattribute in einem ModelMap
(alle haben Namen) und überträgt sie im Fall von JSPs an das HttpServletRequest
Attribute, auf die JSP-Tags und EL-Ausdrücke zugreifen können.
In Ihrem Beispiel fügt Ihre Handlermethode @Controller
, Die ein GET
zum Pfad /movies
Verarbeitet, ein einzelnes Modellattribut hinzu
model.addAttribute("movies", movies); // not named 'command'
und dann weiter zum index.jsp
. Diese JSP versucht dann zu rendern
<form:form>
...
<form:input path="name" type="text" id="name" />
...
</form:form>
Während des Renderns versucht FormTag
(in Wirklichkeit versucht InputTag
), ein Modellattribut mit dem Namen command
(zu finden. der Standardattributname), so dass ein HTML-Element <input>
mit einem name
-Attribut erzeugt werden kann, das aus dem path
-Ausdruck und dem entsprechenden Eigenschaftswert aufgebaut ist, d. h. das Ergebnis von Movie#getFilmName()
.
Da es es nicht finden kann, löst es die Ausnahme aus, die Sie sehen
Java.lang.IllegalStateException: Neither BindingResult nor plain target object for bean name 'command' available as request attribute
Die JSP-Engine erkennt es und antwortet mit einem 500-Statuscode. Wenn Sie ein Movie
POJO nutzen möchten, um Ihr Formular einfach korrekt zu erstellen, können Sie ein Modellattribut explizit mit hinzufügen
model.addAttribute("movie", new Movie());
oder lassen Sie Spring MVC eine erstellen und hinzufügen (muss einen barrierefreien parameterlosen Konstruktor haben)
@RequestMapping(path = "/movies", method = RequestMethod.GET)
public String homePage(@ModelAttribute("command") Movie movie, Model model) {...}
Alternativ können Sie eine mit @ModelAttribute
Versehene Methode in Ihre Klasse @Controller
Aufnehmen
@ModelAttribute("command")
public Movie defaultInstance() {
Movie movie = new Movie();
movie.setFilmName("Rocky II");
return movie;
}
Beachten Sie, dass Spring MVC diese Methode aufruft und das zurückgegebene Objekt implizit zu seinen Modellattributen für jede Anforderung hinzufügt, die vom einschließenden @Controller
Verarbeitet wird.
Möglicherweise haben Sie anhand dieser Beschreibung erraten, dass das form
-Tag von Spring besser zum Rendern eines HTML-Codes <form>
Aus einem vorhandenen Objekt mit tatsächlichen Werten geeignet ist. Wenn Sie einfach ein Leerzeichen <form>
Erstellen möchten, ist es möglicherweise sinnvoller, es selbst zu erstellen und sich nicht auf Modellattribute zu verlassen.
<form method="post" action="${pageContext.request.contextPath}/movies">
<input name="filmName" type="text" />
<input type="submit" value="Upload" />
</form>
Auf der Empfängerseite kann Ihre POST
-Handlermethode weiterhin den filmName
-Eingabewert extrahieren und zum Initialisieren eines Movie
-Objekts verwenden.
Wie wir gesehen haben, sucht FormTag
standardmäßig nach einem Modellattribut mit dem Namen command
oder mit dem Namen, der in modelAttribute
oder commandName
angegeben ist. Stellen Sie sicher, dass Sie den richtigen Namen verwenden.
ModelMap
hat eine addAttribute(Object)
Methode, die hinzufügt
das angegebene Attribut für dieses
Map
unter Verwendung eines generierten Namens.
wo die allgemeine Konvention ist
geben Sie den nicht kapitalisierten Kurznamen des [Attributs]
Class
gemäß den Benennungsregeln für JavaBeans-Eigenschaften zurück: Also wirdcom.myapp.Product
zuproduct
;com.myapp.MyProduct
Wird zumyProduct
;com.myapp.UKProduct
Wird zuUKProduct
Wenn Sie diese (oder eine ähnliche) Methode verwenden oder einen der @RequestMapping
nterstützten Rückgabetypen verwenden, der ein Modellattribut darstellt, stellen Sie sicher, dass der generierte Name dem entspricht du erwartest.
Ein weiterer häufiger Fehler besteht darin, die Methode @Controller
Vollständig zu umgehen. Eine typische Spring MVC-Anwendung folgt diesem Muster:
DispatcherServlet
wählt die Methode @RequestMapping
zur Bearbeitung der Anfrage ausDispatcherServlet
fügt Modellattribute zu HttpServletRequest
hinzu und leitet die Anforderung entsprechend dem Ansichtsnamen an JSP weiterWenn Sie durch eine falsche Konfiguration die Methode @RequestMapping
Überspringen, wurden die Attribute nicht hinzugefügt. Das kann passieren
WEB-INF
oderwelcome-list
Ihres web.xml
Ihre JSP-Ressource enthält, rendert der Servlet-Container diese direkt und umgeht den Spring MVC-Stapel vollständigAuf die eine oder andere Weise soll Ihr @Controller
Aufgerufen werden, damit die Modellattribute entsprechend hinzugefügt werden.
BindingResult
damit zu tun?Ein BindingResult
ist ein Container zur Initialisierung oder Validierung von Modellattributen. Die Spring MVC Dokumentation gibt an
Die Parameter
Errors
oderBindingResult
müssen dem Modellobjekt folgen, das sofort gebunden wird, da die Methodensignatur möglicherweise mehr als ein Modellobjekt hat und Spring eine separate InstanzBindingResult
erstellt für jeden von ihnen [...]
Mit anderen Worten, wenn Sie BindingResult
verwenden möchten, muss es dem entsprechenden Modellattributparameter in einer @RequestMapping
- Methode folgen
@RequestMapping(path = "/movies", method = RequestMethod.POST)
public String upload(@ModelAttribute("movie") Movie movie, BindingResult errors) {
BindingResult
Objekte werden auch als Modellattribute betrachtet. Spring MVC verwendet eine einfache Namenskonvention, um sie zu verwalten, sodass ein entsprechendes reguläres Modellattribut leicht gefunden werden kann. Da das BindingResult
mehr Daten über das Modellattribut enthält (z. B. Validierungsfehler), versucht das FormTag
zuerst, sich daran zu binden. Da sie jedoch Hand in Hand gehen, ist es unwahrscheinlich, dass einer ohne den anderen existieren wird.
Um es mit dem form-Tag einfacher zu machen, fügen Sie einfach einen "commandName" hinzu, der ein schrecklicher Name für das ist, wonach er sucht ... er möchte das Objekt, das Sie in der MdelAttribute-Annotation benannt haben. In diesem Fall also commandName = "movie".
Das erspart dir das Lesen von langatmigen Erklärungen, Freund.
In meinem Fall funktionierte es durch Hinzufügen von modelAttribute="movie"
zum Form-Tag und Voranstellen des Modellnamens vor dem Attribut, etwa <form:input path="filmName" type="text" id="movie.name" />
Ich hatte diesen Fehler auf einem Bildschirm mit mehreren Formularen, die eine Suche durchführen. Jedes Formular wird auf einer eigenen Controllermethode bereitgestellt, wobei die Ergebnisse auf demselben Bildschirm angezeigt werden.
Problem: Ich habe das Hinzufügen der beiden anderen Formulare als Modellattribute in jeder Controller-Methode verpasst, was diesen Fehler verursacht, wenn der Bildschirm mit Ergebnissen gerendert wird.
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";
}