アプリケーション層の実装 ================================================================================ .. only:: html .. contents:: 目次 :depth: 3 :local: 本節では、HTML formを使った画面遷移型のアプリケーションにおけるアプリケーション層の実装について説明する。 .. note:: Ajaxの開発やREST APIの開発で必要となる実装についての説明は以下のページを参照されたい。 - :doc:`../ArchitectureInDetail/WebApplicationDetail/Ajax` | アプリケーション層の実装は、以下の3つにわかれる。 #. | :ref:`controller-label` | Controllerは、リクエストの受付、業務処理の呼び出し、モデルの更新、Viewの決定といった処理を行い、リクエストを受けてからの一連の処理フローを制御する。 | アプリケーション層の実装において、もっとも重要な実装となる。 #. | :ref:`formobject` | フォームオブジェクトは、HTML formとアプリケーションの間での値の受け渡しを行う。 #. | :ref:`view` | View(Thymeleaf)は、モデル(フォームオブジェクトやドメインオブジェクトなど)からデータを取得し、画面(HTML)を生成する。 .. _controller-label: Controllerの実装 -------------------------------------------------------------------------------- | まず、Controllerの実装から説明する。 | Controllerは、以下5つの役割を担う。 #. | **リクエストを受け取るためのメソッドを提供する。** | \ ``@RequestMapping``\ アノテーションが付与されたメソッドを実装することで、リクエストを受け取ることができる。 #. | **リクエストパラメータの入力チェックを行う。** | 入力チェックが必要なリクエストを受け取るメソッドでは、\ ``@Validated``\ アノテーションをフォームオブジェクトの引数に指定することで、リクエストパラメータの入力チェックを行うことができる。 | 単項目チェックはBean Validation、相関チェックはSpring Validator又はBean Validationでチェックを行う。 #. | **業務処理の呼び出しを行う。** | Controllerでは業務処理の実装は行わず、Serviceのメソッドに処理を委譲する。 #. | **業務処理の処理結果をModelに反映する。** | Serviceのメソッドから返却されたドメインオブジェクトを\ ``Model``\ に反映することで、Viewから処理結果を参照できるようにする。 #. | **処理結果に対応するView名を返却する。** | Controllerでは処理結果に対する描画処理を実装せず、描画処理はThymeleaf等のViewで実装する。 | Controllerでは描画処理が実装されているViewのView名の返却のみ行う。 | View名に対応するViewの解決は、Spring Frameworkより提供されている\ ``ViewResolver``\ によって行われ、処理結果に対応するView(Thymeleafなど)が呼び出される仕組みになっている。 .. figure:: images_ApplicationLayer/application_logic-of-controller.png :alt: responsibility of logic :width: 80% :align: center **Picture - Logic of controller** .. note:: Controllerでは、業務処理の呼び出し、処理結果の\ ``Model``\ への反映、遷移先(View名)の決定などの **ルーティング処理の実装に徹することを推奨する。** | Controllerの実装について、以下4つの点に着目して説明する。 - :ref:`controller-new-label` - :ref:`controller_mapping-label` - :ref:`controller_method_argument-label` - :ref:`controller_method_return-label` | .. _controller-new-label: Controllerクラスの作成方法 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | **Controllerは、POJOクラスに @Controller アノテーションを付加したクラス (Annotation-based Controller)として作成する。** | Spring MVCのControllerとしては、``org.springframework.web.servlet.mvc.Controller``\ インタフェースを実装する方法 (Interface-based Controller)もあるが、Spring3以降はDeprecatedになっているため、原則使用しない。 .. code-block:: java @Controller public class SampleController { // ... } | | .. _controller_mapping-label: リクエストとハンドラメソッドのマッピング方法 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | リクエストを受け取るメソッドは、\ ``@RequestMapping``\ アノテーションを付与する。 | 本ガイドラインでは、\ ``@RequestMapping``\ が付加されたメソッドのことを「ハンドラメソッド」と呼ぶ。 .. code-block:: java @RequestMapping(value = "hello") public String hello() { // ... } | リクエストとハンドラメソッドをマッピングするためのルールは、\ ``@RequestMapping``\ アノテーションの属性に指定する。 .. tabularcolumns:: |p{0.10\linewidth}|p{0.10\linewidth}|p{0.80\linewidth}| .. list-table:: :header-rows: 1 :widths: 10 10 80 * - 項番 - 属性名 - 説明 * - 1. - value - | マッピング対象にするリクエストパスを指定する(複数可)。 * - 2. - method - | マッピング対象にするHTTPメソッド(\ ``RequestMethod``\ 型)を指定する(複数可)。 | GET/POSTについてはHTML form向けのリクエストをマッピングする際にも使用するが、それ以外のHTTPメソッド(PUT/DELETEなど)はREST API向けのリクエストをマッピングする際に使用する。 * - 3. - params - | マッピング対象にするリクエストパラメータを指定する(複数可)。 | 主にHTML form向けのリクエストをマッピングする際に使用する。このマッピング方法を使用すると、HTML form上に複数のボタンが存在する場合のマッピングを簡単に実現する事ができる。 * - 4. - headers - | マッピング対象とするリクエストヘッダを指定する(複数可)。 | 主にREST APIやAjax向けのリクエストをマッピングする際に使用する。 * - 5. - consumes - | リクエストのContent-Typeヘッダを使ってマッピングすることが出来る。マッピング対象とするメディアタイプを指定する(複数可)。 | 主にREST APIやAjax向けのリクエストをマッピングする際に使用する。 * - 6. - produces - | リクエストのAcceptヘッダを使ってマッピングすることが出来る。マッピング対象とするメディアタイプを指定する(複数可)。 | 主にREST APIやAjax向けのリクエストをマッピングする際に使用する。 .. note:: **マッピングの組み合わせについて** 複数の属性を組み合わせることで複雑なマッピングを行うことも可能だが、保守性を考慮し、可能な限りシンプルな定義になるようにマッピングの設計を行うこと。 2つの属性の組み合わせ(value属性と別の属性1つ)を目安にすることを推奨する。 | | 以下、マッピングの具体例を6つ示す。 - :ref:`controller-mapping-path-label` - :ref:`controller-mapping-method-label` - :ref:`controller-mapping-params-label` - :ref:`controller-mapping-headers-label` - :ref:`controller-mapping-contenttype-label` - :ref:`controller-mapping-accept-label` | 以降の説明では、以下のControllerクラスにハンドラメソッドを定義する前提となっている。 .. code-block:: java :emphasize-lines: 1-2 @Controller // (1) @RequestMapping("sample") // (2) public class SampleController { // ... } .. tabularcolumns:: |p{0.10\linewidth}|p{0.90\linewidth}| .. list-table:: :header-rows: 1 :widths: 10 90 * - 項番 - 説明 * - | (1) - ``@Controller``\ アノテーションを付加することでAnnotation-basedなコントローラークラスとして認識され、component scanの対象となる。 * - | (2) - クラスレベルで\ ``@RequestMapping("sample")``\ アノテーションを付けることでこのクラス内のハンドラメソッドがsample配下のURLにマッピングされる。 | .. _controller-mapping-path-label: リクエストパスでマッピング """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" 下記の定義の場合、``sample/hello`` というURLにアクセスすると、helloメソッドが実行される。 .. code-block:: java @RequestMapping(value = "hello") public String hello() { | 複数指定した場合は、OR条件で扱われる。 | 下記の定義の場合、 ``sample/hello`` 又は ``sample/bonjour`` というURLにアクセスすると、helloメソッドが実行される。 .. code-block:: java @RequestMapping(value = {"hello", "bonjour"}) public String hello() { 指定するリクエストパスは、具体的な値ではなくパターンを指定することも可能である。パターン指定の詳細は、Spring FrameworkのReference Documentを参照。 - `URI Template Patterns `_\ - `URI Template Patterns with Regular Expressions `_\ - `Path Patterns `_\ - `Patterns with Placeholders `_\ | .. _controller-mapping-method-label: HTTPメソッドでマッピング """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" 下記の定義の場合、 ``sample/hello`` というURLにPOSTメソッドでアクセスすると、helloメソッドが実行される。 サポートしているHTTPメソッドの一覧は `RequestMethodのJavadoc `_ を参照されたい。 指定しない場合、サポートしている全てのHTTPメソッドがマッピング対象となる。 .. code-block:: java @RequestMapping(value = "hello", method = RequestMethod.POST) public String hello() { | 複数指定した場合は、OR条件で扱われる。 | 下記の定義の場合、 ``sample/hello`` というURLにGET又はHEADメソッドでアクセスすると、helloメソッドが実行される。 .. code-block:: java @RequestMapping(value = "hello", method = {RequestMethod.GET, RequestMethod.HEAD}) public String hello() { | .. _controller-mapping-params-label: リクエストパラメータでマッピング """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" | 下記の定義の場合、 ``sample/hello?form`` というURLにアクセスすると、helloメソッドが実行される。 | POSTでリクエストする場合は、リクエストパラメータはURLになくてもリクエストBODYに存在していればよい。 .. code-block:: java @RequestMapping(value = "hello", params = "form") public String hello() { | 複数指定した場合は、AND条件で扱われる。 | 下記の定義の場合、 ``sample/hello?form&formType=foo`` というURLにアクセスすると、helloメソッドが実行される。 .. code-block:: java @RequestMapping(value = "hello", params = {"form", "formType=foo"}) public String hello(@RequestParam("formType") String formType) { サポートされている指定形式は以下の通り。 .. tabularcolumns:: |p{0.08\linewidth}|p{0.25\linewidth}|p{0.67\linewidth}| .. list-table:: :header-rows: 1 :widths: 8 25 67 * - 項番 - 形式 - 説明 * - 1. - paramName - 指定したparameNameのリクエストパラメータが存在する場合にマッピングされる。 * - 2. - !paramName - 指定したparameNameのリクエストパラメータが存在しない場合にマッピングされる。 * - 3. - paramName=paramValue - 指定したparameNameの値がparamValueの場合にマッピングされる。 * - 4. - paramName!=paramValue - 指定したparameNameの値がparamValueでない場合にマッピングされる。 | .. _controller-mapping-headers-label: リクエストヘッダでマッピング """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" 主にREST APIやAjax向けのリクエストをマッピングする際に使用するため、詳細は以下のページを参照されたい。 - :doc:`../ArchitectureInDetail/WebApplicationDetail/Ajax` .. _controller-mapping-contenttype-label: Content-Typeヘッダでマッピング """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" 主にREST APIやAjax向けのリクエストをマッピングする際に使用するため、詳細は以下のページを参照されたい。 - :doc:`../ArchitectureInDetail/WebApplicationDetail/Ajax` .. _controller-mapping-accept-label: Acceptヘッダでマッピング """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" 主にREST APIやAjax向けのリクエストをマッピングする際に使用するため、詳細は以下のページを参照されたい。 - :doc:`../ArchitectureInDetail/WebApplicationDetail/Ajax` | | .. _controller-mapping-policy-label: リクエストとハンドラメソッドのマッピング方針 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 以下の方針でマッピングを行うことを推奨する。 - | **業務や機能といった意味のある単位で、リクエストのURLをグループ化する。** | URLのグループ化とは、 \ ``@RequestMapping(value = "xxx")``\ をクラスレベルのアノテーションとして定義することを意味する。 - | **処理内の画面フローで使用するリクエストのURLは、同じURLにする。** | 同じURLとは \ ``@RequestMapping(value = "xxx")``\ のvalue属性の値を同じ値にすることを意味する。 | 処理内の画面フローで使用するハンドラメソッドの切り替えは、HTTPメソッドとHTTPパラメータによって行う。 以下にベーシックな画面フローを行うサンプルアプリケーションを例にして、リクエストとハンドラメソッドの具体的なマッピング例を示す。 * :ref:`controller-mapping-policy-sampleapp-overview-label` * :ref:`controller-mapping-policy-sampleapp-url-design-label` * :ref:`controller-mapping-policy-sampleapp-mapping-design-label` * :ref:`controller-mapping-policy-sampleapp-form-impl-label` * :ref:`controller-mapping-policy-sampleapp-confirm-impl-label` * :ref:`controller-mapping-policy-sampleapp-redo-impl-label` * :ref:`controller-mapping-policy-sampleapp-create-impl-label` | .. _controller-mapping-policy-sampleapp-overview-label: サンプルアプリケーションの概要 """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" サンプルアプリケーションの機能概要は以下の通り。 - | EntityのCRUD処理を行う機能を提供する。 - | 以下の5つの処理を提供する。 .. tabularcolumns:: |p{0.10\linewidth}|p{0.20\linewidth}|p{0.70\linewidth}| .. list-table:: :header-rows: 1 :widths: 10 20 70 * - 項番 - 処理名 - 処理概要 * - 1. - Entity一覧取得 - 作成済みのEntityを全て取得し、一覧画面に表示する。 * - 2. - Entity新規作成 - 指定した内容で新たにEntityを作成する。処理内には、画面フロー(フォーム画面、確認画面、完了画面)が存在する。 * - 3. - Entity参照 - 指定されたIDのEntityを取得し、詳細画面に表示する。 * - 4. - Entity更新 - 指定されたIDのEntityを更新する。処理内には、画面フロー(フォーム画面、確認画面、完了画面)が存在する。 * - 5. - Entity削除 - 指定されたIDのEntityを削除する。 - | 機能全体の画面フローは以下の通り。 | 画面フロー図には記載していないが、入力チェックエラーが発生した場合はフォーム画面を再描画するものとする。 .. figure:: images_ApplicationLayer/application_sample-screen-flow.png :alt: Screen flow of entity management function :width: 90% :align: center **Picture - Screen flow of entity management function** | .. _controller-mapping-policy-sampleapp-url-design-label: リクエストURL """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" 必要となるリクエストのURLの設計を行う。 - | 機能内で必要となるリクエストのリクエストURLをグループ化する。 | ここではAbcというEntityのCRUD操作を行う機能となるので、 ``/abc/`` から始まるURLとする。 - 処理毎にリクエストURLを設ける。 .. tabularcolumns:: |p{0.10\linewidth}|p{0.30\linewidth}|p{0.60\linewidth}| .. list-table:: :header-rows: 1 :widths: 10 30 60 * - 項番 - 処理名 - 処理毎のURL(パターン) * - 1. - Entity一覧取得 - /abc/list * - 2. - Entity新規作成 - /abc/create * - 3. - Entity参照 - /abc/{id} * - 4. - Entity更新 - /abc/{id}/update * - 5. - Entity削除 - /abc/{id}/delete .. note:: Entity参照、Entity更新、Entity削除処理のURL内に指定している ``{id}`` は、`URI Template Patterns `_\ と呼ばれ、任意の値を指定する事ができる。 サンプルアプリケーションでは、操作するEntityのIDを指定する。 画面フロー図に各処理に割り振られたURLをマッピングすると以下のようになる。 .. figure:: images_ApplicationLayer/application_sample-screen-flow-assigned-url.png :alt: Screen flow of entity management function and assigned URL :width: 90% :align: center **Picture - Screen flow of entity management function and assigned URL** | .. _controller-mapping-policy-sampleapp-mapping-design-label: リクエストとハンドラメソッドのマッピング """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" | リクエストとハンドラメソッドのマッピングの設計を行う。 | 以下は、マッピング方針に則って設計したマッピング定義となる。 .. tabularcolumns:: |p{0.05\linewidth}|p{0.20\linewidth}|p{0.15\linewidth}|p{0.20\linewidth}|p{0.10\linewidth}|p{0.10\linewidth}|p{0.15\linewidth}| .. list-table:: :header-rows: 1 :widths: 5 20 15 22 10 13 15 :class: longtable * - | 項番 - | 処理名 - | URL - | リクエスト名 - | HTTP | メソッド - | HTTP | パラメータ - | ハンドラメソッド * - 1. - Entity一覧取得 - /abc/list - 一覧表示 - GET - \- - list * - 2. - Entity新規作成 - /abc/create - フォーム表示 - \- - form - createForm * - 3. - - - 入力内容確認表示 - POST - confirm - createConfirm * - 4. - - - フォーム再表示 - POST - redo - createRedo * - 5. - - - 新規作成 - POST - \- - create * - 6. - - - 新規作成完了表示 - GET - complete - createComplete * - 7. - Entity参照 - /abc/{id} - 詳細表示 - GET - \- - read * - 8. - Entity更新 - /abc/{id}/update - フォーム表示 - \- - form - updateForm * - 9. - - - 入力内容確認表示 - POST - confirm - updateConfirm * - 10. - - - フォーム再表示 - POST - redo - updateRedo * - 11. - - - 更新 - POST - \- - update * - 12. - - - 更新完了表示 - GET - complete - updateComplete * - 13. - Entity削除 - /abc/{id}/delete - 削除 - POST - \- - delete * - 14. - - - 削除完了表示 - GET - complete - deleteComplete .. raw:: latex \newpage | Entity新規作成、Entity更新、Entity削除処理では、処理内に複数のリクエストが存在しているため、HTTPメソッドとHTTPパラメータによってハンドラメソッドを切り替えている。 | 以下に、Entity新規作成処理を例に、処理内に複数のリクエストが存在する場合のリクエストフローを示す。 | URLは全て ``/abc/create`` で、HTTPメソッドとHTTPパラメータの組み合わせでハンドラメソッドを切り替えている点に注目すること。 .. figure:: images_ApplicationLayer/applicationScreenflow.png :alt: Request flow of entity create processing :width: 90% :align: center **Picture - Request flow of entity create processing** | | 以下に、Entity新規作成処理のハンドラメソッドの実装コードを示す。 | ここではリクエストとハンドラメソッドのマッピングについて理解してもらうのが目的なので、\ ``@RequestMapping``\ の書き方に注目すること。 | ハンドラメソッドの引数や返り値(View名及びView)の詳細については、次章以降で説明する。 - :ref:`controller-mapping-policy-sampleapp-form-impl-label` - :ref:`controller-mapping-policy-sampleapp-confirm-impl-label` - :ref:`controller-mapping-policy-sampleapp-redo-impl-label` - :ref:`controller-mapping-policy-sampleapp-create-impl-label` - :ref:`controller-mapping-policy-sampleapp-complete-impl-label` - :ref:`controller-mapping-policy-sampleapp-multi-impl-label` | .. _controller-mapping-policy-sampleapp-form-impl-label: フォーム表示の実装 """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" フォーム表示する場合は、HTTPパラメータとして ``form`` を指定させる。 .. code-block:: java :emphasize-lines: 1,4 @RequestMapping(value = "create", params = "form") // (1) public String createForm(AbcForm form, Model model) { // omitted return "abc/createForm"; // (2) } .. tabularcolumns:: |p{0.10\linewidth}|p{0.90\linewidth}| .. list-table:: :header-rows: 1 :widths: 10 90 * - 項番 - 説明 * - | (1) - params属性に ``form`` を指定する。 * - | (2) - フォーム画面を描画するためのThymeleafによって生成されるHTMLのView名を返却する。 .. note:: この処理でHTTPメソッドをGETに限る必要がないのでmethod属性を指定していない。 | 以下に、ハンドラメソッド以外の部分の実装例についても説明しておく。 フォーム表示を行う場合、ハンドラメソッドの実装以外に、 - フォームオブジェクトの生成処理の実装。フォームオブジェクトの詳細は、 :ref:`formobject` を参照されたい。 - フォーム画面のViewの実装。Viewの詳細は、 :ref:`view` を参照されたい。 が必要になる。 以下のフォームオブジェクトを使用する。 .. code-block:: java public class AbcForm implements Serializable { private static final long serialVersionUID = 1L; @NotEmpty private String input1; @NotNull @Min(1) @Max(10) private Integer input2; // omitted setter&getter } フォームオブジェクトを生成する。 .. code-block:: java @ModelAttribute public AbcForm setUpAbcForm() { return new AbcForm(); } フォーム画面のView(テンプレートHTML)を作成する。 .. code-block:: html :emphasize-lines: 11

