wake-up-neo.com

Was verursacht "Java.lang.IllegalStateException: Weder BindingResult noch einfaches Zielobjekt für Bean-Name 'command' als Anforderungsattribut verfügbar"?

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?

18

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 das PageContext 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 wie firstName und lastName. Wir werden es als Formular-Backing-Objekt unseres Formular-Controllers verwenden, das form.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 forms 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.

Häufige Fehler

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 wird com.myapp.Product zu product; com.myapp.MyProduct Wird zu myProduct; com.myapp.UKProduct Wird zu UKProduct

Wenn Sie diese (oder eine ähnliche) Methode verwenden oder einen der @RequestMappingnterstü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:

  1. HTTP-GET-Anfrage senden
  2. DispatcherServlet wählt die Methode @RequestMapping zur Bearbeitung der Anfrage aus
  3. Die Handler-Methode generiert einige Modellattribute und gibt den Ansichtsnamen zurück
  4. DispatcherServlet fügt Modellattribute zu HttpServletRequest hinzu und leitet die Anforderung entsprechend dem Ansichtsnamen an JSP weiter
  5. JSP gibt die Antwort wieder

Wenn Sie durch eine falsche Konfiguration die Methode @RequestMapping Überspringen, wurden die Attribute nicht hinzugefügt. Das kann passieren

  • wenn Ihr HTTP-Anforderungs-URI direkt auf Ihre JSP-Ressourcen zugreift, z. weil sie zugänglich sind, dh. außerhalb WEB-INF oder
  • wenn das welcome-list Ihres web.xml Ihre JSP-Ressource enthält, rendert der Servlet-Container diese direkt und umgeht den Spring MVC-Stapel vollständig

Auf die eine oder andere Weise soll Ihr @Controller Aufgerufen werden, damit die Modellattribute entsprechend hinzugefügt werden.

Was hat BindingResult damit zu tun?

Ein BindingResult ist ein Container zur Initialisierung oder Validierung von Modellattributen. Die Spring MVC Dokumentation gibt an

Die Parameter Errors oder BindingResult müssen dem Modellobjekt folgen, das sofort gebunden wird, da die Methodensignatur möglicherweise mehr als ein Modellobjekt hat und Spring eine separate Instanz BindingResult 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.

28

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. 

1
Pasha Utt

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" />

0
Pedro Madrid

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";
}
0
Raj K