Quali sono le cause “java.lang.IllegalStateException: Né BindingResult né semplice object di destinazione per il nome bean” comando “disponibile come attributo di richiesta”?

Questo è pensato per essere una vasta domanda canonica e post di risposta per questi tipi di domande.


Sto cercando di scrivere un’applicazione web MVC di Spring in cui gli utenti possano aggiungere nomi di film a una raccolta in memoria. È configurato in questo modo

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

e

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

Esiste una sola class @Controller nel pacchetto 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:

L’applicazione è configurata con il percorso di contesto /Example . Quando invio una richiesta GET a

 http://localhost:8080/Example/movies 

la richiesta non riesce, Spring MVC risponde con un codice di stato 500 e segnala la seguente eccezione e traccia dello 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) 

Mi aspettavo che JSP generasse un

HTML con un solo input di testo, per un nome di Movie e un pulsante di invio, che posso usare per inviare una richiesta POST con un nuovo Movie . Perché il servlet JSP fallisce invece nel rendering del

Spring?

Stai tentando di utilizzare il tag modulo di Spring MVC .

Questo tag esegue il rendering di un tag form HTML e espone un percorso di collegamento ai tag interni per il binding. Mette l’object comando in PageContext modo che l’object comando sia accessibile dai tag interni. [..]

Supponiamo di avere un object dominio chiamato User . È un JavaBean con proprietà come firstName e lastName . Lo useremo come object di supporto alla forma del nostro controller di forma che restituisce form.jsp .

In altre parole, Spring MVC estrarrà un object comando e utilizzerà il suo tipo come modello per le espressioni del path associazione per i tag interni del form , come input o checkbox , per il rendering di un elemento form HTML.

Questo object comando è anche chiamato un attributo modello e il suo nome è specificato negli attributi modelAttribute o commandName del tag form . L’hai omesso nel tuo JSP

  

Potresti aver specificato un nome esplicitamente. Entrambi sono equivalenti.

   

Il nome attributo predefinito è command (ciò che vedi nel messaggio di errore). Un attributo model è un object, in genere un POJO o una raccolta di POJO, che la tua applicazione fornisce allo stack MVC Spring e che lo stack MVC Spring espone alla tua vista (cioè la M alla V in MVC).

Spring MVC raccoglie tutti gli attributi del modello in una ModelMap (hanno tutti nomi) e, nel caso di JSP, li trasferisce agli attributi HttpServletRequest , dove i tag JSP e le espressioni EL hanno accesso ad essi.

Nell’esempio, il metodo del gestore @Controller che gestisce un GET per il percorso /movies aggiunge un singolo attributo del modello

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

e poi inoltra all’indice.jsp. Questo JSP tenta quindi di eseguire il rendering

  ...  ...  

Durante il rendering, FormTag (in realtà, InputTag ) tenta di trovare un attributo di modello denominato command (il nome dell’attributo predefinito) in modo che possa produrre un elemento HTML con un attributo name creato dall’espressione path e dalla proprietà corrispondente valore, cioè. il risultato di Movie#getFilmName() .

Dal momento che non riesce a trovarlo, lancia l’eccezione che vedi

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

Il motore JSP lo rileva e risponde con un codice di stato di 500. Se si desidera sfruttare un POJO Movie per build semplicemente il modulo correttamente, è ansible aggiungere un attributo modello esplicitamente con

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

o avere Spring MVC creare e aggiungerne uno per te (deve avere un costruttore parametrico accessibile)

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

In alternativa, includi un metodo annotato @ModelAttribute nella tua class @Controller

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

Notare che Spring MVC chiamerà questo metodo e aggiungerà implicitamente l’object restituito agli attributi del modello per ogni richiesta gestita dall’acclesso di @Controller .

Potresti aver intuito da questa descrizione che il tag form di Spring è più adatto per il rendering di un

HTML da un object esistente, con valori reali. Se vuoi semplicemente creare un

vuoto, potrebbe essere più appropriato costruirlo tu stesso e non fare affidamento su alcun attributo del modello.

 

Sul lato ricevente, il metodo del gestore POST , sarà comunque in grado di estrarre il valore di input filmName e utilizzarlo per inizializzare un object Movie .

Errori comuni

Come abbiamo visto, FormTag cerca un attributo modello denominato command per impostazione predefinita o con il nome specificato in modelAttribute o commandName . Assicurati di utilizzare il nome giusto.

ModelMap ha un metodo addAttribute(Object) che aggiunge

l’attributo fornito a questa Map usando un nome generato .

dove è la convenzione generale

restituire il nome breve non assegnato della Class [attribute], in base alle regole di denominazione delle proprietà JavaBeans: Quindi, com.myapp.Product diventa product ; com.myapp.MyProduct diventa myProduct ; com.myapp.UKProduct diventa UKProduct

Se stai utilizzando questo metodo (o un metodo simile) o se stai utilizzando uno dei tipi di ritorno supportati da @RequestMapping che rappresenta un attributo di modello, assicurati che il nome generato sia quello che ti aspetti.

Un altro errore comune consiste nel bypassare del tutto il metodo @Controller . Una tipica applicazione MVC Spring segue questo schema:

  1. Invia richiesta GET HTTP
  2. DispatcherServlet seleziona il metodo @RequestMapping per gestire la richiesta
  3. Il metodo del gestore genera alcuni attributi del modello e restituisce il nome della vista
  4. DispatcherServlet aggiunge gli attributi del modello a HttpServletRequest e inoltra la richiesta a JSP corrispondente al nome della vista
  5. JSP rende la risposta

Se, con qualche @RequestMapping configurazione, salti del tutto il metodo @RequestMapping , gli attributi non saranno stati aggiunti. Questo può succedere

  • se l’URI della richiesta HTTP accede direttamente alle risorse JSP, ad es. perché sono accessibili, vale a dire. all’esterno di WEB-INF , o
  • se l’ welcome-list di welcome-list del tuo web.xml contiene la tua risorsa JSP, il contenitore Servlet lo renderà direttamente, bypassando interamente lo stack Spring MVC

In un modo o nell’altro, vuoi richiamare @Controller in modo che gli attributi del modello vengano aggiunti in modo appropriato.

Cosa BindingResult con questo?

Un BindingResult è un contenitore per l’inizializzazione o la convalida degli attributi del modello. La documentazione di Spring MVC afferma

I parametri Errors o BindingResult devono seguire l’object modello che viene associato immediatamente poiché la firma del metodo potrebbe avere più di un object modello e Spring creerà un’istanza BindingResult separata per ciascuno di essi […]

In altre parole, se si desidera utilizzare BindingResult , deve seguire il parametro dell’attributo del modello corrispondente in un metodo @RequestMapping

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

BindingResult oggetti BindingResult sono considerati attributi del modello. Spring MVC utilizza una semplice convenzione di denominazione per gestirli, facilitando la ricerca di un attributo di modello regolare corrispondente. Poiché BindingResult contiene più dati sull’attributo del modello (ad esempio errori di convalida), FormTag tenta di FormTag binding prima. Tuttavia, dal momento che vanno di pari passo, è improbabile che esista senza l’altro.

Per semplificare le cose con il tag form basta aggiungere un “commandName” che è un nome orribile per quello che effettivamente sta cercando … vuole l’object che hai chiamato nell’annotazione MdelAttribute. Quindi in questo caso commandName = “movie”.

Questo ti salverà leggendo le lunghe spiegazioni amichevoli.

Nel mio caso, ha funzionato aggiungendo modelAttribute="movie" al tag form e anteponendo il nome del modello all’attributo, qualcosa come