Abc Create Form



.. tabularcolumns:: |p{0.10\linewidth}|p{0.90\linewidth}| .. list-table:: :header-rows: 1 :widths: 10 90 * - 項番 - 説明 * - | (1) - 確認画面へ遷移するためのsubmitボタンには\ ``name="confirm"``\ というパラメータを指定しておく。 | 以下に、フォーム表示の動作について説明する。 | フォーム表示処理を呼び出す。 | ``abc/create?form`` というURIにアクセスする。 | ``form`` というHTTPパラメータの指定があるため、ControllerのcreateFormメソッドが呼び出されフォーム画面が表示される。 .. figure:: images_ApplicationLayer/applicationCreateFormDisplay.png :width: 90% | .. _controller-mapping-policy-sampleapp-confirm-impl-label: 入力内容確認表示の実装 """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" フォームの入力内容を確認する場合は、POSTメソッドでデータを送信し、HTTPパラメータに ``confirm`` を指定させる。 .. code-block:: java :emphasize-lines: 1,5,8 @RequestMapping(value = "create", method = RequestMethod.POST, params = "confirm") // (1) public String createConfirm(@Validated AbcForm form, BindingResult result, Model model) { if (result.hasErrors()) { return createRedo(form, model); // return "abc/createForm"; (2) } // omitted return "abc/createConfirm"; // (3) } .. tabularcolumns:: |p{0.10\linewidth}|p{0.90\linewidth}| .. list-table:: :header-rows: 1 :widths: 10 90 * - 項番 - 説明 * - | (1) - method属性に ``RequestMethod.POST`` 、params属性に ``confirm`` を指定する。 * - | (2) - 入力チェックエラーが発生した場合の処理は、フォーム再表示用のハンドラメソッドを呼び出すことを推奨する。フォーム画面を再表示するための処理の共通化を行うことができる。 * - | (3) - 入力内容確認画面を描画するためのThymeleafによって生成されるHTMLのView名を返却する。 .. note:: POSTメソッドを指定させる理由は、個人情報やパスワードなどの秘密情報がブラウザのアドレスバーに現れ、他人に容易に閲覧されることを防ぐためである。 (もちろんセキュリティ対策としては十分ではなく、SSLなどのセキュアなサイトにする必要がある)。 | 以下に、ハンドラメソッド以外の部分の実装例についても説明しておく。 入力内容確認表示を行う場合、ハンドラメソッドの実装以外に、 - 入力内容確認画面のViewの実装。Viewの詳細は、 :ref:`view` を参照されたい。 が必要になる。 入力内容確認画面のView(テンプレートHTML)を作成する。 .. code-block:: html :emphasize-lines: 5,9,11-12

Abc Create Form



.. tabularcolumns:: |p{0.10\linewidth}|p{0.90\linewidth}| .. list-table:: :header-rows: 1 :widths: 10 90 * - 項番 - 説明 * - | (1) - フォーム画面で入力された値は、Createボタン及びBackボタンが押下された際に再度サーバに送る必要があるため、HTML formのhidden項目とする。 * - | (2) - フォーム画面に戻るためのsubmitボタンには\ ``name="redo"``\ というパラメータを指定しておく。 * - | (3) - 新規作成を行うためのsubmitボタンにはパラメータ名の指定は不要。 .. note:: ``th:text`` 属性を使用すると、値をHTMLエスケープして表示することができる。 XSS対策のため、HTMLエスケープは必ず行うこと。詳細については :doc:`Cross Site Scripting <../Security/XSS>` を参照されたい。 | 以下に、入力内容確認の動作について説明する。 | 入力内容確認表示処理を呼び出す。 | フォーム画面でInput1に ``aa`` を、Input2に "``5``" を入力し、Confirmボタンを押下する。 | Confirmボタンを押下すると、 ``abc/create?confirm`` というURIにPOSTメソッドでアクセスする。 | ``confirm`` というHTTPパラメータがあるため、ControllerのcreateConfirmメソッドが呼び出され、入力内容確認画面が表示される。 .. figure:: images_ApplicationLayer/applicationCreateConfirmDisplay.png :width: 90% Confirmボタンを押下するとPOSTメソッドでHTTPパラメータが送信されるため、URIには現れていないが、HTTPパラメータとして ``confirm`` が含まれている。 .. figure:: images_ApplicationLayer/applicationCreateConfirmNetwork.png :width: 90% | .. _controller-mapping-policy-sampleapp-redo-impl-label: フォーム再表示の実装 """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" フォームを再表示する場合は、HTTPパラメータにredoを指定させる。 .. code-block:: java :emphasize-lines: 1,4 @RequestMapping(value = "create", method = RequestMethod.POST, params = "redo") // (1) public String createRedo(AbcForm form, Model model) { // omitted return "abc/createForm"; // (2) } .. tabularcolumns:: |p{0.10\linewidth}|p{0.90\linewidth}| .. list-table:: :header-rows: 1 :widths: 10 90 * - 項番 - 説明 * - | (1) - method属性に ``RequestMethod.POST`` 、params属性に ``redo`` を指定する。 * - | (2) - 入力内容確認画面を描画するためのThymeleafによって生成されるHTMLのView名を返却する。 | 以下に、フォーム再表示の動作について説明する。 | フォーム再表示リクエストを呼び出す。 | 入力内容確認画面で、Backボタンを押下する。 | Backボタンを押下すると、 ``abc/create?redo`` というURIにPOSTメソッドでアクセスする。 | ``redo`` というHTTPパラメータがあるため、ControllerのcreateRedoメソッドが呼び出され、フォーム画面が再表示される。 .. figure:: images_ApplicationLayer/applicationCreateConfirmDisplay.png :width: 90% Backボタンを押下するとPOSTメソッドでHTTPパラメータが送信されるため、URIには現れていないが、HTTPパラメータとして ``redo`` が含まれている。 また、フォームの入力値をhidden項目として送信されるため、フォーム画面で入力値を復元することが出来る。 .. figure:: images_ApplicationLayer/applicationBackToCreateFormDisplay.png :width: 90% .. figure:: images_ApplicationLayer/applicationBackToCreateFormNetwork.png :width: 90% .. note:: 戻るボタンの実現方法には、ボタンの属性に ``onclick="javascript:history.back()"`` を設定する方法もある。 両者では以下が異なり、要件に応じて選択する必要がある。 * ブラウザの戻るボタンを押した場合の挙動 * 戻るボタンがあるページに直接アクセスして戻るボタンを押した場合の挙動 * ブラウザの履歴 | .. _controller-mapping-policy-sampleapp-create-impl-label: 新規作成の実装 """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" | フォームの入力内容を登録する場合は、POSTで登録対象のデータ(hiddenパラメータ)を送信させる。 | 新規作成リクエストはこの処理のメインリクエストになるので、HTTPパラメータによる振り分けは行っていない。 | この処理ではデータベースの状態を変更するので、二重送信によって新規作成処理が複数回実行されないように制御する必要がある。 | そのため、この処理が終了した後はView(画面)を直接表示するのではなく、次の画面(新規作成完了画面)へリダイレクトしている。このパターンをPOST-Redirect-GET(PRG)パターンと呼ぶ。 :abbr:`PRG (Post-Redirect-Get)` パターンの詳細については :doc:`../ArchitectureInDetail/WebApplicationDetail/DoubleSubmitProtection` を参照されたい。 .. code-block:: java :emphasize-lines: 1,7 @RequestMapping(value = "create", method = RequestMethod.POST) // (1) public String create(@Validated AbcForm form, BindingResult result, Model model) { if (result.hasErrors()) { return createRedo(form, model); // return "abc/createForm"; } // omitted return "redirect:/abc/create?complete"; // (2) } .. tabularcolumns:: |p{0.10\linewidth}|p{0.90\linewidth}| .. list-table:: :header-rows: 1 :widths: 10 90 * - 項番 - 説明 * - | (1) - method属性に ``RequestMethod.POST`` を指定し、params属性は指定しない。 * - | (2) - :abbr:`PRG (Post-Redirect-Get)` パターンとするため、新規作成完了表示リクエストにリダイレクトするためのURLをView名として返却する。 .. note:: "redirect:/xxx"を返却すると"/xxx"へリダイレクトさせることができる。 .. warning:: PRGパターンとすることで、ブラウザのF5ボタン押下時のリロードによる二重送信を防ぐ事はできるが、二重送信の対策としては十分ではない。 二重送信の対策としては、共通部品として提供しているTransactionTokenCheckを行う必要がある。 TransactionTokenCheckの詳細については :doc:`../ArchitectureInDetail/WebApplicationDetail/DoubleSubmitProtection` を参照されたい。 | 以下に、「新規作成」の動作について説明する。 | 新規作成処理を呼び出す。 | 入力内容確認画面で、Createボタンを押下する。 | Createボタンを押下すると、 ``abc/create`` というURIにPOSTメソッドでアクセスする。 | ボタンを識別するためのHTTPパラメータを送信していないので、Entity新規作成処理のメインのリクエストと判断され、Controllerのcreateメソッドが呼び出される。 | 新規作成リクエストでは、直接画面を返さず、新規作成完了表示( ``/abc/create?complete`` )へリダイレクトしているため、HTTPステータスが302になっている。 .. figure:: images_ApplicationLayer/applicationCreateNetwork.png :width: 90% | .. _controller-mapping-policy-sampleapp-complete-impl-label: 新規作成完了表示の実装 """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" 新規作成処理が完了した事を通知する場合は、HTTPパラメータに ``complete`` を指定させる。 .. code-block:: java :emphasize-lines: 1,4 @RequestMapping(value = "create", params = "complete") // (1) public String createComplete() { // omitted return "abc/createComplete"; // (2) } .. tabularcolumns:: |p{0.10\linewidth}|p{0.90\linewidth}| .. list-table:: :header-rows: 1 :widths: 10 90 * - 項番 - 説明 * - | (1) - params属性に ``complete`` を指定する。 * - | (2) - 新規作成完了画面を描画するため、Thymeleafによって生成されるHTMLのView名を返却する。 .. note:: この処理もHTTPメソッドをGETに限る必要がないのでmethod属性を指定しなくても良い。 | 以下に、「新規作成完了表示」の動作について説明する。 | 新規作成完了後、リダイレクト先に指定されたURI( ``/abc/create?complete`` )にアクセスする。 | ``complete`` というHTTPパラメータがあるため、ControllerのcreateCompleteメソッドが呼び出され、新規作成完了画面が表示される。 .. figure:: images_ApplicationLayer/applicationCreateCompleteDisplay.png :width: 90% .. figure:: images_ApplicationLayer/applicationCreateCompleteNetwork.png :width: 90% .. note:: PRGパターンを利用しているため、ブラウザをリロードしても、新規作成処理は実行されず、新規作成完了が再度表示されるだけである。 | .. _controller-mapping-policy-sampleapp-multi-impl-label: HTML form上に複数のボタンを配置する場合の実装 """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" 1つのフォームに対して複数のボタンを設置したい場合、ボタンを識別するためのHTTPパラメータを送ることで、 実行するハンドラメソッドを切り替える。 ここではサンプルアプリケーションの入力内容確認画面のCreateボタンとBackボタンを例に説明する。 下図のように、入力内容確認画面のフォームには、新規作成を行うCreateボタンと新規作成フォーム画面を再表示するBackボタンが存在する。 .. figure:: images_ApplicationLayer/applicationControllerBackToForm.png :alt: Multiple button in the HTML form :width: 80% :align: center **Picture - Multiple button in the HTML form** Backボタンを押下した場合、新規作成フォーム画面を再表示するためのリクエスト( ``/abc/create?redo`` )を送信する必要があるため、 HTML form内に以下のコードが必要となる。 .. code-block:: html :emphasize-lines: 1 .. tabularcolumns:: |p{0.10\linewidth}|p{0.90\linewidth}| .. list-table:: :header-rows: 1 :widths: 10 90 * - 項番 - 説明 * - | (1) - 上記のように、入力内容確認画面( ``abc/createConfirm.html`` )のBackボタンに\ ``name="redo"``\ というパラメータを指定する。 Backボタン押下時の動作については、 :ref:`controller-mapping-policy-sampleapp-redo-impl-label` を参照されたい。 | .. _controller-mapping-policy-sampleapp-all-impl-label: サンプルアプリケーションのControllerのソースコード """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" | 以下に、サンプルアプリケーションの新規作成処理実装後のControllerの全ソースを示す。 | Entity一覧取得、Entity参照、Entity更新、Entity削除も同じ要領で実装することになるが、説明は割愛する。 .. code-block:: java @Controller @RequestMapping("abc") public class AbcController { @ModelAttribute public AbcForm setUpAbcForm() { return new AbcForm(); } // Handling request of "/abc/create?form" @RequestMapping(value = "create", params = "form") public String createForm(AbcForm form, Model model) { // omitted return "abc/createForm"; } // Handling request of "POST /abc/create?confirm" @RequestMapping(value = "create", method = RequestMethod.POST, params = "confirm") public String createConfirm(@Validated AbcForm form, BindingResult result, Model model) { if (result.hasErrors()) { return createRedo(form, model); } // omitted return "abc/createConfirm"; } // Handling request of "POST /abc/create?redo" @RequestMapping(value = "create", method = RequestMethod.POST, params = "redo") public String createRedo(AbcForm form, Model model) { // omitted return "abc/createForm"; } // Handling request of "POST /abc/create" @RequestMapping(value = "create", method = RequestMethod.POST) public String create(@Validated AbcForm form, BindingResult result, Model model) { if (result.hasErrors()) { return createRedo(form, model); } // omitted return "redirect:/abc/create?complete"; } // Handling request of "/abc/create?complete" @RequestMapping(value = "create", params = "complete") public String createComplete() { // omitted return "abc/createComplete"; } } | | .. _controller_method_argument-label: ハンドラメソッドの引数について ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `ハンドラメソッドの引数は様々な値をとることができる `_ が、 基本的には次に挙げるものは原則として使用しないこと。 * ServletRequest * HttpServletRequest * org.springframework.web.context.request.WebRequest * org.springframework.web.context.request.NativeWebRequest * java.io.InputStream * java.io.Reader * java.io.OutputStream * java.io.Writer * java.util.Map * org.springframework.ui.ModelMap .. note:: ``HttpServletRequest`` のgetAttribute/setAttribute や ``Map`` のget/putのような汎用的なメソッドの利用を許可すると自由な値の受け渡しができてしまい、 プロジェクトの規模が大きくなると保守性を著しく低下させる可能性がある。 同様の理由で、他で代替できる場合は ``HttpSession`` を極力使用しないことを推奨する。 共通的なパラメータ(リクエストパラメータ)をJavaBeanに格納してControllerの引数に渡したい場合は 後述の :ref:`methodargumentresolver` を使用することで実現できる。 | 以下に、引数の使用方法について、目的別に13例示す。 - :ref:`controller_method_argument-model-label` - :ref:`controller_method_argument-pathvariable-label` - :ref:`controller_method_argument-requestparam-label` - :ref:`controller_method_argument-form-label` - :ref:`controller_method_argument-validation-label` - :ref:`controller_method_argument-redirectattributes-label` - :ref:`controller_method_argument-redirectattributes-param-label` - :ref:`controller_method_argument-redirectattributes-path-label` - :ref:`controller_method_argument-cookievalue-label` - :ref:`controller_method_argument-cookiewrite-label` - :ref:`controller_method_argument-pagination-label` - :ref:`controller_method_argument-upload-label` - :ref:`controller_method_argument-message-label` | .. _controller_method_argument-model-label: 画面(View)にデータを渡す """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" 画面(View)に表示するデータを渡したい場合は、``org.springframework.ui.Model``\ (以降 ``Model`` と呼ぶ) をハンドラメソッドの引数として受け取り、 \ ``Model``\ オブジェクトに渡したいデータ(オブジェクト)を追加する。 - SampleController.java .. code-block:: java :emphasize-lines: 2-4 @RequestMapping("hello") public String hello(Model model) { // (1) model.addAttribute("hello", "Hello World!"); // (2) model.addAttribute(new HelloBean("Bean Hello World!")); // (3) return "sample/hello"; // returns view name } - hello.html .. code-block:: html :emphasize-lines: 1-2

