web-dev-qa-db-ja.com

「Java.lang.IllegalStateException:BindingResultも、Bean名 'command'のプレーンターゲットオブジェクトもリクエスト属性として使用できない」の原因は何ですか?

これは、これらのタイプの質問に対する広範な標準的な質問と回答の投稿になることを意図しています。


ユーザーがインメモリコレクションに映画名を追加できるSpring MVC Webアプリケーションを作成しようとしています。そのように構成されています

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

そして

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

@Controllerパッケージには単一の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を含む

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

アプリケーションは、コンテキストパス/Exampleで構成されます。 GETリクエストを送信するとき

http://localhost:8080/Example/movies

リクエストは失敗し、Spring MVCは500ステータスコードで応答し、次の例外とスタックトレースを報告します

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)

JSPは、POSTリクエストを送信するために使用できるMovie名と送信ボタンの単一のテキスト入力でHTML <form>を生成することを期待していました新しいMovie。代わりに、JSPサーブレットがSpringの<form:form>タグのレンダリングに失敗するのはなぜですか?

25

Spring MVCのform tag を使用しようとしています。

このタグは、HTML formタグをレンダリングし、バインディング用の内部タグへのバインディングパスを公開します。コマンドオブジェクトをPageContextに配置して、内部タグからコマンドオブジェクトにアクセスできるようにします。 [..]

Userというドメインオブジェクトがあるとします。 firstNamelastNameなどのプロパティを持つJavaBeanです。 _form.jsp_を返すフォームコントローラーのformバッキングオブジェクトとして使用します。

つまり、Spring MVCはコマンドオブジェクトを抽出し、 path または な​​どのformの内部タグのinput式をバインドするための青写真としてそのタイプを使用しますcheckbox 、HTMLのform要素をレンダリングします。

このコマンドオブジェクトはモデル属性とも呼ばれ、その名前はformタグのmodelAttributeまたはcommandName属性で指定されます。 JSPで省略しました

_<form:form> 
_

名前を明示的に指定することもできます。これらは両方とも同等です。

_<form:form modelAttribute="some-example-name">
<form:form commandName="some-example-name">
_

default属性名はcommand (エラーメッセージに表示されるもの)です。モデル属性は、アプリケーションがSpring MVCスタックに提供するオブジェクト、通常はPOJOまたはPOJOのコレクションであり、Spring MVCスタックがビューに公開します(MVのMからVへ)。

Spring MVCは、すべてのモデル属性を ModelMap (それらはすべて名前を持っています)に収集し、JSPの場合、それらを HttpServletRequest 属性に転送します。タグとEL式はそれらにアクセスできます。

この例では、パス_@Controller_へのGETを処理する_/movies_ハンドラーメソッドが単一のモデル属性を追加します

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

その後、_index.jsp_に転送します。次に、このJSPはレンダリングしようとします

_<form:form>
    ...
    <form:input path="name" type="text" id="name" />
    ...
</form:form>
_

これをレンダリングする際、 FormTag (実際には、 InputTag )はcommand(デフォルトの属性名)という名前のモデル属性を見つけようとし、 name式と対応するプロパティ値から構築されたpath属性を持つHTML _<input>_要素。 Movie#getFilmName()の結果。

見つからないため、表示される例外をスローします

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

JSPエンジンはそれをキャッチし、500ステータスコードで応答します。 Movie POJOを利用してフォームを正しく構築したい場合は、モデル属性を明示的に追加できます。

_model.addAttribute("movie", new Movie());
_

またはSpring MVCに作成して追加してもらいます(アクセス可能なパラメータなしのコンストラクタが必要です)

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

または、_@ModelAttribute_クラスに_@Controller_アノテーション付きメソッドを含める

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

Spring MVCはこのメソッドを呼び出し、返されたオブジェクトを、囲んでいる_@Controller_によって処理される各リクエストのモデル属性に暗黙的に追加することに注意してください。

この説明から、Springのformタグは、実際の値を使用して、既存のオブジェクトからHTML _<form>_をレンダリングするのにより適していると推測できます。単純に空の_<form>_を作成する場合は、モデル属性に依存せずに自分で作成する方が適切な場合があります。

_<form method="post" action="${pageContext.request.contextPath}/movies">
    <input name="filmName" type="text" />
    <input type="submit" value="Upload" />
</form>
_

受信側のPOSTハンドラーメソッドは、filmName入力値を抽出し、それを使用してMovieオブジェクトを初期化できます。

一般的なエラー

これまで見てきたように、FormTagはデフォルトで、またはcommandまたはmodelAttributeで指定された名前を持つcommandNameという名前のモデル属性を探します。正しい名前を使用していることを確認してください。

ModelMapには、追加する addAttribute(Object) メソッドがあります

generated の名前を使用して、このMapに提供された属性。

一般的な慣習は

javaBeansプロパティの命名規則に従って、[属性] Classの大文字ではない短縮名を返します。したがって、_com.myapp.Product_はproductになります。 _com.myapp.MyProduct_はmyProductになります。 _com.myapp.UKProduct_はUKProductになります

この(または同様の)メソッドを使用している場合、またはモデル属性を表す_@RequestMapping_ supported return types のいずれかを使用している場合は、生成される名前があなたが期待するもの。

別の一般的なエラーは、_@Controller_メソッドを完全にバイパスすることです。典型的なSpring MVCアプリケーションは、次のパターンに従います。

  1. HTTP GETリクエストを送信
  2. DispatcherServletは、リクエストを処理する_@RequestMapping_メソッドを選択します
  3. ハンドラーメソッドはいくつかのモデル属性を生成し、ビュー名を返します
  4. DispatcherServletはモデル属性をHttpServletRequestに追加し、ビュー名に対応するJSPにリクエストを転送します
  5. JSPが応答をレンダリングする

何らかの設定ミスにより_@RequestMapping_メソッドを完全にスキップした場合、属性は追加されません。これは起こりうる

  • hTTPリクエストURIがJSPリソースに直接アクセスする場合(例:彼らがアクセス可能であるため、すなわち。 _WEB-INF_外、または
  • _welcome-list_の_web.xml_にJSPリソースが含まれている場合、サーブレットコンテナは、Spring MVCスタックを完全にバイパスして、それを直接レンダリングします

何らかの方法で、モデル属性が適切に追加されるように_@Controller_を呼び出す必要があります。

BindingResultはこれと何の関係がありますか?

BindingResult は、モデル属性の初期化または検証のためのコンテナです。 Spring MVC documentation 状態

ErrorsまたはBindingResultパラメーターは、メソッドシグネチャに複数のモデルオブジェクトが含まれる場合があり、Springはそれぞれに個別のBindingResultインスタンスを作成するため、すぐにバインドされるモデルオブジェクトに従う必要があります[...]

つまり、BindingResultを使用する場合は、_@RequestMapping_メソッドの対応するモデル属性パラメーターに従う必要があります

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

BindingResultオブジェクトもモデル属性と見なされます。 Spring MVCはシンプルな命名規則を使用してそれらを管理し、対応する通常のモデル属性を簡単に見つけられるようにします。 BindingResultにはモデル属性に関するより多くのデータ(検証エラーなど)が含まれているため、FormTagは最初にモデル属性にバインドしようとします。しかし、それらは手をつないで行くので、一方が他方なしで存在する可能性は低いです。

28

Formタグで物事を単純にするために、実際に探しているものの恐ろしい名前である「commandName」を追加するだけです。MdelAttributeアノテーションで指定したオブジェクトが必要です。したがって、この場合はcommandName = "movie"です。

長い説明の友人を読むのを節約できます。

1
Pasha Utt

私の場合、modelAttribute="movie"をformタグに追加し、<form:input path="filmName" type="text" id="movie.name" />のようなモデル名を属性に追加することで機能しました

0
Pedro Madrid

検索を行う複数のフォームがある画面でこのエラーが発生しました。各フォームは、同じ画面に結果が表示される独自のコントローラーメソッドに投稿します。

問題:他の2つのフォームを各コントローラーメソッドのモデル属性として追加できなかったため、画面に結果が表示されるときにエラーが発生しました。

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