- HTML of created by View(hello.html) .. code-block:: html :emphasize-lines: 1-2 Hello World!
Bean Hello World!
.. tabularcolumns:: |p{0.10\linewidth}|p{0.90\linewidth}| .. list-table:: :header-rows: 1 :widths: 10 90 * - 項番 - 説明 * - | (1) - \ ``Model``\ オブジェクトを引数として受け取る。 * - | (2) - | 引数で受け取った\ ``Model``\ オブジェクトの\ ``addAttribute``\ メソッドを呼び出し、渡したいデータを\ ``Model``\ オブジェクトに追加する。 | 例では、``hello`` という属性名で ``Hello World!`` という文字列のデータを追加している。 * - | (3) - | \ ``addAttribute``\ メソッドの第一引数を省略すると値のクラス名の先頭を小文字にした文字列が属性名になる。 | 例では、 ``model.addAttribute("helloBean", new HelloBean());`` を行ったのと同じ結果となる。 * - | (4) - | テンプレートHTML側では、 ``th:text`` などの属性において${属性名}のような式を記述することできる。 | ``${}`` は変数式で、\ ``Model``\ オブジェクトに追加したデータを取得することができる。 | 例では、取得したデータをHTMLエスケープして出力するために ``th:text`` 属性を利用し、「th:text="${hello}"」としている。 | HTMLエスケープの詳細については、 :doc:`Cross Site Scripting <../Security/XSS>` を参照されたい。 * - | (5) - | 「${属性名.JavaBeanのプロパティ名}」と記述することで\ ``Model``\に格納されているJavaBeanから値を取得することができる。 * - | (6) - | Thymeleafによって出力されるHTML。 .. note:: \ ``Model``\ は使用しない場合でも引数に指定しておいてもよい。実装初期段階では必要なくても 後で使う場合がある(後々メソッドのシグニチャを変更する必要がなくなる)。 .. note:: ``Model`` に ``addAttribute`` することで、 ``HttpServletRequest`` に ``setAttribute`` されるため、 Spring MVCの管理下にないモジュール(例えばServletFilterなど)からも値を参照することが出来る。 | .. _controller_method_argument-pathvariable-label: URLのパスから値を取得する """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" | URLのパスから値を取得する場合は、引数に\ ``@PathVariable``\ アノテーションを付与する。 | \ ``@PathVariable``\ アノテーションを使用してパスから値を取得する場合、 \ ``@RequestMapping``\ アノテーションのvalue属性に取得したい部分を変数化しておく必要がある。 .. code-block:: java :emphasize-lines: 1,3,4 @RequestMapping("hello/{id}/{version}") // (1) public String hello( @PathVariable("id") String id, // (2) @PathVariable Integer version, // (3) Model model) { // do something return "sample/hello"; // returns view name } .. tabularcolumns:: |p{0.10\linewidth}|p{0.90\linewidth}| .. list-table:: :header-rows: 1 :widths: 10 90 * - 項番 - 説明 * - | (1) - | \ ``@RequestMapping``\ アノテーションのvalue属性に、抜き出したい箇所をパス変数として指定する。パス変数は、「{変数名}」の形式で指定する。 | 上記例では、 ``id`` と ``version`` という二つのパス変数を指定している。 * - | (2) - | \ ``@PathVariable``\ アノテーションのvalue属性には、パス変数の変数名を指定する。 | 上記例では、 ``sample/hello/aaaa/1`` というURLにアクセスした場合、引数idに文字列 ``aaaa`` が渡る。 * - | (3) - | ``@PathVariable``\ アノテーションのvalue属性は省略可能で、省略した場合は引数名がリクエストパラメータ名となる。 | 上記例では、 ``sample/hello/aaaa/1`` というURLにアクセスした場合、引数versionに数値 "``1``" が渡る。 | ただしこの方法は、 * \ ``-g``\ オプション(デバッグ情報を出力するモード) * Java8から追加された\ ``-parameters``\ オプション(メソッド・パラメータにリフレクション用のメタデータを生成するモード) のどちらかを指定してコンパイルする必要がある。 .. note:: バインドする引数の型はString以外でも良い。型が合わない場合は\ ``org.springframework.beans.TypeMismatchException``\ がスローされ、デフォルトの動作は400(Bad Request)が応答される。 例えば、上記例で ``sample/hello/aaaa/v1`` というURLでアクセスした場合、``v1`` をIntegerに変換できないため、例外がスローされる。 .. warning:: ``@PathVariable``\ アノテーションのvalue属性を省略する場合、デプロイするアプリケーションは\ ``-g``\ オプション又はJava8から追加された\ ``-parameters``\ オプションを指定してコンパイルする必要がある。 これらのオプションを指定した場合、コンパイル後のクラスにはデバッグ時に必要となる情報や処理などが挿入されるため、メモリや処理性能に影響を与えることがあるので注意が必要である。 基本的には、value属性を明示的に指定する方法を推奨する。 | .. _controller_method_argument-requestparam-label: リクエストパラメータを個別に取得する """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" リクエストパラメータを1つずつ取得したい場合は、引数に\ ``@RequestParam``\ アノテーションを付与する。 .. code-block:: java :emphasize-lines: 3-6 @RequestMapping("bindRequestParams") public String bindRequestParams( @RequestParam("id") String id, // (1) @RequestParam String name, // (2) @RequestParam(value = "age", required = false) Integer age, // (3) @RequestParam(value = "genderCode", required = false, defaultValue = "unknown") String genderCode, // (4) Model model) { // do something return "sample/hello"; // returns view name } .. tabularcolumns:: |p{0.10\linewidth}|p{0.90\linewidth}| .. list-table:: :header-rows: 1 :widths: 10 90 * - 項番 - 説明 * - | (1) - | \ ``@RequestParam``\ アノテーションのvalue属性には、リクエストパラメータ名を指定する。 | 上記例では、 ``sample/hello?id=aaaa`` というURLにアクセスした場合、引数idに文字列 ``aaaa`` が渡る。 * - | (2) - | ``@RequestParam``\ アノテーションのvalue属性は省略可能で、省略した場合は引数名がリクエストパラメータ名となる。 | 上記例では、 ``sample/hello?name=bbbb&....`` というURLにアクセスした場合、引数nameに文字列 ``bbbb`` が渡る。 | ただしこの方法は、 * \ ``-g``\ オプション(デバッグ情報を出力するモード) * Java8から追加された\ ``-parameters``\ オプション(メソッド・パラメータにリフレクション用のメタデータを生成するモード) のどちらかを指定してコンパイルする必要がある。 * - | (3) - | デフォルトの動作では、指定したリクエストパラメータが存在しないとエラーとなる。リクエストパラメータが存在しないケースを許容する場合は、required属性を ``false`` に指定する。 | 上記例では、``age`` というリクエストパラメータがない状態でアクセスした場合、引数ageに\ ``null``\ が渡る。 * - | (4) - | 指定したリクエストパラメータが存在しない場合にデフォルト値を使用したい場合は、defaultValue属性にデフォルト値を指定する。 | 上記例では、``genderCode`` というリクエストパラメータがない状態でアクセスした場合、引数genderCodeに ``unknown`` が渡る。 .. note:: 必須パラメータを指定しないでアクセスした場合は、\ ``org.springframework.web.bind.MissingServletRequestParameterException``\ がスローされ、デフォルトの動作は400(Bad Request)が応答される。 ただし、defaultValue属性を指定している場合、例外はスローされず、defaultValue属性で指定した値が渡る。 .. note:: バインドする引数の型はString以外でも良い。型が合わない場合は\ ``org.springframework.beans.TypeMismatchException``\ がスローされ、デフォルトの動作は400(Bad Request)が応答される。 例えば、上記例で ``sample/hello?age=aaaa&...`` というURLでアクセスした場合、 ``aaaa`` をIntegerに変換できないため、例外がスローされる。 | **以下の条件に当てはまる場合は、次に説明するフォームオブジェクトにバインドすること。** - リクエストパラメータがHTML form内の項目である。 - リクエストパラメータはHTML form内の項目ではないが、リクエストパラメータに必須チェック以外の入力チェックを行う必要がある。 - リクエストパラメータの入力チェックエラーのエラー詳細をパラメータ毎に出力する必要がある。 - 3つ以上のリクエストパラメータをバインドする。(保守性、可読性の観点) | .. _controller_method_argument-form-label: リクエストパラメータをまとめて取得する """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" | リクエストパラメータをオブジェクトにまとめて取得する場合は、フォームオブジェクトを使用する。 | フォームオブジェクトは、HTML formを表現するJavaBeanである。フォームオブジェクトの詳細は :ref:`formobject` を参照されたい。 以下は、``@RequestParam`` で個別にリクエストパラメータを受け取っていたハンドラメソッドを、フォームオブジェクトで受け取るように変更した場合の実装例である。 ``@RequestParam`` を使って個別にリクエストパラメータを受け取っているハンドラメソッドは以下の通り。 .. code-block:: java @RequestMapping("bindRequestParams") public String bindRequestParams( @RequestParam("id") String id, @RequestParam String name, @RequestParam(value = "age", required = false) Integer age, @RequestParam(value = "genderCode", required = false, defaultValue = "unknown") String genderCode, Model model) { // do something return "sample/hello"; // returns view name } | フォームオブジェクトクラスを作成する。 | このフォームオブジェクトに対応するHTML formのテンプレートHTMLは :ref:`formobject_thymeleaf` を参照されたい。 .. code-block:: java public class SampleForm implements Serializable{ private static final long serialVersionUID = 1477614498217715937L; private String id; private String name; private Integer age; private String genderCode; // omit setters and getters } .. note:: **リクエストパラメータ名とフォームオブジェクトのプロパティ名は一致させる必要がある。** 上記のフォームオブジェクトに対して ``id=aaa&name=bbbb&age=19&genderCode=men?tel=01234567`` というパラメータが送信された場合、 ``id`` , ``name`` , ``age`` , ``genderCode`` は名前が一致するプロパティに値が格納されるが、 ``tel`` は名前が一致するプロパティがないため、フォームオブジェクトに取り込まれない。 ``@RequestParam`` を使って個別に受け取っていたリクエストパラメータをフォームオブジェクトとして受け取るようにする。 .. code-block:: java :emphasize-lines: 2 @RequestMapping("bindRequestParams") public String bindRequestParams(@Validated SampleForm form, // (1) BindingResult result, Model model) { // do something return "sample/hello"; // returns view name } .. tabularcolumns:: |p{0.10\linewidth}|p{0.90\linewidth}| .. list-table:: :header-rows: 1 :widths: 10 90 * - 項番 - 説明 * - | (1) - \ ``SampleForm``\ オブジェクトを引数として受け取る。 .. note:: フォームオブジェクトを引数に用いた場合、\ ``@RequestParam``\ の場合とは異なり、 必須チェックは行われない。\ **フォームオブジェクトを使用する場合は、次に説明する** :ref:`controller_method_argument-validation-label` **を行うこと**\ 。 .. warning:: EntityなどDomainオブジェクトをそのままフォームオブジェクトとして使うこともできるが、 実際には、WEBの画面上にしか存在しないパラメータ(確認用パスワードや、規約確認チェックボックス等)が存在する。 Domainオブジェクトにそのような画面項目に依存する項目を入れるべきではないので、Domainオブジェクトとは別にフォームオブジェクト用のクラスを作成することを推奨する。 リクエストパラメータからDomainオブジェクトを作成する場合は、一旦フォームオブジェクトにバインドしてからプロパティ値をDomainオブジェクトにコピーすること。 | .. _controller_method_argument-validation-label: 入力チェックを行う """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" リクエストパラメータがバインドされているフォームオブジェクトに対して入力チェックを行う場合は、 フォームオブジェクト引数に\ ``@Validated``\ アノテーションを付け、 フォームオブジェクト引数の直後に\ ``org.springframework.validation.BindingResult``\ (以降\ ``BindingResult``\ と呼ぶ) を引数に指定する。 入力チェックの詳細については、 :doc:`../ArchitectureInDetail/WebApplicationDetail/Validation` を参照されたい。 フォームオブジェクトクラスのフィールドに入力チェックで必要となるアノテーションを付加する。 .. code-block:: java public class SampleForm implements Serializable { private static final long serialVersionUID = 1477614498217715937L; @NotNull @Size(min = 10, max = 10) private String id; @NotNull @Size(min = 1, max = 10) private String name; @Min(1) @Max(100) private Integer age; @Size(min = 1, max = 10) private Integer genderCode; // omit setters and getters } | フォームオブジェクト引数に\ ``@Validated``\ アノテーションを付与する。 | ``@Validated``\ アノテーションを付けた引数は、ハンドラメソッド実行前に入力チェックが行われ、チェック結果が直後の\ ``BindingResult``\ 引数に格納される。 | フォームオブジェクトにString型以外を指定した場合に発生する型変換エラーも \ ``BindingResult``\ に格納されている。 .. code-block:: java :emphasize-lines: 2,3,5 @RequestMapping("bindRequestParams") public String bindRequestParams(@Validated SampleForm form, // (1) BindingResult result, // (2) Model model) { if (result.hasErrors()) { // (3) return "sample/input"; // back to the input view } // do something return "sample/hello"; // returns view name } .. tabularcolumns:: |p{0.10\linewidth}|p{0.90\linewidth}| .. list-table:: :header-rows: 1 :widths: 10 90 * - 項番 - 説明 * - | (1) - \ ``SampleForm``\ オブジェクトに\ ``@Validated``\ アノテーションを付与し、入力チェック対象のオブジェクトにする。 * - | (2) - 入力チェック結果が格納される\ ``BindingResult``\ を引数に指定する。 * - | (3) - 入力チェックエラーが存在するか判定する。エラーがある場合は、``true`` が返却される。 | .. _controller_method_argument-redirectattributes-label: リダイレクト先にデータを渡す """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" ハンドラメソッドを実行した後にリダイレクトする場合に、リダイレクト先で表示するデータを渡したい場合は、\ ``org.springframework.web.servlet.mvc.support.RedirectAttributes``\ (以降\ ``RedirectAttributes``\ と呼ぶ) をハンドラメソッドの引数として受け取り、 ``RedirectAttributes``\ オブジェクトに渡したいデータを追加する。 - SampleController.java .. code-block:: java :emphasize-lines: 2-5,10 @RequestMapping("hello") public String hello(RedirectAttributes redirectAttrs) { // (1) redirectAttrs.addFlashAttribute("hello", "Hello World!"); // (2) redirectAttrs.addFlashAttribute(new HelloBean("Bean Hello World!")); // (3) return "redirect:/sample/hello?complete"; // (4) } @RequestMapping(value = "hello", params = "complete") public String helloComplete() { return "sample/complete"; // (5) } - complete.html .. code-block:: html :emphasize-lines: 1-2

- HTML of created by View(complete.html) .. code-block:: html :emphasize-lines: 1-2 Hello World!
Bean Hello World!
.. tabularcolumns:: |p{0.10\linewidth}|p{0.90\linewidth}| .. list-table:: :header-rows: 1 :widths: 10 90 :class: longtable * - 項番 - 説明 * - | (1) - \ ``RedirectAttributes``\ オブジェクトを引数として受け取る。 * - | (2) - | \ ``RedirectAttributes``\ オブジェクトの\ ``addFlashAttribute``\ メソッドを呼び出し、渡したいデータを\ ``RedirectAttributes``\ オブジェクトに追加する。 | 例では、 ``hello`` という属性名で ``Hello World!`` という文字列のデータを追加している。 * - | (3) - | \ ``addFlashAttribute``\ メソッドの第一引数を省略すると値に渡したオブジェクトのクラス名の先頭を小文字にした文字列が属性名になる。 | 例では、 ``model.addFlashAttribute("helloBean", new HelloBean());`` を行ったのと同じ結果となる。 * - | (4) - | 画面(View)を直接表示せず、次の画面を表示するためのリクエストにリダイレクトする。 * - | (5) - | リダイレクト後のハンドラメソッドでは、(2)(3)で追加したデータを表示する画面のView名を返却する。 * - | (6) - | View(テンプレートHTML)側では、 ``th:text`` などの属性において${属性名}のような式を記述することできる。 | ``${}`` は変数式で、\ ``Model``\オブジェクトだけでなく\ ``RedirectAttributes``\ オブジェクトに追加したデータも取得することができる。 | 例では、取得したデータをHTMLエスケープして出力するために ``th:text`` 属性を利用し、「th:text="${hello}"」としている。 | HTMLエスケープの詳細については、 :doc:`Cross Site Scripting <../Security/XSS>` を参照されたい。 * - | (7) - | 「${属性名.JavaBeanのプロパティ名}」と記述することで\ ``RedirectAttributes``\に格納されているJavaBeanから値を取得することができる。 * - | (8) - | Thymeleafによって出力されるHTML。 .. raw:: latex \newpage .. warning:: ``Model`` に追加してもリダイレクト先にデータを渡すことはできない。 .. note:: \ ``Model``\ の\ ``addAttribute``\ メソッドに非常によく似ているが、データの生存期間が異なる。 \ ``RedirectAttributes``\ の\ ``addFlashAttribute``\ ではflash scopeというスコープにデータが格納され、 リダイレクト後の1リクエスト(PRGパターンのG)でのみ追加したデータを参照することができる。2回目以降のリクエストの時にはデータは消えている。 .. figure:: images_ApplicationLayer/applicationFlashscope.png :alt: Survival time of flush scope :width: 80% :align: center **Picture - Survival time of flush scope** | .. _controller_method_argument-redirectattributes-param-label: リダイレクト先へリクエストパラメータを渡す """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" リダイレクト先へ動的にリクエストパラメータを設定したい場合は、引数の\ ``RedirectAttributes``\ オブジェクトに渡したい値を追加する。 .. code-block:: java :emphasize-lines: 4 @RequestMapping("hello") public String hello(RedirectAttributes redirectAttrs) { String id = "aaaa"; redirectAttrs.addAttribute("id", id); // (1) // must not return "redirect:/sample/hello?complete&id=" + id; return "redirect:/sample/hello?complete"; } .. tabularcolumns:: |p{0.10\linewidth}|p{0.90\linewidth}| .. list-table:: :header-rows: 1 :widths: 10 90 * - 項番 - 説明 * - | (1) - | 属性名にリクエストパラメータ名、属性値にリクエストパラメータの値を指定して、\ ``RedirectAttributes``\ オブジェクトの\ ``addAttribute``\ メソッドを呼び出す。 | 上記例では、 ``/sample/hello?complete&id=aaaa`` にリダイレクトされる。 .. warning:: 上記例ではコメント化しているが、``return "redirect:/sample/hello?complete&id=" + id;``\ と結果は同じになる。 ただし、 ``RedirectAttributes``\ オブジェクトの\ ``addAttribute``\ メソッドを用いるとURIエンコーディングも行われるので、 動的に埋め込むリクエストパラメータについては、**返り値のリダイレクトURLとして組み立てるのではなく、必ずaddAttributeメソッドを使用してリクエストパラメータに設定すること。** 動的に埋め込まないリクエストパラメータ(上記例だと"complete")については、返り値のリダイレクトURLに直接指定してよい。 | .. _controller_method_argument-redirectattributes-path-label: リダイレクト先URLのパスに値を埋め込む """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" リダイレクト先URLのパスに動的に値を埋め込みたい場合は、リクエストパラメータの設定と同様引数の\ ``RedirectAttributes``\ オブジェクトに埋め込みたい値を追加する。 .. code-block:: java :emphasize-lines: 4,6 @RequestMapping("hello") public String hello(RedirectAttributes redirectAttrs) { String id = "aaaa"; redirectAttrs.addAttribute("id", id); // (1) // must not return "redirect:/sample/hello/" + id + "?complete"; return "redirect:/sample/hello/{id}?complete"; // (2) } .. tabularcolumns:: |p{0.10\linewidth}|p{0.90\linewidth}| .. list-table:: :header-rows: 1 :widths: 10 90 * - 項番 - 説明 * - | (1) - | 属性名とパスに埋め込みたい値を指定して、\ ``RedirectAttributes``\ オブジェクトの\ ``addAttribute``\ メソッドを呼び出す。 * - | (2) - | リダイレクトURLの埋め込みたい箇所に「{属性名}」のパス変数を指定する。 | 上記例では、 ``/sample/hello/aaaa?complete`` にリダイレクトされる。 .. warning:: 上記例ではコメント化しているが、``"redirect:/sample/hello/" + id + "?complete";``\ と結果は同じになる。 ただし、 ``RedirectAttributes``\ オブジェクトの\ ``addAttribute``\ メソッドを用いるとURLエンコーディングも行われるので、 動的に埋め込むパス値については、**返り値のリダイレクトURLとして記述せずに、必ずaddAttributeメソッドを使用し、パス変数を使って埋め込むこと。** | .. _controller_method_argument-cookievalue-label: Cookieから値を取得する """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" Cookieから取得したい場合は、引数に\ ``@CookieValue``\ アノテーションを付与する。 .. code-block:: java :emphasize-lines: 2 @RequestMapping("readCookie") public String readCookie(@CookieValue("JSESSIONID") String sessionId, Model model) { // (1) // do something return "sample/readCookie"; // returns view name } .. tabularcolumns:: |p{0.10\linewidth}|p{0.90\linewidth}| .. list-table:: :header-rows: 1 :widths: 10 90 * - 項番 - 説明 * - | (1) - | \ ``@CookieValue``\ アノテーションのvalue属性には、Cookie名を指定する。 | 上記例では、Cookieから"JSESSIONID"というCookie名の値が引数sessionIdに渡る。 .. note:: ``@RequestParam``\ 同様、required属性、defaultValue属性があり、引数の型にはString型以外の指定も可能である。 詳細は、 :ref:`controller_method_argument-requestparam-label` を参照されたい。 | .. _controller_method_argument-cookiewrite-label: Cookieに値を書き込む """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" | Cookieに値を書き込む場合は、\ ``HttpServletResponse``\ オブジェクトの\ ``addCookie``\ メソッドを直接呼び出してCookieに追加する。 | Spring MVCからCookieに値を書き込む仕組みが提供されていないため(3.2.3時点)、**この場合に限り HttpServletResponse を引数に取っても良い。** .. code-block:: java :emphasize-lines: 3,5 @RequestMapping("writeCookie") public String writeCookie(Model model, HttpServletResponse response) { // (1) Cookie cookie = new Cookie("foo", "HelloWorld!"); response.addCookie(cookie); // (2) // do something return "sample/writeCookie"; } .. tabularcolumns:: |p{0.10\linewidth}|p{0.90\linewidth}| .. list-table:: :header-rows: 1 :widths: 10 90 * - 項番 - 説明 * - | (1) - Cookieを書き込むために、\ ``HttpServletResponse``\ オブジェクトを引数に指定する。 * - | (2) - | \ ``Cookie``\ オブジェクトを生成し、\ ``HttpServletResponse``\ オブジェクトに追加する。 | 上記例では、 ``foo`` というCookie名で ``HelloWorld!`` という値を設定している。 .. tip:: \ ``HttpServletResponse``\ を引数として受け取ることに変わりはないが、Cookieに値を書き込むためのクラスとして、 Spring Frameworkから\ ``org.springframework.web.util.CookieGenerator``\ というクラスが提供されている。必要に応じて使用すること。 .. note:: HTTP Cookieの処理を規定するRFC 6265では、Cookieの名前や値に一部使用できない文字があることに注意されたい。 例えば、RFC 6265に準拠した実装のTomcat 8.5では、Cookieの値にスペースを使用することができない。 `RFC 6265(HTTP State Management Mechanism)の4.1 SetCookie `_ のSyntaxを参照されたい。 | .. _controller_method_argument-pagination-label: ページネーション情報を取得する """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" | 一覧検索を行うリクエストでは、ページネーション情報が必要となる。 | ``org.springframework.data.domain.Pageable``\ (以降\ ``Pageable``\ と呼ぶ) オブジェクトをハンドラメソッドの引数に取ることで、ページネーション情報(ページ数、取得件数)を容易に扱うことができる。 詳細については :doc:`../ArchitectureInDetail/WebApplicationDetail/Pagination` を参照すること。 | .. _controller_method_argument-upload-label: アップロードファイルを取得する """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" アップロードされたファイルを取得する方法は大きく2つある。 - フォームオブジェクトに\ ``MultipartFile``\のプロパティを用意する。 - \ ``@RequestParam``\ アノテーションを付与して\ ``org.springframework.web.multipart.MultipartFile``\ をハンドラメソッドの引数とする。 詳細については :doc:`../ArchitectureInDetail/WebApplicationDetail/FileUpload` を参照されたい。 | .. _controller_method_argument-message-label: 画面に結果メッセージを表示する """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" \ ``Model``\ オブジェクト又は\ ``RedirectAttributes``\ オブジェクトをハンドラメソッドの引数として受け取り、 \ ``ResultMessages``\ オブジェクトを追加することで処理の結果メッセージを表示できる。 詳細については :doc:`../ArchitectureInDetail/WebApplicationDetail/MessageManagement` を参照されたい。 | | .. _controller_method_return-label: ハンドラメソッドの返り値について ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `ハンドラメソッドの返り値についても様々な値をとることができる `_ が、 基本的には次に挙げるもののみを使用すること。 - String(View名) 以下に、目的別に返り値の使用方法について説明する。 - :ref:`controller_method_return-html-label` - :ref:`controller_method_return-download-label` | .. _controller_method_return-html-label: HTMLを応答する """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" | ハンドラメソッドの実行結果をHTMLとして応答する場合、ハンドラメソッドの返り値は、ThymeleafのView名を返却する。 | Thymeleafを使ってHTMLを生成する場合の ``ViewResolver`` には、 ``ThymeleafViewResolver`` を用いる。 | ``ThymeleafViewResolver`` の設定例については、 :ref:`configuration-of-blank-project-label` を参照されたい。 - SampleController.java .. code-block:: java :emphasize-lines: 4 @RequestMapping("hello") public String hello() { // omitted return "sample/hello"; // (1) } .. tabularcolumns:: |p{0.10\linewidth}|p{0.90\linewidth}| .. list-table:: :header-rows: 1 :widths: 10 90 * - 項番 - 説明 * - | (1) - ハンドラメソッドの返り値として ``sample/hello`` というView名を返却した場合、 ``ThymeleafViewResolver`` の設定によりテンプレートHTMLとして ``/WEB-INF/views/sample/hello.html`` を利用して生成したHTMLが返される。 .. note:: JSPやVelocity、FreeMarkerなど他のテンプレートエンジンを使用してHTMLを生成する場合でも、ハンドラメソッドの返り値は ``sample/hello`` のままでよい。 使用するテンプレートエンジンでの差分は ``ViewResolver`` によって解決される。 | .. _controller_method_return-download-label: ダウンロードデータを応答する """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" | データベースなどに格納されているデータをダウンロードデータ(\ ``application/octet-stream``\ 等 )として応答する場合、 | レスポンスデータの生成(ダウンロード処理)を行うViewを作成し、処理を委譲することを推奨する。 | ハンドラメソッドでは、ダウンロード対象となるデータを \ ``Model``\ に追加し、ダウンロード処理を行うViewのView名を返却する。 | View名からViewを解決する方法としては、個別のViewResolverを作成する方法もあるが、ここではSpring Frameworkから提供されている\ ``BeanNameViewResolver``\ を使用する。 | ダウンロード処理の詳細については、 :doc:`../ArchitectureInDetail/WebApplicationDetail/FileDownload` を参照されたい。 - spring-mvc.xml \ ````\ 要素を使用する場合の定義例 .. code-block:: xml :emphasize-lines: 2 Spring Framework 4.1から追加された\ ````\ 要素を使用する場合の定義例 .. code-block:: xml :emphasize-lines: 2 - SampleController.java .. code-block:: java :emphasize-lines: 4 @RequestMapping("report") public String report() { // omitted return "sample/report"; // (4) } - XxxExcelView.java .. code-block:: java :emphasize-lines: 1-2 @Component("sample/report") // (5) public class XxxExcelView extends AbstractExcelView { // (6) @Override protected void buildExcelDocument(Map model, HSSFWorkbook workbook, HttpServletRequest request, HttpServletResponse response) throws Exception { HSSFSheet sheet; HSSFCell cell; sheet = workbook.createSheet("Spring"); sheet.setDefaultColumnWidth(12); // write a text at A1 cell = getCell(sheet, 0, 0); setText(cell, "Spring-Excel test"); cell = getCell(sheet, 2, 0); setText(cell, (Date) model.get("serverTime")).toString()); } } .. tabularcolumns:: |p{0.10\linewidth}|p{0.90\linewidth}| .. list-table:: :header-rows: 1 :widths: 10 90 * - 項番 - 説明 * - | (1) - \ ``BeanNameViewResolver``\ を定義する。 \ ``BeanNameViewResolver``\ は、返却されたView名に一致するBeanをアプリケーションコンテキストから探してViewを解決するクラスとなっている。 * - | (2) - Thymeleaf用の\ ``ThymeleafViewResolver``\ と併用する場合は、これらの\ ``ViewResolver``\ より、高い優先度を指定する事を推奨する。 上記例では、 "``0``" を指定することで、\ ``ThymeleafViewResolver``\ より先に\ ``BeanNameViewResolver``\によるView解決が行われる。 * - | (3) - Spring Framework 4.1から追加された\ ````\ 要素を使用して、\ ``BeanNameViewResolver``\ を定義する。 \ ````\ 要素を使用して\ ``ViewResolver``\ を定義する場合は、子要素に指定する\ ``ViewResolver``\の定義順が優先順位となる。 * - | (4) - ハンドラメソッドの返り値として ``sample/report`` というView名を返却した場合、 (5)でBean登録されたViewインスタンスによって生成されたデータがダウンロードデータとして応答される。 * - | (5) - コンポーネントの名前にView名を指定して、ViewオブジェクトをBeanとして登録する。 上記例では、 ``sample/report`` というbean名(View名)で ``x.y.z.app.views.XxxExcelView`` のインスタンスがBean登録される。 * - | (6) - Viewの実装例。 上記例では、 ``org.springframework.web.servlet.view.document.AbstractExcelView`` を継承し、Excelデータを生成するViewクラスの実装となる。 | | 処理の実装 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | **Controllerでは、業務処理の実装は行わない** という点がポイントとなる。 | 業務処理の実装はServiceで行い、Controllerでは業務処理が実装されているServiceのメソッドを呼び出す。 | 業務処理の実装の詳細については :doc:`DomainLayer` を参照されたい。 .. note:: Controllerは、基本的には画面遷移の決定などの処理のルーティングと\ ``Model``\ の設定のみ実装することに徹し、可能な限りシンプルな状態に保つこと。 この方針で統一することにより、Controllerで実装すべき処理が明確になり、開発規模が大きくなった場合でもControllerのメンテナンス性を保つことができる。 | Controllerで実装すべき処理を以下に4つ示す。 - :ref:`controller_logic_correlationcheck-label` - :ref:`controller_logic_businesslogic-label` - :ref:`controller_logic_domainobject-label` - :ref:`controller_logic_formobject-label` | .. _controller_logic_correlationcheck-label: 入力値の相関チェック """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" | 入力値に対する相関チェックは、\ ``org.springframework.validation.Validator``\ インタフェースを実装したValidationクラス、もしくは、Bean Validationで検証を行う。 | 相関チェックの実装の詳細については、:doc:`../ArchitectureInDetail/WebApplicationDetail/Validation` を参照されたい。 | 相関チェックの実装自体はControllerのハンドラメソッドで行うことはないが、相関チェックを行う\ ``Validator``\ を\ ``org.springframework.web.bind.WebDataBinder``\ に追加する必要がある。 .. code-block:: java :emphasize-lines: 2,6 @Inject PasswordEqualsValidator passwordEqualsValidator; // (1) @InitBinder protected void initBinder(WebDataBinder binder){ binder.addValidators(passwordEqualsValidator); // (2) } .. tabularcolumns:: |p{0.10\linewidth}|p{0.90\linewidth}| .. list-table:: :header-rows: 1 :widths: 10 90 * - 項番 - 説明 * - | (1) - 相関チェックを行う\ ``Validator``\ をInjectする。 * - | (2) - | Injectした\ ``Validator``\ を \ ``WebDataBinder``\ に追加する。 | \ ``WebDataBinder``\ に追加しておくことで、ハンドラメソッド呼び出し前に行われる入力チェック処理にて、(1)で追加した\ ``Validator``\ が実行され、相関チェックを行うことが出来る。 | .. _controller_logic_businesslogic-label: 業務処理の呼び出し """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" 業務処理が実装されているServiceをInjectし、InjectしたServiceのメソッドを呼び出すことで業務処理を実行する。 .. code-block:: java :emphasize-lines: 2,6 @Inject SampleService sampleService; // (1) @RequestMapping("hello") public String hello(Model model){ String message = sampleService.hello(); // (2) model.addAttribute("message", message); return "sample/hello"; } .. tabularcolumns:: |p{0.10\linewidth}|p{0.90\linewidth}| .. list-table:: :header-rows: 1 :widths: 10 90 * - 項番 - 説明 * - | (1) - | 業務処理が実装されている ``Service`` をInjectする。 * - | (2) - Injectした ``Service`` のメソッドを呼び出し、業務処理を実行する。 | .. _controller_logic_domainobject-label: ドメインオブジェクトへの値反映 """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" | 本ガイドラインでは、HTML formから送信されたデータは直接ドメインオブジェクトにバインドするのではなく、フォームオブジェクトにバインドする方法を推奨している。 | そのため、ControllerではServiceのメソッドに渡すドメインオブジェクトにフォームオブジェクトの値を反映する処理を行う必要がある。 .. code-block:: java :emphasize-lines: 4,11-12 @RequestMapping("hello") public String hello(@Validated SampleForm form, BindingResult result, Model model){ // omitted Sample sample = new Sample(); // (1) sample.setField1(form.getField1()); sample.setField2(form.getField2()); sample.setField3(form.getField3()); // ... // and more ... // ... String message = sampleService.hello(sample); // (2) model.addAttribute("message", message); // (3) return "sample/hello"; } .. tabularcolumns:: |p{0.10\linewidth}|p{0.90\linewidth}| .. list-table:: :header-rows: 1 :widths: 10 90 * - 項番 - 説明 * - | (1) - | Serviceの引数となるドメインオブジェクトを生成し、フォームオブジェクトにバインドされている値を反映する。 * - | (2) - Serviceのメソッドを呼び出し、業務処理を実行する。 * - | (3) - 業務処理から返却されたデータを \ ``Model``\ に追加する。 | ドメインオブジェクトへ値を反映する処理は、Controllerのハンドラメソッド内で実装してもよいが、コード量が多くなる場合はハンドラメソッドの可読性を考慮してHelperクラスのメソッドに処理を委譲することを推奨する。 | 以下にHelperメソッドに処理を委譲した場合の例を示す。 - SampleController.java .. code-block:: java :emphasize-lines: 2,7 @Inject SampleHelper sampleHelper; // (1) @RequestMapping("hello") public String hello(@Validated SampleForm form, BindingResult result){ // omitted String message = sampleHelper.hello(form); // (2) model.addAttribute("message", message); return "sample/hello"; } - SampleHelper.java .. code-block:: java :emphasize-lines: 6 public class SampleHelper { @Inject SampleService sampleService; public String hello(SampleForm form){ // (3) Sample sample = new Sample(); sample.setField1(form.getField1()); sample.setField2(form.getField2()); sample.setField3(form.getField3()); // ... // and more ... // ... String message = sampleService.hello(sample); return message; } } .. tabularcolumns:: |p{0.10\linewidth}|p{0.90\linewidth}| .. list-table:: :header-rows: 1 :widths: 10 90 * - 項番 - 説明 * - | (1) - ControllerにHelperクラスのオブジェクトをInjectする。 * - | (2) - InjectしたHelperクラスのメソッドを呼び出すことで、ドメインオブジェクトへの値の反映を行っている。 Helperクラスに処理を委譲することで、Controllerの実装をシンプルな状態に保つことができる。 * - | (3) - ドメインオブジェクトを生成した後にServiceクラスのメソッド呼び出し、業務処理を実行している。 .. note:: Helperクラスに処理を委譲する以外の方法として、Bean変換機能を使用する方法がある。 Bean変換機能の詳細は、:doc:`../ArchitectureInDetail/GeneralFuncDetail/Dozer` を参照されたい。 | .. _controller_logic_formobject-label: フォームオブジェクトへの値反映 """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" | 本ガイドラインでは、HTML formの項目にバインドするデータはドメインオブジェクトではなく、フォームオブジェクトを使用する方法を推奨している。 | そのため、ControllerではServiceのメソッドから返却されたドメインオブジェクトの値をフォームオブジェクトに反映する処理を行う必要がある。 .. code-block:: java :emphasize-lines: 4,5,11 @RequestMapping("hello") public String hello(SampleForm form, BindingResult result, Model model){ // omitted Sample sample = sampleService.getSample(form.getId()); // (1) form.setField1(sample.getField1()); // (2) form.setField2(sample.getField2()); form.setField3(sample.getField3()); // ... // and more ... // ... model.addAttribute(sample); // (3) return "sample/hello"; } .. tabularcolumns:: |p{0.10\linewidth}|p{0.90\linewidth}| .. list-table:: :header-rows: 1 :widths: 10 90 * - 項番 - 説明 * - | (1) - 業務処理が実装されているServiceのメソッドを呼び出し、ドメインオブジェクトを取得する。 * - | (2) - 取得したドメインオブジェクトの値をフォームオブジェクトに反映する。 * - | (3) - 表示のみ行う項目がある場合は、データを参照できるようにするために、\ ``Model``\ にドメインオブジェクトを追加する。 .. note:: 画面に表示のみ行う項目については、フォームオブジェクトに項目をもつのではなく、Entityなどのドメインオブジェクトから直接値を参照することを推奨する。 フォームオブジェクトへの値反映処理は、Controllerのハンドラメソッド内で実装してもよいが、 コード量が多くなる場合はハンドラメソッドの可読性を考慮してHelperクラスのメソッドに委譲することを推奨する。 - SampleController.java .. code-block:: java :emphasize-lines: 5 @RequestMapping("hello") public String hello(@Validated SampleForm form, BindingResult result){ // omitted Sample sample = sampleService.getSample(form.getId()); sampleHelper.applyToForm(sample, form); // (1) model.addAttribute(sample); return "sample/hello"; } - SampleHelper.java .. code-block:: java :emphasize-lines: 2 public void applyToForm(SampleForm destForm, Sample srcSample){ destForm.setField1(srcSample.getField1()); // (2) destForm.setField2(srcSample.getField2()); destForm.setField3(srcSample.getField3()); // ... // and more ... // ... } .. tabularcolumns:: |p{0.10\linewidth}|p{0.90\linewidth}| .. list-table:: :header-rows: 1 :widths: 10 90 * - 項番 - 説明 * - | (1) - ドメインオブジェクトの値をフォームオブジェクトに反映するためのメソッドを呼び出す。 * - | (2) - ドメインオブジェクトの値をフォームオブジェクトに反映するためのメソッドにて、ドメインオブジェクトの値をフォームオブジェクトに反映する。 .. note:: Helperクラスに処理を委譲する以外の方法として、Bean変換機能を使用する方法がある。 Bean変換機能の詳細は、:doc:`../ArchitectureInDetail/GeneralFuncDetail/Dozer` を参照されたい。 | | .. _formobject: フォームオブジェクトの実装 -------------------------------------------------------------------------------- フォームオブジェクトはHTML上のformを表現するオブジェクト(JavaBean)であり、以下の役割を担う。 #. **データベース等で保持している業務データを保持し、HTML formから参照できるようにする。** #. **HTML formから送信されたリクエストパラメータを保持し、ハンドラメソッドで参照できるようにする。** .. figure:: ./images_ApplicationLayer/applicationFormobject.png :width: 80% :align: center | フォームオブジェクトの実装について、以下4点に着目して説明する。 - :ref:`formobject_new-label` - :ref:`formobject_init-label` - :ref:`formobject_bindhtmlform-label` - :ref:`formobject_bindrequestparam-label` | .. _formobject_new-label: フォームオブジェクトの作成方法 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ フォームオブジェクトはJavaBeanとして作成する。 Spring Frameworkでは、HTML formから送信されたリクエストパラメータ(文字列)を、フォームオブジェクトに定義されている型に変換してからバインドする機能を提供しているため、 フォームオブジェクトに定義するフィールドの型は、\ ``java.lang.String``\ だけではなく、任意の型で定義することができる。 .. code-block:: java public class SampleForm implements Serializable { private String id; private String name; private Integer age; private String genderCode; private Date birthDate; // ommitted getter/setter } .. tip:: **Spring Frameworkから提供されている型変換を行う仕組みについて** Spring Frameworkは、以下の3つの仕組みを使って型変換を行っており、基本的な型への変換は標準でサポートされている。各変換機能の詳細については、リンク先のページを参照されたい。 * `Spring Type Conversion `_\ * `Spring Field Formatting `_\ * `java.beans.PropertyEditor implementations `_\ .. warning:: フォームオブジェクトには画面に表示のみ行う項目は保持せず、HTML formの項目のみ保持することを推奨する。 フォームオブジェクトに画面表示のみ行う項目の値を設定した場合、フォームオブジェクトをHTTPセッションオブジェクトに格納する際にメモリを多く消費する事になり、メモリ枯渇の原因になる可能性がある。 画面表示のみの項目は、Entityなどのドメイン層のオブジェクトをリクエストスコープに追加(\ ``Model.addAttribute``\ )することでテンプレートHTMLにデータを渡すことを推奨する。 | フィールド単位の数値型変換 """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" \ ``@NumberFormat``\ アノテーションを使用することでフィールド毎に数値の形式を指定することが出来る。 .. code-block:: java :emphasize-lines: 2 public class SampleForm implements Serializable { @NumberFormat(pattern = "#,#") // (1) private Integer price; // ommitted getter/setter } .. tabularcolumns:: |p{0.10\linewidth}|p{0.90\linewidth}| .. list-table:: :header-rows: 1 :widths: 10 90 * - 項番 - 説明 * - | (1) - HTML formから送信されるリクエストパラメータの数値形式を指定する。例では、patternとして ``#,#`` 形式を指定しているので、「,」でフォーマットされた値をバインドすることができる。 リクエストパラメータの値が ``1,050`` の場合、フォームオブジェクトのpriceには ``1050`` のIntegerオブジェクトがバインドされる。 ``@NumberFormat``\ アノテーションで指定できる属性は以下の通り。 .. tabularcolumns:: |p{0.10\linewidth}|p{0.10\linewidth}|p{0.80\linewidth}| .. list-table:: :header-rows: 1 :widths: 10 10 80 * - 項番 - 属性名 - 説明 * - 1. - style - 数値のスタイルを指定する。詳細は、`NumberFormat.StyleのJavadoc `_\ を参照されたい。 * - 2. - pattern - Javaの数値形式を指定する。詳細は、`DecimalFormatのJavadoc `_\ を参照されたい。 | .. _ApplicationLayer-DateTimeFormat: フィールド単位の日時型変換 """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" \ ``@DateTimeFormat``\ アノテーションを使用することでフィールド毎に日時の形式を指定することが出来る。 .. code-block:: java :emphasize-lines: 2 public class SampleForm implements Serializable { @DateTimeFormat(pattern = "yyyyMMdd") // (1) private Date birthDate; // ommitted getter/setter } .. tabularcolumns:: |p{0.10\linewidth}|p{0.90\linewidth}| .. list-table:: :header-rows: 1 :widths: 10 90 * - 項番 - 説明 * - | (1) - HTML formから送信されるリクエストパラメータの日時形式を指定する。例では、patternとして ``yyyyMMdd`` 形式を指定している。 リクエストパラメータの値が ``20131001`` の場合、フォームオブジェクトのbirthDateには 2013年10月1日のDateオブジェクトがバインドされる。 \ ``@DateTimeFormat``\ アノテーションで指定できる属性は以下の通り。 .. tabularcolumns:: |p{0.10\linewidth}|p{0.10\linewidth}|p{0.80\linewidth}| .. list-table:: :header-rows: 1 :widths: 10 10 80 * - 項番 - 属性名 - 説明 * - 1. - iso - ISOの日時形式を指定する。詳細は、`DateTimeFormat.ISOのJavadoc `_\ を参照。 * - 2. - pattern - Javaの日時形式を指定する。詳細は、`SimpleDateFormatのJavadoc `_\ を参照されたい。 * - 3. - style - | 日付と時刻のスタイルを2桁の文字列として指定する。 | 1桁目が日付のスタイル、2桁目が時刻のスタイルとなる。 | スタイルとして指定できる値は以下の値となる。 | | S : \ ``java.text.DateFormat.SHORT``\ と同じ形式となる。 | M : \ ``java.text.DateFormat.MEDIUM``\ と同じ形式となる。 | L : \ ``java.text.DateFormat.LONG``\ と同じ形式となる。 | F : \ ``java.text.DateFormat.FULL``\ と同じ形式となる。 | - : 省略を意味するスタイル。 | | 指定例及び変換例) | MM : Dec 9, 2013 3:37:47 AM | M- : Dec 9, 2013 | -M : 3:41:45 AM | Controller単位の型変換 """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" \ ``@InitBinder``\ アノテーションを使用することでController毎に型変換の定義を指定する事も出来る。 .. code-block:: java :emphasize-lines: 1,5 @InitBinder // (1) public void initWebDataBinder(WebDataBinder binder) { binder.registerCustomEditor( Long.class, new CustomNumberEditor(Long.class, new DecimalFormat("#,#"), true)); // (2) } .. code-block:: java :emphasize-lines: 1 @InitBinder("sampleForm") // (3) public void initSampleFormWebDataBinder(WebDataBinder binder) { // ... } .. tabularcolumns:: |p{0.10\linewidth}|p{0.90\linewidth}| .. list-table:: :header-rows: 1 :widths: 10 90 * - 項番 - 説明 * - | (1) - \ ``@InitBinder``\ アノテーション を付与したメソッド用意すると、バインド処理が行われる前にこのメソッドが呼び出され、デフォルトの動作をカスタマイズすることができる。 * - | (2) - 例では、Long型のフィールドの数値形式を ``#,#`` に指定しているので、「,」でフォーマットされた値をバインドすることができる。 * - | (3) - \ ``@InitBinder``\ アノテーションのvalue属性にフォームオブジェクトの属性名を指定することで、フォームオブジェクト毎にデフォルトの動作をカスタマイズすることもできる。 例では、 ``sampleForm`` という属性名のフォームオブジェクトに対するバインド処理が行われる前にメソッドが呼び出される。 | 入力チェック用のアノテーションの指定 """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" フォームオブジェクトのバリデーションは、Bean Validationを使用して行うため、フィールドの制約条件を示すアノテーションを指定する必要がある。 入力チェックの詳細は、:doc:`../ArchitectureInDetail/WebApplicationDetail/Validation` を参照されたい。 | .. _formobject_init-label: フォームオブジェクトの初期化方法 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ HTMLにバインドするフォームオブジェクトの事をform-backing beanと呼び、\ ``@ModelAttribute``\ アノテーションを使うことで結びつけることができる。 form-backing beanの初期化は、\ ``@ModelAttribute``\ アノテーションを付与したメソッドで行う。 このようなメソッドのことを本ガイドラインではModelAttributeメソッドと呼び、\ ``setUpXxxForm``\ というメソッド名で定義することを推奨する。 .. code-block:: java :emphasize-lines: 1 @ModelAttribute // (1) public SampleForm setUpSampleForm() { SampleForm form = new SampleForm(); // populate form return form; } .. code-block:: java :emphasize-lines: 1 @ModelAttribute("xxx") // (2) public SampleForm setUpSampleForm() { SampleForm form = new SampleForm(); // populate form return form; } .. code-block:: java :emphasize-lines: 3 @ModelAttribute public SampleForm setUpSampleForm( @CookieValue(value = "name", required = false) String name, // (3) @CookieValue(value = "age", required = false) Integer age, @CookieValue(value = "birthDate", required = false) Date birthDate) { SampleForm form = new SampleForm(); form.setName(name); form.setAge(age); form.setBirthDate(birthDate); return form; } .. tabularcolumns:: |p{0.10\linewidth}|p{0.90\linewidth}| .. list-table:: :header-rows: 1 :widths: 10 90 * - 項番 - 説明 * - | (1) - \ ``Model``\ に追加するための属性名は、クラス名の先頭を小文字にした値(デフォルト値)が設定される。この例では ``sampleForm`` が属性名になる。 返却したオブジェクトは、\ ``model.addAttribute(form)``\ 相当の処理が実行され\ ``Model``\ に追加される。 * - | (2) - ``Model``\ に追加するための属性名を指定したい場合は、\ ``@ModelAttribute``\ アノテーションのvalue属性に指定する。この例では ``xxx`` が属性名になる。 返却したオブジェクトは、``model.addAttribute("xxx", form)``\ 相当の処理が実行され\ ``Model``\ に追加される。 デフォルト値以外の属性名を指定した場合、ハンドラメソッドの引数としてフォームオブジェクトを受け取る時に\ ``@ModelAttribute("xxx")``\ の指定が必要になる。 * - | (3) - ModelAttributeメソッドは、ハンドラメソッドと同様に初期化に必要なパラメータを渡すこともできる。例では、\ ``@CookieValue``\ アノテーションを使用してCookieの値をフォームオブジェクトに設定している。 .. note:: フォームオブジェクトにデフォルト値を設定したい場合はModelAttributeメソッドで値を設定すること。 例の(3)ではCookieから値を取得しているが、定数クラスなどに定義されている固定値を直接設定してもよい。 .. note:: ModelAttributeメソッドはController内に複数定義することができる。各メソッドはControllerのハンドラメソッドが呼び出される前に毎回実行される。 .. warning:: ModelAttributeメソッドはリクエストごとにメソッドが実行されるため、特定のリクエストの時のみに必要なオブジェクトについてModelAttributeメソッドを使って生成すると、無駄なオブジェクトの生成及び初期化処理が行われる点に注意すること。 特定のリクエストのみで必要なオブジェクトについては、ハンドラメソッド内で生成し\ ``Model``\ に追加する方法にすること。 | .. _formobject_thymeleaf: .. _formobject_bindhtmlform-label: HTMLへのバインディング方法 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | \ ``Model``\ に追加されたフォームオブジェクトの各プロパティは、Thymeleaf + Springで提供される ``th:field`` 属性で指定することで、HTMLのinput要素にバインドすることができる。 | JSPには ```` タグを利用してフォームオブジェクトをHTML formにバインドする機能があるが、Thymeleafでは ``th:field`` 属性に ``th:object`` 属性を併用することで同様の機能を実現することができる。 .. code-block:: html .. code-block:: html :emphasize-lines: 1,2
Id :
Name :
Age :
Gender :
Birth Date :
.. tabularcolumns:: |p{0.10\linewidth}|p{0.90\linewidth}| .. list-table:: :header-rows: 1 :widths: 10 90 * - 項番 - 説明 * - | (1) - スタンダードダイアレクトが提供する属性を使用したとき、EclipseなどのIDEでの警告を抑止するため、ネームスペースを付与する。 * - | (2) - \ ``
``\ タグの ``th:object`` 属性には、\ ``Model``\ に格納されているフォームオブジェクトの属性名を指定する。 \ ``th:object``\ 属性については、 :ref:`view_thymeleaf_object-label` も参照されたい。 * - | (3) - \ ````\ タグの ``th:field`` 属性には、フォームオブジェクトのプロパティ名を指定する。 | .. _formobject_bindrequestparam-label: リクエストパラメータのバインディング方法 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ HTML formから送信されたリクエストパラメータは、フォームオブジェクトにバインドし、Controllerのハンドラメソッドの引数に渡すことができる。 .. code-block:: java :emphasize-lines: 3 @RequestMapping("hello") public String hello( @Validated SampleForm form, // (1) BindingResult result, Model model) { if (result.hasErrors()) { return "sample/input"; } // process form... return "sample/hello"; } .. code-block:: java :emphasize-lines: 10 @ModelAttribute("xxx") public SampleForm setUpSampleForm() { SampleForm form = new SampleForm(); // populate form return form; } @RequestMapping("hello") public String hello( @ModelAttribute("xxx") @Validated SampleForm form, // (2) BindingResult result, Model model) { // ... } .. tabularcolumns:: |p{0.10\linewidth}|p{0.90\linewidth}| .. list-table:: :header-rows: 1 :widths: 10 90 * - 項番 - 説明 * - | (1) - フォームオブジェクトにリクエストパラメータが反映された状態で、Controllerのハンドラメソッドの引数に渡される。 * - | (2) - ModelAttributeメソッドにて属性名を指定した場合、\ ``@ModelAttribute("xxx")``\ といった感じで、フォームオブジェクトの属性名を明示的に指定する必要がある。 .. warning:: ModelAttributeメソッドで指定した属性名とメソッドの引数で指定した属性名が異なる場合、ModelAttributeメソッドで生成したインスタンスとは別のインスタンスが生成されるので注意が必要。 ハンドラメソッドで属性名の指定を省略した場合、クラス名の先頭を小文字にした値が属性名として扱われる。 | バインディング結果の判定 """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" HTML formから送信されたリクエストパラメータをフォームオブジェクトにバインドする際に発生したエラー(入力チェックエラーも含む)は、 \ ``org.springframework.validation.BindingResult``\ に格納される。 .. code-block:: java :emphasize-lines: 4,6 @RequestMapping("hello") public String hello( @Validated SampleForm form, BindingResult result, // (1) Model model) { if (result.hasErrors()) { // (2) return "sample/input"; } // ... } .. tabularcolumns:: |p{0.10\linewidth}|p{0.90\linewidth}| .. list-table:: :header-rows: 1 :widths: 10 90 * - 項番 - 説明 * - | (1) - フォームオブジェクトの直後に\ ``BindingResult``\ を宣言すると、フォームオブジェクトへのバインド時のエラーと入力チェックエラーを参照することができる。 * - | (2) - \ ``BindingResult.hasErrors()``\ を呼び出すことで、フォームオブジェクトの入力値のエラー有無を判定することができる。 フィールドエラーの有無、グローバルエラー(相関チェックエラーなどのクラスレベルのエラー)の有無を個別に判定することもできるので、要件に応じて使い分けること。 .. tabularcolumns:: |p{0.10\linewidth}|p{0.40\linewidth}|p{0.50\linewidth}| .. list-table:: :header-rows: 1 :widths: 10 40 50 * - 項番 - メソッド - 説明 * - 1. - ``hasGlobalErrors()`` - グローバルエラーの有無を判定するメソッド * - 2. - ``hasFieldErrors()`` - フィールドエラーの有無を判定するメソッド * - 3. - ``hasFieldErrors(String field)`` - 指定したフィールドのエラー有無を判定するメソッド | .. _view: Viewの実装 -------------------------------------------------------------------------------- Viewは以下の役割を担う。 #. | **クライアントに応答するレスポンスデータ(HTML)を生成する。** | Viewはモデル(フォームオブジェクトやドメインオブジェクトなど)から必要なデータを取得し、クライアントが描画するために必要な形式でレスポンスデータを生成する。 | .. _ApplicationLayerImplementOfThymeleaf: ThymeleafのテンプレートHTMLの実装 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | クライアントにHTMLを応答する場合はThymeleafを使用する。そのために、ViewはHTML形式で実装する。 | Thymeleafによって生成されたHTMLを呼び出すための ``ViewResolver`` は、Thymeleaf + Springより提供されている ``ThymeleafViewResolver`` を使用する。 | ``ViewResolver`` の設定方法については、 :ref:`configuration-of-blank-project-label` を参照されたい。 以下に、基本的なテンプレートHTMLの実装方法について説明する。 - :ref:`view_thymeleaf_namespace-label` - :ref:`view_thymeleaf_text-label` - :ref:`view_thymeleaf_numbers-label` - :ref:`view_thymeleaf_dates-label` - :ref:`view_thymeleaf_requesturl-label` - :ref:`view_thymeleaf_message-label` - :ref:`view_thymeleaf_textcombine-label` - :ref:`view_thymeleaf_conditional-label` - :ref:`view_thymeleaf_if-label` - :ref:`view_thymeleaf_each-label` - :ref:`view_thymeleaf_object-label` - :ref:`view_thymeleaf_with-label` - :ref:`view_thymeleaf_preprocessing-label` - :ref:`view_thymeleaf_form-label` - :ref:`view_thymeleaf_errors-label` - :ref:`view_thymeleaf_resultmessages-label` - :ref:`view_thymeleaf_codelist-label` - :ref:`view_thymeleaf_pagination-label` - :ref:`view_thymeleaf_authorization-label` 本章では、ThymeleafおよびThymeleaf + Springのダイアレクト、ならびにThymeleafのSpring Security連携用ダイアレクトで提供されている代表的な属性やオブジェクトの使い方を説明しているが、 全てについて説明はしていないので、詳細な使い方については、それぞれのドキュメントを参照すること。 .. tabularcolumns:: |p{0.10\linewidth}|p{0.30\linewidth}|p{0.60\linewidth}| .. list-table:: :header-rows: 1 :widths: 10 30 60 * - 項番 - 説明 - ドキュメント * - 1. - Thymeleafのダイアレクト - - ``_\ * - 2. - Thymeleaf + Springのダイアレクト - - ``_\ * - 3. - ThymeleafのSpring Security連携用ダイアレクト - - ``_\ | .. _view_thymeleaf_namespace-label: Thymeleafのネームスペースを設定する """"""""""""""""""""""""""""""""""""""""""""""""" | Thymeleafを使用してテンプレートHTMLを作成する場合は、 ``th:text`` のようなThymeleaf独自の属性を使用する必要があるため、Thymeleafのネームスペースを付与する。 | 通常は、テンプレートHTMLのどこでもThymeleaf独自の属性を使用できるように、 ```` 要素にネームスペースを付与することを推奨する。 .. code-block:: html :emphasize-lines: 1 .. note:: ネームスペースはXHTMLの標準で定義された以外の要素・属性を使用する場合に付与するものであり、HTML5では本来不要なものである。(事実、ネームスペースを付与しなくとも、テンプレートの解釈に問題は生じない。) ただし、HTML5であっても標準で定義された以外の要素・属性を使用すると、EclipseなどのIDEで警告が出力されるため、これを抑止するためにネームスペースを付与すべきである。 なお、テンプレートを解釈して出力されるHTMLからは、ネームスペース( ``xmlns:th`` )は削除される。 .. note:: 本ガイドラインでは解説しないが、HTML5に準拠する形でThymeleafを使用することも可能である。 具体的には、HTML5で独自の属性を使用する場合は属性名に ``data-`` をつけるが、Thymeleafでもこれを使用して ``data-th-text`` のように属性を記述することができる。 詳細については、"The Standard Dialect"の `3.1 A multi-language welcome `_\ を参照されたい。 | .. _view_thymeleaf_text-label: モデルに格納されている値を表示する """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" | Thymeleafで動的な値をHTMLに表示するには、 ``th:text`` 属性を使用する。 | モデル(フォームオブジェクトやドメインオブジェクトなど)に格納されている値をHTMLに表示する場合、 ``th:text`` 属性に変数式 ``${}`` を使用すれば良い。 | なお、式にはThymeleaf Standard Expressionと呼ばれるEL式を利用して、オブジェクトやプロパティを指定することができる。 - SampleController.java .. code-block:: java :emphasize-lines: 3 @RequestMapping("hello") public String hello(Model model) { model.addAttribute(new HelloBean("Bean Hello World!")); // (1) return "sample/hello"; // returns view name } - hello.html .. code-block:: html :emphasize-lines: 1 - HTML created by View(hello.html) .. code-block:: html :emphasize-lines: 1 Bean Hello World! .. tabularcolumns:: |p{0.10\linewidth}|p{0.90\linewidth}| .. list-table:: :header-rows: 1 :widths: 10 90 * - 項番 - 説明 * - | (1) - | \ ``Model``\ オブジェクトに \ ``HelloBean``\ オブジェクトを追加する。 * - | (2) - | View(テンプレートHTML)側では、 ``th:text`` などの属性において${属性名}のような式を記述することできる。 | ``${}`` は変数式で、\ ``Model``\ オブジェクトに追加したデータを取得することができる。 | 例では、取得したデータをHTMLエスケープして出力するために ``th:text`` 属性を利用し、「th:text="${hello}"」としている。 | XSS対策のため必ずHTMLエスケープを行うことを推奨する。詳細については、 :doc:`Cross Site Scripting <../Security/XSS>` を参照されたい。 | .. _view_thymeleaf_numbers-label: モデルに格納されている数値を表示する """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" 数値型の値をフォーマットして出力する場合、Thymeleafの ``#numbers`` オブジェクトを使用する。 ``#numbers`` オブジェクトは、数値のフォーマットを行う以下のようなメソッドをもつ。 .. tabularcolumns:: |p{0.05\linewidth}|p{0.15\linewidth}|p{0.40\linewidth}|p{0.40\linewidth}| .. list-table:: :header-rows: 1 :widths: 5 15 40 40 :class: longtable * - 項番 - メソッド名 - 説明 - 使用例 * - 1. - formatInteger - 整数値にフォーマットする。 引数として、以下のパターンをとる。 * 整数型、最小桁数 * 整数型、最小桁数、千の位の区切り文字 - .. code-block:: text ${#numbers.formatInteger(num, 1, 'COMMA')} * - 2. - formatDecimal - 小数値にフォーマットする。 引数として、以下のパターンをとる。 * 浮動小数点型、最小桁数、小数桁数 * 浮動小数点型、最小桁数、小数桁数、小数点の文字 * 浮動小数点型、最小桁数、千の位の区切り文字、小数桁数、小数点の文字 - .. code-block:: text ${#numbers.formatDecimal(num, 1, 'COMMA', 2, 'POINT')} * - 3. - formatPercent - パーセント表示にフォーマットする。 引数として、以下のパターンをとる。 * 浮動小数点型 * 浮動小数点型、最小桁数、小数桁数 - .. code-block:: text ${#numbers.formatPercent(num, 1, 2)} .. note:: 千の位の区切り文字、小数点の文字としては、以下のものが指定できる。 * 'POINT' - ピリオド "``.``" * 'COMMA' - カンマ "``,``" * 'WHITESPACE' - 半角スペース * 'NONE' - 区切り文字なし * 'DEFAULT' - ロケールに依存 指定しなかった場合、千の位の区切り文字には'NONE'が、小数点には'POINT'が設定される。 | 例えば小数を表示する際には、 ``#numbers.formatDecimal`` メソッドを使用してフォーマットする。 .. code-block:: html :emphasize-lines: 1 .. tabularcolumns:: |p{0.10\linewidth}|p{0.90\linewidth}| .. list-table:: :header-rows: 1 :widths: 10 90 * - 項番 - 説明 * - | (1) - | 取得した値を ``#numbers.formatDecimal`` メソッドでフォーマットし、変数式 ``${}`` に指定する。 | 例では、最小桁数に1を、小数桁数に2を、小数点に ``POINT`` (ピリオド)を、表示するフォーマットに設定している。 | 仮に ``helloBean.numberItem`` の値が ``1.2`` の場合、画面には ``1.20`` が出力される。 .. note:: ``#numbers`` は、配列やリストなどを対象にフォーマットを行うことも可能である。 詳細については、"The Standard Dialect"の `19 Appendix B: Expression Utility Objects 内のNumbers `_\ を参照されたい。 | .. _view_thymeleaf_dates-label: モデルに格納されている日時を表示する """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" 日時型の値をフォーマットして出力する場合、Thymeleafの ``#dates`` オブジェクト、あるいは ``#calendars`` オブジェクトを使用する。 ``java.util.Date`` オブジェクトのフォーマットを行う場合は、 ``#dates`` オブジェクトの ``format`` メソッドを利用する。 .. code-block:: html :emphasize-lines: 1 .. tabularcolumns:: |p{0.10\linewidth}|p{0.90\linewidth}| .. list-table:: :header-rows: 1 :widths: 10 90 * - 項番 - 説明 * - | (1) - | 取得した値を ``#dates.format`` メソッドでフォーマットし、変数式 ``${}`` に指定する。 | 例では、日付を ``yyyy-MM-dd`` 形式でフォーマットしている。 | 仮に ``helloBean.dateItem`` の値が2013年3月2日の場合、画面には ``2013-03-02`` が出力される。 .. note:: ``#dates`` は、配列やリストを対象にフォーマットを行うことも可能である。 詳細については、"The Standard Dialect"の `19 Appendix B: Expression Utility Objects 内のDates `_\ を参照されたい。 | ``java.util.Calendar`` オブジェクトのフォーマットを行う場合は、 ``#calendars`` オブジェクトの ``format`` メソッドを利用する。 .. code-block:: html :emphasize-lines: 1 .. tabularcolumns:: |p{0.10\linewidth}|p{0.90\linewidth}| .. list-table:: :header-rows: 1 :widths: 10 90 * - 項番 - 説明 * - | (1) - | 取得した値を ``#calendars.format`` メソッドでフォーマットし、変数式 ``${}`` に指定する。 | 例では、日付を ``yyyy-MM-dd`` 形式でフォーマットしている。 | 仮に ``helloBean.calendarItem`` の値が2013年3月2日の場合、画面には ``2013-03-02`` が出力される。 .. note:: ``#calendars`` は、配列やリストを対象にフォーマットを行うことも可能である。 詳細については、"The Standard Dialect"の `19 Appendix B: Expression Utility Objects 内のCalendars `_\ を参照されたい。 | .. _view_thymeleaf_requesturl-label: リクエストURLを生成する """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" HTMLの\ ````\ 要素の\ ``action``\ 属性や\ ````\ 要素の\ ``href``\ 属性、\ ````\ 要素の\ ``src``\ 属性などに対してURLを出力する場合は、Thymeleafの\ ``th:action``\属性、\ ``th:href``\属性、\ ``th:src``\属性を使用する。 これらの属性では、以下のいずれかの方法によって生成されたリクエストURL(Controllerのメソッドを呼び出すためのURL)を設定する。 * ThymeleafのリンクURL式 ``@{}`` を使用してリクエストURLを組み立てる * Thymeleaf + Springの ``#mvc.url`` メソッドを使用してリクエストURLを組み立てる .. note:: どちらの方法を使用してもよいが、一つのアプリケーションの中で混在して使用することは、 保守性を低下させる可能性があるので避けた方がよい。 | | 以降の説明で使用するControllerのメソッドの実装サンプルを示す。 | 以降の説明では、以下に示すメソッドを呼び出すためのリクエストURLを生成するための実装方法について説明する。 .. code-block:: java package com.example.app.hello; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; @RequestMapping("hello") @Controller public class HelloController { // (1) @RequestMapping({"", "/"}) public String hello() { return "hello/home"; } } .. tabularcolumns:: |p{0.10\linewidth}|p{0.90\linewidth}| .. list-table:: :header-rows: 1 :widths: 10 90 * - 項番 - 説明 * - | (1) - | このメソッドに割り当てられるリクエストURLは、\ ``{コンテキストパス}/hello``\ となる。 | **ThymeleafのリンクURL式を使用してリクエストURLを組み立てる** まず、ThymeleafのリンクURL式 ``@{}`` を使用してリクエストURLを組み立てる方法について説明する。 * コンテキストルートからの相対パスを指定する .. code-block:: html .. tabularcolumns:: |p{0.10\linewidth}|p{0.90\linewidth}| .. list-table:: :header-rows: 1 :widths: 10 90 * - 項番 - 説明 * - | (2) - | リンクURL式 ``@{}`` に"``/``"から始まるパスを記述すると、記述したパスをコンテキストルートに付与したURLが生成される。 * 現在のパスからの相対パスを指定する .. code-block:: html .. tabularcolumns:: |p{0.10\linewidth}|p{0.90\linewidth}| .. list-table:: :header-rows: 1 :widths: 10 90 * - 項番 - 説明 * - | (3) - | リンクURL式 ``@{}`` にパスを記述すると、現在のパスから見た相対的なURLが生成される。 .. note:: リンクURL式 ``@{}`` は、コンテキストルートからの相対パスやページ現在のパスからの相対パスを生成するほか、サーバールートからの相対パス、プロトコルからの相対パスも生成できる。 詳細については、"The Standard Dialect"の `4.4 Link URLs `_\ を参照されたい。 | リンクURL式には、パスの一部またはパラメータとして変数を埋め込むことが可能である。 * パスの一部に変数を埋め込む .. code-block:: html
.. tabularcolumns:: |p{0.10\linewidth}|p{0.90\linewidth}| .. list-table:: :header-rows: 1 :widths: 10 90 * - 項番 - 説明 * - | (4) - | リンクURL式 ``@{}`` のパス内で変数式を使うこともできる。 | 例では、パスの ``{userId}`` の部分に変数 ``${user.id}`` の値が代入され、 ``/user/3/details`` といったパスが生成される。 * パラメータとして変数を埋め込む .. code-block:: html
.. tabularcolumns:: |p{0.10\linewidth}|p{0.90\linewidth}| .. list-table:: :header-rows: 1 :widths: 10 90 * - 項番 - 説明 * - | (4) - | リンクURL式 ``@{}`` のパス内にパラメータを指定することができる。 | 例では、変数 ``${user.id}`` の値が代入され、 ``/user/details?userId=3`` といったパスが生成される。 | **Thymeleaf + Springの#mvc.urlメソッドを使用してリクエストURLを組み立てる** つぎに、Thymeleaf + Springの\ ``#mvc.url``\メソッドを使用してリクエストURLを組み立てる方法について説明する。 \ ``#mvc.url``\ メソッドを使用すると、Controllerのメソッドのメタ情報(メソッドシグネチャやアノテーションなど)と連携して、 リクエストURLを組み立てる事ができる。 .. code-block:: html
.. tabularcolumns:: |p{0.10\linewidth}|p{0.90\linewidth}| .. list-table:: :header-rows: 1 :widths: 10 90 * - 項番 - 説明 * - | (4) - | \ ``#mvc.url``\ メソッドの引数には、呼び出すControllerのメソッドに割り振られているリクエストマッピング名を指定する。 | \ ``#mvc.url``\ メソッドからは、リクエストURLを組み立てるクラス(\ ``Mvc.MethodArgumentBuilderWrapper``\ )のオブジェクトが返却される。 | \ ``Mvc.MethodArgumentBuilderWrapper``\ クラスは、ラップしている\ ``MvcUriComponentsBuilder.MethodArgumentBuilder``\オブジェクトと同等の機能をもつ。 | \ ``MvcUriComponentsBuilder.MethodArgumentBuilder``\クラスには、 * \ ``arg``\ メソッド * \ ``build``\ メソッド * \ ``buildAndExpand``\ メソッド | が用意されており、それぞれ、以下の役割を持つ。 * \ ``arg``\ メソッドは、Controllerのメソッドの引数に渡す値を指定するためのメソッドである。 * \ ``build``\ メソッドは、リクエストURLを生成するためのメソッドである。 * \ ``buildAndExpand``\ メソッドは、Controllerのメソッドの引数として宣言されていない動的な部分(パス変数など)に埋め込む値を指定した上で、リクエストURLを生成するためのメソッドである。 | 上記例では、リクエストURLが静的なURLであるため、\ ``build``\ メソッドのみを呼び出してリクエストURLを生成している。 | リクエストURLが動的なURL(パス変数やクエリ文字列が存在するURL)の場合は、\ ``arg``\ メソッドや\ ``buildAndExpand``\ メソッドを呼び出す必要がある。 | \ ``arg``\ メソッドと\ ``buildAndExpand``\ メソッドの具体的な使用例については、 | 「\ `Spring Framework Reference Documentation(Building URIs to Controllers and methods from views) `_\ 」を参照されたい。 .. note:: **リクエストマッピング名について** リクエストマッピング名は、デフォルト実装(\ ``org.springframework.web.servlet.mvc.method.RequestMappingInfoHandlerMethodMappingNamingStrategy``\ の実装)では、 「クラス名の大文字部分(クラスの短縮名) + "\ ``#``\" + メソッド名」となる。 リクエストマッピング名は重複しないようにする必要がある。 名前が重複してしまった場合は、\ ``@RequestMapping``\ アノテーションの\ ``name``\ 属性に一意となる名前を指定する必要がある。 Controllerのメソッドに割り当てられたリクエストマッピング名を確認したい場合は、 \ :file:`logback.xml`\ に以下の設定を追加すればよい。 .. code-block:: xml 上記設定を行った後に再起動すると、以下のようなログが出力されるようになる。 .. code-block:: text date:2014-12-09 18:34:29 thread:RMI TCP Connection(2)-127.0.0.1 X-Track: level:TRACE logger:o.s.w.s.m.m.a.RequestMappingHandlerMapping message:Mapping name=HC#hello | .. _view_thymeleaf_message-label: メッセージを表示する """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" | プロパティファイルからメッセージを取得し表示する場合、Thymeleafのメッセージ式 ``#{}`` または変数式で ``#messages`` オブジェクトを使用する。 | 単純なメッセージの表示にはメッセージ式を使用することを推奨する。 | なお、画面名、項目名、ガイダンス用のメッセージなどについては、国際化の必要がない場合はHTMLに直接記載してもよい。 | ただし、国際化の必要がある場合はプロパティファイルからメッセージを取得し表示することを推奨する。 | 詳細は、 :doc:`../ArchitectureInDetail/WebApplicationDetail/Internationalization` を参照されたい。 * properties .. code-block:: properties # (1) label.orderStatus=注文ステータス .. tabularcolumns:: |p{0.10\linewidth}|p{0.90\linewidth}| .. list-table:: :header-rows: 1 :widths: 10 90 * - 項番 - 説明 * - | (1) - | プロパティファイルにラベルの値を定義する。 * テンプレートHTML * メッセージ式 ``#{}`` を使う場合 .. code-block:: html .. tabularcolumns:: |p{0.10\linewidth}|p{0.90\linewidth}| .. list-table:: :header-rows: 1 :widths: 10 90 * - 項番 - 説明 * - | (2) - | メッセージ式にプロパティファイルのキー名を指定するとキー名に一致するプロパティ値が表示される。 * 変数式で ``#messages`` オブジェクトを使う場合 .. code-block:: html .. tabularcolumns:: |p{0.10\linewidth}|p{0.90\linewidth}| .. list-table:: :header-rows: 1 :widths: 10 90 * - 項番 - 説明 * - | (3) - | 変数式で ``#messages.msg`` メソッドにプロパティファイルのキー名を指定するとキー名に一致するプロパティ値が表示される。 .. note:: メッセージ式で指定したキーに該当するメッセージが存在しない場合は、 ``??label.orderStatus??`` のようにメッセージキーが返却される。 しかし例えば、メッセージが存在しない場合にはデフォルトメッセージを表示したい場合など、メッセージキーが返却されると判定が複雑になってしまう。 このような場合は、 ``#messages.msgOrNull`` メソッドと、後述するデフォルト式を利用することで、簡潔に記述することができる。 以下にコード例を示す。 .. code-block:: html デフォルト式については、 :ref:`view_thymeleaf_conditional-label` も参照されたい。 | .. _view_thymeleaf_textcombine-label: 文字列を組み立てる """"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" | リテラルやモデルに格納されている値などを組み合わせた文字列を出力したい場合、 "``+``" 演算子やパイプ( "``|``" )を使用する。 | 単純に文字列を結合するのであれば、可読性の高いパイプの使用を推奨する。 | ただしパイプ内では計算や条件判定ができないため、これらが必要な場合は "``+``" 演算子を使用すると良い。 - hello.html .. code-block:: html :emphasize-lines: 1-2 - HTML created by View(hello.html) .. code-block:: html :emphasize-lines: 1-2 Message : Bean Hello World! Message : Bean Hello World! .. tabularcolumns:: |p{0.10\linewidth}|p{0.90\linewidth}| .. list-table:: :header-rows: 1 :widths: 10 90 * - 項番 - 説明 * - | (1) - | パイプ( "``|``" )で囲むことにより、結合された文字列を生成できる。 * - | (2) - | "``+``" 演算子を利用すると、変数式やシングルクォート ``''`` で囲んだテキストを結合できる。 * - | (3) - | HTMLの出力例。出力されるHTMLは2行とも同じになる。 | テキストの結合方法の混在は可読性を低下させるため、ここでは、パイプによる結合を推奨する。 | .. _view_thymeleaf_conditional-label: 条件を判定する """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" | ThymeleafのテンプレートHTMLでは、式内で条件を判定し、その結果によって異なる動作をさせることができる。 | 判定された結果は、 ``true`` または ``false`` で返却される。 | 演算子の詳細については、"The Standard Dialect"の `4 Standard Expression Syntax `_\ を参照されたい。 演算子を用いた条件の判定の結果によって、2つの式のどちらかを選択する式を条件式という。 .. code-block:: html :emphasize-lines: 1 .. tabularcolumns:: |p{0.10\linewidth}|p{0.90\linewidth}| .. list-table:: :header-rows: 1 :widths: 10 90 * - 項番 - 説明 * - | (1) - | ``${user.age} != null`` という条件の判定が ``true`` であった場合、 ``${user.age}`` を表示する。 | ``false`` であった場合は、"no age specified"という文字列を表示する。 | このように、条件式は「条件 ? 式 : 式」と表されるため、3項演算子とも呼ばれる。 条件式には、等価演算子のほかに、算術演算子や比較演算子も利用できる。 .. code-block:: html :emphasize-lines: 1 .. tabularcolumns:: |p{0.10\linewidth}|p{0.90\linewidth}| .. list-table:: :header-rows: 1 :widths: 10 90 * - 項番 - 説明 * - | (1) - | ``${user.age}`` が12以上であった場合'adult'を、12未満であった場合'child'を表示する。 .. note:: 数値の配列やリストに対して、合計値や平均値を取得したい場合、 ``#aggregates`` オブジェクトを利用できる。 詳細については、"The Standard Dialect"の `19 Appendix B: Expression Utility Objects 内のAggregates `_\ を参照されたい。 条件の判定の結果によって処理が変わる式としては、条件式のほかに、デフォルト式と呼ばれるものもある。 .. code-block:: html :emphasize-lines: 1 .. tabularcolumns:: |p{0.10\linewidth}|p{0.90\linewidth}| .. list-table:: :header-rows: 1 :widths: 10 90 * - 項番 - 説明 * - | (2) - | ``?:`` 演算子は、左式 (例では ``${user.age}`` )が ``null`` であったときに限って右式(例では"no age specified")を選択し、それ以外の場合は左式を選択する。 | これは上の3項演算子と同じ機能をもっており、このようにデフォルト式は簡単にnullチェックを行うことができる。 .. note:: No Operation Token( "``_``" )は、記述した属性で何もしないことを指示するものである。 以下のコード例において、 ``user.age`` が ``null`` だった場合、 ``th:text`` 属性は処理されず、'no age specified'が表示される。 .. code-block:: html no age specified | .. _view_thymeleaf_if-label: 条件によって表示を切り替える """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" モデルが保持する値によって表示を切り替えたい場合は、Thymeleafの ``th:if`` 属性または ``th:switch`` 属性を使用する。 .. note:: ``th:if`` 属性の逆の機能として ``th:unless`` 属性があるが、 ``th:if`` と ``th:unless`` の混在は可読性を低下させる場合があるため、いずれかへの統一を推奨する。 本ガイドラインにおいては、 ``th:if`` に統一している。 * ``th:if`` 属性を使用して表示を切り替える。 .. code-block:: html :emphasize-lines: 1
.. tabularcolumns:: |p{0.10\linewidth}|p{0.90\linewidth}| .. list-table:: :header-rows: 1 :widths: 10 90 * - 項番 - 説明 * - | (1) - | ``th:if`` 属性に条件を指定する。 | 条件式が ``true`` の場合は ``th:if`` を記述した要素の表示処理が実行され、 ``false`` の場合は要素ごと削除され、表示処理は実行されない。 | 例では、注文ステータスが ``'complete'`` ではない場合に ``
`` 要素の表示処理が実行され、 | 注文ステータスが ``'complete'`` であった場合には ``
`` 要素が削除され、表示処理は実行されない。 | また、 ``th:if`` 属性の逆の機能として ``th:unless`` 属性があるが、 ``th:if`` と ``th:unless`` の混在は可読性を低下させる場合があるため、いずれかへの統一を推奨する。 | 本ガイドラインにおいては、 ``th:if`` に統一している。 * ``th:switch`` 属性を使用して表示を切り替える。 .. code-block:: html
.. tabularcolumns:: |p{0.10\linewidth}|p{0.90\linewidth}| .. list-table:: :header-rows: 1 :widths: 10 90 * - 項番 - 説明 * - | (1) - | ``th:switch`` 属性に変数値を指定する。 * - | (2) - | ``th:case`` 属性に条件を指定する。 ``th:case`` で指定した値が ``th:switch`` で指定した変数値と等しかった場合、その要素の表示処理が実行される。 | ``th:case`` 属性は上から順に評価され、最初に合致した条件における表示処理が実行される。 * - | (3) - | いずれの条件にも合致しなかった場合に実行したい表示処理は、 ``th:case="*"`` を最後に指定して記述する。 | .. _view_thymeleaf_each-label: コレクションの要素に対して表示処理を繰り返す """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" モデルが保持するコレクションや配列、Map等に対して表示処理を繰り返したい場合は、Thymeleafの ``th:each`` 属性を使用する。 .. code-block:: html :emphasize-lines: 6-8
Name Address
.. tabularcolumns:: |p{0.10\linewidth}|p{0.90\linewidth}| .. list-table:: :header-rows: 1 :widths: 10 90 * - 項番 - 説明 * - | (1) - | ``th:each`` 属性の右項にコレクションを指定し、左項にはコレクション内の各オブジェクトを格納する変数名を指定する。 | ```` 要素は、コレクション内のオブジェクトごとに繰り返し処理される。 * - | (2) - | ``th:each`` 属性の左項に指定した変数に格納されているオブジェクトから変数値を取得している。 .. note:: コレクション内のオブジェクトに対してインデックスなどを取りたい場合は、次のようにテンプレートHTMLを実装する。 .. code-block:: html :emphasize-lines: 6-8
No Name Address
.. tabularcolumns:: |p{0.10\linewidth}|p{0.90\linewidth}| .. list-table:: :header-rows: 1 :widths: 10 90 * - 項番 - 説明 * - | (1) - | ``th:each`` 属性の左項に、要素の番号を格納する変数を2つ目に指定する。 * - | (2) - | ``th:each`` 属性の左項で2つ目に指定した変数から、現在処理を行っている要素の位置を取得している。countは、要素の位置を1始まりで取得している。 | count以外の属性については、"The Standard Dialect"の `6.2 Keeping iteration status `_\ を参照されたい。 .. note:: ``th:each`` 属性の詳細は、"The Standard Dialect"の `6 Iteration basis `_\ を参照されたい。 | .. _view_thymeleaf_object-label: オブジェクトのプロパティを省略して指定する """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" Thymeleafの ``th:object`` 属性を用いると、オブジェクト名を省略してプロパティを指定することができる。 .. code-block:: html :emphasize-lines: 1
.. tabularcolumns:: |p{0.10\linewidth}|p{0.90\linewidth}| .. list-table:: :header-rows: 1 :widths: 10 90 * - 項番 - 説明 * - | (1) - | ``th:object``\ 属性にオブジェクトを変数式 ``${}`` で指定する。 * - | (2) - | オブジェクトのプロパティを選択変数式 ``*{}`` で指定する。これは、変数式を用いて ``th:text="${helloBean.message}`` と指定するのと同じ結果になる。 | .. _view_thymeleaf_with-label: ローカル変数を定義する """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" | Thymeleafでは、テンプレートHTMLの特定の要素に定義され、その要素と要素配下のみで評価可能な変数のことをローカル変数と呼ぶ。 | このローカル変数を定義する場合、 ``th:with`` 属性を使用する。 .. code-block:: html :emphasize-lines: 1
.. tabularcolumns:: |p{0.10\linewidth}|p{0.90\linewidth}| .. list-table:: :header-rows: 1 :widths: 10 90 * - 項番 - 説明 * - | (1) - | \ ``th:with``\ 属性に"変数名=値"の形式で設定すると、指定した値をもつローカル変数を定義できる。 | 例では、 ``localvar`` という名の変数を定義し、 ``Hello, (ユーザー名)`` という文字列を代入している。 | このローカル変数は、 ``th:with`` 属性を指定した ``
`` 要素とその要素配下でのみ有効となる。 * - | (2) - | 変数式 ``${}`` にローカル変数 ``localvar`` を指定する。 | .. _view_thymeleaf_preprocessing-label: プリプロセッシング """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" | ThymeleafのテンプレートHTMLでは通常、式は左から順に評価される。 | そのため、先に処理したい部分が式内にある場合は特殊な記法が必要となり、それはプリプロセッシングと呼ばれている。 .. code-block:: html :emphasize-lines: 1
.. tabularcolumns:: |p{0.10\linewidth}|p{0.90\linewidth}| .. list-table:: :header-rows: 1 :widths: 10 90 * - 項番 - 説明 * - | (1) - | 添え字などを先に解決する場合、プリプロセッシングが利用される。 | 例では、現在 ``th:each`` で処理を行っている要素の位置を ``status.index`` で取得してから、 ``addresses`` オブジェクトの添え字としている。 | プリプロセッシングを利用しない場合、 ``addresses[${status.index}]`` において ``${status.index}`` を文字列として扱ってしまい、、 ``java.lang.NumberFormatException`` エラーが発生する。 | .. _view_thymeleaf_form-label: フォームオブジェクトのプロパティをバインドする """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" | Thymeleaf + Springの ``th:field`` 属性を使用すると、フォームオブジェクトのプロパティをバインドすることができる。 .. note:: フォームオブジェクトのプロパティのバインドそのものは ``th:field`` を使用することで可能となるが、 ``th:object`` と併用することで簡潔な記述となり、可読性が高まるため、これを推奨する。 .. code-block:: html :emphasize-lines: 1-2
Id :
.. tabularcolumns:: |p{0.10\linewidth}|p{0.90\linewidth}| .. list-table:: :header-rows: 1 :widths: 10 90 * - 項番 - 説明 * - | (1) - \ ``
``\ タグの ``th:object`` 属性に、\ ``Model``\ に格納されているフォームオブジェクトを指定する。 * - | (2) - \ ````\ タグの ``th:field`` 属性に、バインドするプロパティ名を指定する。 | .. _view_thymeleaf_errors-label: 入力チェックエラーを表示する """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" 入力チェックエラーの内容を表示する場合、Thymeleaf + Springの ``th:errors`` 属性を使用する。詳細は、 :doc:`../ArchitectureInDetail/WebApplicationDetail/Validation` を参照されたい。 .. note:: 入力チェックそのものは ``th:errors`` を使用することで可能となるが、 ``th:object`` と併用することで簡潔な記述となり、可読性が高まるため、これを推奨する。 .. code-block:: html :emphasize-lines: 2 Id :
.. tabularcolumns:: |p{0.10\linewidth}|p{0.90\linewidth}| .. list-table:: :header-rows: 1 :widths: 10 90 * - 項番 - 説明 * - | (1) - | \ ``th:errors``\ 属性に、エラー表示したいプロパティのプロパティ名を指定する。 | なお、指定したプロパティに入力チェックエラーがなかった場合、出力されるHTMLにおいて、 ``th:errors`` 属性を含む要素(ここでは ```` )は削除される。 | .. _view_thymeleaf_resultmessages-label: 処理結果のメッセージを表示する """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" | 処理結果を通知するメッセージを表示する場合、 ``ResultMessages`` オブジェクトから結果メッセージを取り出して表示する必要がある。 | 結果メッセージはコードまたはメッセージ文字列として格納されており、前者はコードをキーにプロパティファイルからメッセージを取得して表示する必要がある。 | 詳細は、 :doc:`../ArchitectureInDetail/WebApplicationDetail/MessageManagement` を参照されたい。 以下では、TERASOLUNAのJSPタグである ```` のデフォルト設定で出力するHTMLを生成する例として、以下のようにソースコードを記述している。 .. code-block:: html :emphasize-lines: 3-4

Message pattern

.. tabularcolumns:: |p{0.10\linewidth}|p{0.90\linewidth}| .. list-table:: :header-rows: 1 :widths: 10 90 * - 項番 - 説明 * - | (1) - | ``resultMessages`` オブジェクトが ``null`` でないとき、 ``
`` とその配下の要素が実行される。 * - | (2) - | ``resultMessages`` オブジェクトに格納された ``message`` プロパティを、Thymeleafの ``#messages`` オブジェクトを使用して繰り返し取得し、出力する。 | .. _view_thymeleaf_codelist-label: コードリストを表示する """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" | コードリストは、 ``java.util.Map`` 型として取得することができ、 ``Map`` インタフェースと同じ方法で参照することができる。 | 詳細は、 :doc:`../ArchitectureInDetail/WebApplicationDetail/Codelist` を参照されたい。 コードリストをセレクトボックスに表示する。 .. code-block:: html :emphasize-lines: 3 .. tabularcolumns:: |p{0.10\linewidth}|p{0.90\linewidth}| .. list-table:: :header-rows: 1 :widths: 10 90 * - 項番 - 説明 * - | (1) - | コードリスト名( ``CL_ORDERSTATUS`` )を属性名として、コードリスト( ``java.util.Map`` インタフェース)が格納されている。 | ``asMap`` メソッドで取得したコードリストから、セレクトボックスに各コードのキー値を表示し、選択されたコード名を ``th:field`` 属性で指定されたオブジェクトに代入している。 | ``th:each`` 属性については、 :ref:`view_thymeleaf_each-label` も参照されたい。 セレクトボックスで選択した値のコード名を表示する。 .. code-block:: html .. tabularcolumns:: |p{0.10\linewidth}|p{0.90\linewidth}| .. list-table:: :header-rows: 1 :widths: 10 90 * - 項番 - 説明 * - | (1) - | セレクトボックス作成時と同様に、コードリスト名( ``CL_ORDERSTATUS`` ) を属性名として格納されたコードリスト( ``java.util.Map`` インタフェース)を、 ``asMap`` メソッドで取得する。 | 取得したコードリストのキー値として、セレクトボックスで選択した値を指定することで、コード名を表示することができる。 | .. _view_thymeleaf_pagination-label: ページネーション用のリンクを表示する """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" 一覧表示を行う画面にてページネーション用のリンクを表示する場合、モデルから ``Page`` インタフェースを取得し、ページネーション用のリンクを生成する。 .. todo:: **TBD** 次版以降で詳細を記載する予定である。 | .. _view_thymeleaf_authorization-label: 権限によって表示を切り替える """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" | ログインしているユーザの権限によって表示を切り替える場合は、ThymeleafのSpring Security連携用ダイアレクトで提供されている ``sec:authorize`` 属性や ``#authorization`` オブジェクトを使用する。 | 詳細は、 :doc:`../Security/Authorization` を参照されたい。 | JavaScriptの実装 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 画面描画後に画面項目の制御(表示/非表示、活性/非活性などの制御)を行う必要がある場合は、JavaScriptを使用して、項目の制御を行う。 .. todo:: **TBD** 次版以降で詳細を記載する予定である。 | スタイルシートの実装 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | 画面のデザインに関わる属性値の指定はテンプレートHTMLに直接指定するのではなく、スタイルシート(cssファイル)に指定することを推奨する。 | テンプレートHTMLでは、項目を一意に特定するためのid属性の指定と項目の分類を示すclass属性の指定を行い、実際の項目の配置や見た目にかかわる属性値の指定はスタイルシート(cssファイル)で指定する。 | このような構成にすることで、HTMLの実装からデザインに関わる処理を減らすことができる。 | 同時にちょっとしたデザイン変更であれば、テンプレートHTMLを修正せずにスタイルシート(cssファイル)の修正のみで対応可能となる。 | 共通処理の実装 -------------------------------------------------------------------------------- | .. _controller-common-process: Controllerの呼び出し前後で行う共通処理の実装 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 本項でいう共通処理とは、Controllerを呼び出し前後に行う必要がある共通的な処理のことを指す。 | Servlet Filterの実装 """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" | Spring MVCに依存しない共通処理については、Servlet Filterで実装する。 | ただし、Controllerのハンドラメソッドにマッピングされるリクエストに対してのみ共通処理を行いたい場合は、Servlet FilterではなくHandlerInterceptorで実装すること。 | 以下に、Servlet Filterのサンプルを示す。 | サンプルコードでは、クライアントのIPアドレスをログ出力するために ``MDC`` に値を格納している。 .. raw:: latex \newpage - java .. code-block:: java :emphasize-lines: 1 public class ClientInfoPutFilter extends OncePerRequestFilter { // (1) private static final String ATTRIBUTE_NAME = "X-Forwarded-For"; protected final void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { String remoteIp = request.getHeader(ATTRIBUTE_NAME); if (remoteIp == null) { remoteIp = request.getRemoteAddr(); } MDC.put(ATTRIBUTE_NAME, remoteIp); try { filterChain.doFilter(request, response); } finally { MDC.remove(ATTRIBUTE_NAME); } } } - web.xml .. code-block:: xml :emphasize-lines: 1,5 clientInfoPutFilter x.y.z.ClientInfoPutFilter clientInfoPutFilter /* .. tabularcolumns:: |p{0.10\linewidth}|p{0.90\linewidth}| .. list-table:: :header-rows: 1 :widths: 10 90 * - 項番 - 説明 * - | (1) - サンプルではSpring Frameworkから提供されている ``org.springframework.web.filter.OncePerRequestFilter`` の子クラスとしてServlet Filterを作成することで、同一リクエスト内で1回だけ実行されることを保証している。 * - | (2) - 作成したServlet Filterを ``web.xml`` に登録する。 * - | (3) - 登録したServlet Filterを適用するURLのパターンを指定する。 Servlet FilterをSpring FrameworkのBeanとして定義することもできる。 - web.xml .. code-block:: xml :emphasize-lines: 3 clientInfoPutFilter org.springframework.web.filter.DelegatingFilterProxy clientInfoPutFilter /* - applicationContext.xml .. code-block:: xml :emphasize-lines: 1 .. tabularcolumns:: |p{0.10\linewidth}|p{0.90\linewidth}| .. list-table:: :header-rows: 1 :widths: 10 90 * - 項番 - 説明 * - | (1) - サンプルではSpring Frameworkから提供されている ``org.springframework.web.filter.DelegatingFilterProxy`` をServlet Filterのクラスに指定することで、(2)で定義したServlet Filterに処理が委譲される。 * - | (2) - 作成したServlet FilterのクラスをBean定義ファイル( ``applicationContext.xml`` )に追加する。 その際に、id属性には ``web.xml`` で指定したフィルター名( ```` タグで指定した値 )にすること。 | HandlerInterceptorの実装 """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" | Spring MVCに依存する共通処理については、 HandlerInterceptorで実装する。 | HandlerInterceptorは、リクエストにマッピングされたハンドラメソッドが決定した後に呼び出されるので、アプリケーションが許可しているリクエストに対してのみ共通処理を行うことができる。 HandlerInterceptorでは以下の3つのポイントで処理を実行することが出来る。 - | Controllerのハンドラメソッドを実行する前 | ``HandlerInterceptor#preHandle`` メソッドとして実装する。 - | Controllerのハンドラメソッドが正常終了した後 | ``HandlerInterceptor#postHandle`` メソッドとして実装する。 - | Controllerのハンドラメソッドの処理が完了した後(正常/異常に関係なく実行される) | ``HandlerInterceptor#afterCompletion`` メソッドとして実装する。 | 以下に、HandlerInterceptorのサンプルを示す。 | サンプルコードでは、Controllerの処理が正常終了した後にinfoレベルのログを出力している。 .. code-block:: java :emphasize-lines: 1 public class SuccessLoggingInterceptor extends HandlerInterceptorAdapter { // (1) private static final Logger logger = LoggerFactory .getLogger(SuccessLoggingInterceptor.class); @Override public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { HandlerMethod handlerMethod = (HandlerMethod) handler; Method m = handlerMethod.getMethod(); logger.info("[SUCCESS CONTROLLER] {}.{}", new Object[] { m.getDeclaringClass().getSimpleName(), m.getName()}); } } - spring-mvc.xml .. code-block:: xml :emphasize-lines: 4-5,7 .. tabularcolumns:: |p{0.10\linewidth}|p{0.90\linewidth}| .. list-table:: :header-rows: 1 :widths: 10 90 * - 項番 - 説明 * - | (1) - サンプルではSpring Frameworkから提供されている ``org.springframework.web.servlet.handler.HandlerInterceptorAdapter`` の子クラスとしてHandlerInterceptorを作成している。 ``HandlerInterceptorAdapter`` は ``HandlerInterceptor`` インタフェースの空実装を提供しているため、子クラスで不要なメソッドの実装をしないで済む。 * - | (2) - 作成したHandlerInterceptorを適用するパスのパターンを指定する。 * - | (3) - 作成したHandlerInterceptorを適用しないパスのパターンを指定する。 * - | (4) - 作成したHandlerInterceptorを ``spring-mvc.xml`` の ```` タグ内に追加する。 | Controllerの共通処理の実装 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ここでいう共通処理とは、すべてのControllerで共通的に実装する必要がある処理のことを指す。 .. _methodargumentresolver: HandlerMethodArgumentResolverの実装 """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" Spring FrameworkのデフォルトでサポートされていないオブジェクトをControllerの引数として渡したい場合は、 HandlerMethodArgumentResolverを実装してControllerの引数として受け取れるようにする。 | 以下に、HandlerMethodArgumentResolverのサンプルを示す。 | サンプルコードでは、 共通的なリクエストパラメータをJavaBeanに変換してControllerのメソッドで受け取れるようにしている。 - JavaBean .. code-block:: java :emphasize-lines: 1 public class CommonParameters implements Serializable { // (1) private String param1; private String param2; private String param3; // .... } - HandlerMethodArgumentResolver .. code-block:: java :emphasize-lines: 2,6,13 public class CommonParametersMethodArgumentResolver implements HandlerMethodArgumentResolver { // (2) @Override public boolean supportsParameter(MethodParameter parameter) { return CommonParameters.class.equals(parameter.getParameterType()); // (3) } @Override public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception { CommonParameters params = new CommonParameters(); // (4) params.setParam1(webRequest.getParameter("param1")); params.setParam2(webRequest.getParameter("param2")); params.setParam3(webRequest.getParameter("param3")); return params; } - Controller .. code-block:: java :emphasize-lines: 2 @RequestMapping(value = "home") public String home(CommonParameters commonParams) { // (5) logger.debug("param1 : {}",commonParams.getParam1()); logger.debug("param2 : {}",commonParams.getParam2()); logger.debug("param3 : {}",commonParams.getParam3()); // ... return "sample/home"; } - spring-mvc.xml .. code-block:: xml :emphasize-lines: 4 .. tabularcolumns:: |p{0.10\linewidth}|p{0.90\linewidth}| .. list-table:: :header-rows: 1 :widths: 10 90 * - 項番 - 説明 * - | (1) - 共通パラメータを保持するJavaBean。 * - | (2) - ``org.springframework.web.method.support.HandlerMethodArgumentResolver`` インタフェースを実装する。 * - | (3) - 処理対象とする型を判定する。例では、共通パラメータを保持するJavaBeanの型がControllerの引数として指定されていた場合に、このクラスのresolveArgumentメソッドが呼び出される。 * - | (4) - リクエストパラメータから値を取得し、共通パラメータを保持するJavaBeanに設定し返却する。 * - | (5) - | Controllerのハンドラメソッドの引数に共通パラメータを保持するJavaBeanを指定する。 | (4)で返却されるオブジェクトが渡される。 * - | (6) - 作成したHandlerMethodArgumentResolverを ``spring-mvc.xml`` の ```` タグ内に追加する。 .. note:: 全てのControllerのハンドラメソッドで共通的に渡すパラメータがある場合は、HandlerMethodArgumentResolverを使ってJavaBeanに変換してから渡す方法が有効的である。 ここでいうパラメータとは、リクエストパラメータに限らない。 | .. _application_layer_controller_advice: \ ``@ControllerAdvice``\ の実装 """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" \ ``@ControllerAdvice``\ アノテーションを付与したクラスでは、 複数のControllerで実行したい共通的な処理を実装する。 \ ``@ControllerAdvice``\ アノテーションを付与したクラスを作成すると、 - ``@InitBinder`` を付与したメソッド - ``@ExceptionHandler`` を付与したメソッド - ``@ModelAttribute`` を付与したメソッド で実装した処理を、複数のControllerに適用する事ができる。 .. tip:: \ ``@ControllerAdvice``\ アノテーションは、Spring Framework 3.2 から追加された仕組みだが、 全てのControllerに処理が適用される仕組みになっていたため、アプリケーション全体の共通処理しか実装できなかった。 Spring Framework 4.0 からは、共通処理を適用するControllerを柔軟に指定する事ができるように改善されている。 この改善により、様々な粒度で共通処理を実装する事ができるようになった。 | .. _application_layer_controller_advice_attribute: 以下に、共通処理を適用するControllerを指定する方法(属性の指定方法)について説明する。 .. tabularcolumns:: |p{0.10\linewidth}|p{0.15\linewidth}|p{0.75\linewidth}| .. list-table:: :header-rows: 1 :widths: 10 15 75 :class: longtable * - 項番 - 属性 - 説明と指定例 * - | (1) - ``annotations`` - アノテーションを指定する。 指定したアノテーションが付与されたControllerに対して共通処理が適用される。 以下に指定例を示す。 .. code-block:: java @ControllerAdvice(annotations = LoginFormModelAttributeSetter.LoginFormModelAttribute.class) public class LoginFormModelAttributeSetter { @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) public static @interface LoginFormModelAttribute {} // ... } .. code-block:: java @LoginFormModelAttribute @Controller public class WelcomeController { // ... } .. code-block:: java @LoginFormModelAttribute @Controller public class LoginController { // ... } 上記例では、\ ``WelcomeController``\ と\ ``LoginController``\ に\ ``@LoginFormModelAttribute``\ アノテーションを付与しているため、 \ ``WelcomeController``\ と\ ``LoginController``\ に共通処理が適用される。 * - | (2) - ``assignableTypes`` - クラス又はインタフェースを指定する。 指定したクラス又はインタフェースに割り当て可能(キャスト可能)なControllerに対して共通処理が適用される。 本属性を使用する場合は、共通処理を適用するControllerであることを示すためのマーカーインタフェースを属性値に指定するスタイルを採用することを推奨する。 このスタイルを採用した場合、Controller側では、適用したい共通処理用のマーカーインタフェースを実装するだけでよい。 以下の指定例を示す。 .. code-block:: java @ControllerAdvice(assignableTypes = ISODateInitBinder.ISODateApplicable.class) public class ISODateInitBinder { public static interface ISODateApplicable {} // ... } .. code-block:: java @Controller public class SampleController implements ISODateApplicable { // ... } 上記例では、\ ``SampleController``\ が\ ``@ISODateApplicable``\ インタフェース(マーカーインタフェース)を実装しているため、 \ ``SampleController``\ に共通処理が適用される。 * - | (3) - ``basePackageClasses`` - クラス又はインタフェースを指定する。 指定したクラス又はインタフェースのパッケージ配下のControllerに対して共通処理が適用される。 本属性を使用する場合は、 * \ ``@ControllerAdvice``\ を付与したクラス * パッケージを識別するためのマーカーインタフェース を属性値に指定するスタイルを採用することを推奨する。 以下に指定例を示す。 .. code-block:: java package com.example.app @ControllerAdvice(basePackageClasses = AppGlobalExceptionHandler.class) public class AppGlobalExceptionHandler { // ... } .. code-block:: java package com.example.app.sample @Controller public class SampleController { // ... } 上記例では、\ ``SampleController``\ が\ ``@ControllerAdvice``\ を付与したクラス(\ ``AppGlobalExceptionHandler``\)が格納されているパッケージ(\ ``com.example.app``\ )配下に格納されているため、 \ ``SampleController``\ に共通処理が適用される。 .. code-block:: java package com.example.app.common @ControllerAdvice(basePackageClasses = AppPackage.class) public class AppGlobalExceptionHandler { // ... } .. code-block:: java package com.example.app public interface AppPackage { } \ ``@ControllerAdvice``\ が付与されているクラスとControllerが格納されているクラスのパッケージ階層が異なる場合や、複数のベースパッケージに共通処理を適用したい場合は、 パッケージを識別するためのマーカインタフェースを用意すればよい。 * - | (4) - ``basePackages`` - パッケージ名を指定する。 指定したパッケージ配下のControllerに対して共通処理が適用される。 以下に指定例を示す。 .. code-block:: java @ControllerAdvice(basePackages = "com.example.app") public class AppGlobalExceptionHandler { // ... } * - | (5) - ``value`` - \ ``basePackages``\ へのエイリアス。 \ ``basePackages``\ 属性を指定した際と同じ動作となる。 以下に指定例を示す。 .. code-block:: java @ControllerAdvice("com.example.app") public class AppGlobalExceptionHandler { // ... } .. raw:: latex \newpage .. tip:: \ ``basePackageClasses``\ 属性 / \ ``basePackages``\ 属性 / \ ``value``\ 属性は、 共通処理を適用したいControllerが格納されているベースパッケージを指定するための属性であるが、 \ ``basePackageClasses``\ 属性を使用した場合、 * 存在しないパッケージを指定してしまう事を防ぐことが出来る * IDE上で行ったパッケージ名変更と連動することが出来る ため、タイプセーフな指定方法と言える。 | | 以下に、\ ``@InitBinder``\ メソッドの実装サンプルを示す。 | サンプルコードでは、 リクエストパラメータで指定できる日付型で形式を ``yyyy/MM/dd`` に設定している。 .. code-block:: java :emphasize-lines: 1,2,5-6 @ControllerAdvice // (1) @Order(0) // (2) public class SampleControllerAdvice { // (3) @InitBinder public void initBinder(WebDataBinder binder) { SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy/MM/dd"); dateFormat.setLenient(false); binder.registerCustomEditor(Date.class, new CustomDateEditor(dateFormat, true)); } } .. tabularcolumns:: |p{0.10\linewidth}|p{0.90\linewidth}| .. list-table:: :header-rows: 1 :widths: 10 90 * - 項番 - 説明 * - | (1) - \ ``@ControllerAdvice``\ アノテーションを付与することで、ControllerAdviceのBeanであることを示している。 * - | (2) - \ ``@Order``\ アノテーションを付与することで、共通処理が適用される優先度を指定する。複数のControllerAdviceに依存関係があるなど、ControllerAdviceに順序性を持たせたい場合は必ず指定すること。順序性を持たせる必要がなければ指定しなくてもよい。 * - | (3) - \ ``@InitBinder``\ メソッドを実装する。全てのControllerに対して\ ``@InitBinder``\ メソッドが適用される。 | | 以下に、\ ``@ExceptionHandler``\ メソッドの実装サンプルを示す。 | サンプルコードでは、 ``org.springframework.dao.PessimisticLockingFailureException`` をハンドリングしてロックエラー画面のViewを返却している。 .. code-block:: java :emphasize-lines: 1-2 // (1) @ExceptionHandler(PessimisticLockingFailureException.class) public String handlePessimisticLockingFailureException( PessimisticLockingFailureException e) { return "error/lockError"; } .. tabularcolumns:: |p{0.10\linewidth}|p{0.90\linewidth}| .. list-table:: :header-rows: 1 :widths: 10 90 * - 項番 - 説明 * - | (1) - \ ``@ExceptionHandler``\ メソッドを実装する。全てのControllerに対して\ ``@ExceptionHandler``\ メソッドが適用される。 | | 以下に、\ ``@ModelAttribute``\ メソッドの実装サンプルを示す。 | サンプルコードでは、 共通的なリクエストパラメータをJavaBeanに変換して ``Model`` に格納している。 - ControllerAdvice .. code-block:: java :emphasize-lines: 1-2 // (1) @ModelAttribute public CommonParameters setUpCommonParameters( @RequestParam(value = "param1", defaultValue="def1") String param1, @RequestParam(value = "param2", defaultValue="def2") String param2, @RequestParam(value = "param3", defaultValue="def3") String param3) { CommonParameters params = new CommonParameters(); params.setParam1(param1); params.setParam2(param2); params.setParam3(param3); return params; } - Controller .. code-block:: java :emphasize-lines: 2 @RequestMapping(value = "home") public String home(@ModelAttribute CommonParameters commonParams) { // (2) logger.debug("param1 : {}",commonParams.getParam1()); logger.debug("param2 : {}",commonParams.getParam2()); logger.debug("param3 : {}",commonParams.getParam3()); // ... return "sample/home"; } .. tabularcolumns:: |p{0.10\linewidth}|p{0.90\linewidth}| .. list-table:: :header-rows: 1 :widths: 10 90 * - 項番 - 説明 * - | (1) - \ ``@ModelAttribute``\ メソッドを実装する。全てのControllerに対して\ ``@ModelAttribute``\ メソッドが適用される。 * - | (2) - \ ``@ModelAttribute``\ メソッドで生成されたオブジェクトが渡る。 | 二重送信防止について -------------------------------------------------------------------------------- 送信ボタンの複数回押下や完了画面の再読み込み(F5ボタンによる再読み込み)などで、 同じ処理が複数回実行されてしまう可能性があるため、二重送信を防止するための対策は必ず行うこと。 対策を行わない場合に発生する問題点や対策方法の詳細は、 :doc:`../ArchitectureInDetail/WebApplicationDetail/DoubleSubmitProtection` を参照されたい。 | セッションの使用について -------------------------------------------------------------------------------- | Spring MVCのデフォルトの動作では、モデル(フォームオブジェクトやドメインオブジェクトなど)はセッションには格納されない。 | セッションに格納したい場合は、\ ``@SessionAttributes``\ アノテーションをControllerクラスに付与する必要がある。 | 入力フォームが複数の画面にわかれている場合は、 一連の画面遷移を行うリクエストでモデル(フォームオブジェクトやドメインオブジェクトなど)を共有できるため、 \ ``@SessionAttributes``\ アノテーションの利用を検討すること。 | ただし、セッションを使用する際の注意点があるので、そちらを確認した上で\ ``@SessionAttributes``\ アノテーションの利用有無を判断すること。 セッションの利用指針及びセッション使用時の実装方法の詳細は、 :doc:`../ArchitectureInDetail/WebApplicationDetail/SessionManagement` を参照されたい。 .. raw:: latex \newpage