Google
 
Web andore.com
Powered by SmartDoc

13 ウェブMVCフレームワーク(Ver 1.2.7)

13.1 ウェブMVCフレームワークのはじめに

SpringのウェブMVCフレームワークは、アップロードファイルのサポートと同様に、リクエストをハンドラにディスパッチし、ハンドラマッピング、ビュー解決、ロケールやテーマの解決が設定可能なDispatcherServletを中心に設計されている。このデフォルトハンドラは、ちょうどModelAndView handleRequest(request,response)メソッドが示しているように、とても単純なControllerインタフェースである。これはすでにアプリケーションコントローラに利用されているが、例えばAbstractControllerAbstractCommandControllerSimpleFormControllerから構成される含まれた実装階層を好むだろう。アプリケーションコントローラは通常、これらのサブクラスになる。適切なベースクラスを選択することができ、もしフォームがなければFormControllerを必要とはしない、ということに注意して欲しい。これはStrutsと大きく異なる点だ。

コマンド、あるいはフォームオブジェクトとしてどんなオブジェクトを用いてもよく、あるインタフェースを実装したり、あるベースクラスから派生させる必要はない。Springのデータバインディングは柔軟性が高く、例えば、アプリケーションによって評価されるバリデーションエラーのような型のミスマッチをシステムエラーとしてでなく扱う。よって、不正なサブミッションを扱えるようにしたり、Stringを適切に変換するためだけにビジネスオブジェクトのプロパティをフォームオブジェクトにStringとして複製する必要はない。その代わり、ビジネスオブジェクトに直接バインドするのが望ましい。これは全てのアクション型に対するActionActionFormのような必要とするベースクラスを中心に構築されているStrutsとのもうひとつの大きな違いだ。

WebWorkと比べて、Springにはより多くの区別されたオブジェクトの役割がある。SpringではController、オプショナルコマンド、フォームオブジェクト、ビューに渡されるモデルの概念をサポートする。このモデルには通常コマンドやフォームオブジェクトだけでなく、任意の参照データも含まれている。それに対し、WebWorkのActionでは全ての役割が1つの単一オブジェクトに組み込まれている。WebWorkでは、既存のビジネスオブジェクトを自分のフォームとして使うことができるが、それは、そのビジネスオブジェクトが期待するActionクラスのビーンプロパティにしている場合にのみ可能だ。最後に、リクエストを処理するのと同じActionインスタンスが評価とビューの設置に用いられる。よって、参照データもActionのビーンプロパティとしてモデル化される必要がある。このようにひとつのオブジェクト与えられる役割があまりにも多すぎる。

Springのビュー解決は非常に柔軟だ。あるControllerの実装ですらビューを直接ModelAndViewとしてnullを返すレスポンスに書き込むことができる。通常、ModelAndViewインスタンスはビーン名と(コマンドやフォームのような、参照データを含んでいる)適切なオブジェクトを含んだ、ビュー名とモデルのマップから構成される。ビュー名の解決は、ビーン名から、プロパティファイルから、あるいは自前のViewResolverの実装から高度に設定が可能だ。この抽象モデルのマップは頑張らなくてもビューテクノロジを完全に抽象化することができる。JSP、Velocityであろうと他のどんなレンダリングテクノロジであろうと、直接統合することが可能だ。このモデルマップはJSPリクエストの属性や、Velocityのテンプレートモデルのような適切なフォーマットに単純に変換される。

13.1.1 他のMVC実装をプラグインする

他のMVC実装を好んで使うプロジェクトがいくつかあるのには理由がある。スキルやツールへのそれまでのの投資が生かせることを期待するチームはとても多い。また、Strutsフレームワークに関する知識や経験がとても豊富だ。なので、もしStrutsのアーキテクチャ上の欠点を受け入れることができるのであれば、Strutsは今までどおりWeb層における有効な選択肢となり得るのだ。同じことがWebWorkやその他のウェブMVCフレームワークにも当てはまる。

もし、SpringのウェブMVCは使いたくない、でもSpringで利用可能な他の解決策を使いたいというのであれば、その自分で選んだウェブMVCフレームワークをSpringと簡単に統合することができる。SpringのルートアプリケーションコンテキストをContextLoaderListenerから起動し、ServletContext属性(あるいは、Springの個々のヘルパーメソッド)を使ってStrutsやWebWorkのアクションからアクセスするだけだ。ここで注意して欲しいのは、"プラグイン"ではないので、それ専用に統合する必要は全くないということだ。ウェブ層の観点から、ルートアプリケーションコンテキストインスタンスをエントリポイントとするSpringをライブラリとして使うだけだ。

あなたが登録したビーンやSpringのサービスは全てSpringのウェブMVCがなくてもあなたの思うがままだ。Springはこのシナリオの中ではStrutsやWebWorkとは競合しない。ビーン設定からデータアクセスやトランザクションのハンドリングまで、純粋なウェブMVCフレームワークが対象としない多くの分野にも適用できます。なので、例えば、JDBCやHibernateのトランザクションを抽象化するのに使いたいだけであったとしてもSpringのミドル層やデータアクセス層を使えば、あなたのアプリケーションをリッチにすることができるのである。

13.1.2 Spring MVCの機能

Springのウェブモジュールではユニークなウェブサポート機能を豊富に提供している。以下に挙げる:

13.2 DispatcherServlet

SpringのウェブMVCフレームワークは、多くの他のウェブMVCフレームワークのように、リクエストドリブンなウェブMVCフレームワークで、リクエストをコントローラにディスパッチするサーブレットやウェブアプリケーション開発に沿った機能を提供するように設計されている。しかしながら、SpringのDispatcherServletはそれだけではない。これは、SpringのApplicationContextと完全に統合されており、Springの持つ他の全ての機能を使うことが可能だ。

通常のサーブレットと同様、DispatcherServletはそれぞれのウェブアプリケーションにあるweb.xmlで宣言されている。DispatcherServletに処理させたいリクエストは、同じweb.xmlファイルの中でURLマッピングを用いて対応づけされていなければならない。

<web-app>
    ...
    <servlet>
        <servlet-name>example</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <load-on-startup>1</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>example</servlet-name>
        <url-pattern>*.form</url-pattern>
    </servlet-mapping>
</web-app>

上記例では、.formで終わる全てのリクエストはDispatcherServletにて処理される。ここでこのDispatcherServletの設定が必要になる。

3.11章のApplicationContextの導入で解説したように、SpringにおけるApplicationContextはスコープになる。このウェブMVCフレームワークでは、各DispatcherServletはそれぞれがWebApplicationContextを持ち、ルートのWebApplicationContextですでに定義されているビーンに継承される。この継承されたビーン定義はサーブレットごとのスコープの中でオーバライドされ、新しいスコープに特化したビーンが指定されたサーブレットインスタンスのローカルに定義される。

このフレームワークは、DispatcherServletの初期化時に、そのウェブアプリケーションのWEB-INFディレクトリから[servlet名]-servlet.xmlという名前のファイルを探し、そこに(グローバルスコープにある同じ名前のビーン定義を上書きして)このビーン定義を生成する。

DispatcherServletによって利用されるコンフィグの場所は、サーブレットの初期化パラメータで変更することができる。(詳細は下記を参照されたい)

このWebApplicationContextは、ウェブアプリケーションに必要な特別な機能がいくつかあることを除いては、通常のApplicationContextと全く同じだ。通常のApplicationContextと違う点は、テーマの解決が可能であること(13.7章、「テーマを使う」を参照して欲しい)と、(ServletContextへのリンクをもつことで)どのサーブレットと関連をもつかを知っていることだ。WebApplicationContextServletContext内部にあり、必要な場合は、RequestCOntextUtilsを使って常にWebApplicationContextをルックアップすることができる。

SpringのDispatcherServletはリクエストを処理し、適切なビューを表示できるように自身が利用する特別なビーンと密接になっている。これらのビーンはSpringフレームワークに含まれており、他のビーンと同じようにWebApplicationContextで設定することが可能である。ビーンについては、それぞれ下記に詳細が述べられている。ここでは、どのようなビーンが用意されているか、DistapcherServletに関しての話題を続けられるようにしよう。ほとんどのビーンでは、デフォルトの振る舞いが用意されているので、どのように設定すればいいか心配する必要はない。

13.1 WebApplicationContextの特別なビーン
表記 説明
ハンドラマッピング (節[ハンドラマッピング])前処理、あるいは後処理や、(例えば、コントローラに指定されたURLに合致するなど)特定の条件にマッチした場合に実行されるコントローラのリスト
コントローラ (節[13.3 コントローラ]) MVCの一部として、実際の機能(あるいは少なくとも機能にアクセス)を提供するビーン
ビューリゾルバ (節[ビューとビューの解決])ビュー名からビューを解決する。DispatcherServletで利用される
ロケールリゾルバ (節[ロケールを使う])国際化されたビューを利用可能にするためにクライアントが利用しているロケールを解決する。
テーマリゾルバ (節[テーマを使う])ウェブアプリケーションが用いるテーマを解決し、パーソナライズされたレイアウトを提供する
マルチパートリゾルバ (節[Springのマルチパート(ファイルアップロード)サポート]) HTMLフォームからファイルのアップロード処理機能を提供する
ハンドラ例外リゾルバ (節[例外をハンドリングする])ビューへの例外のマッピングや他のもっと複雑な例外ハンドリングコードの実装機能を提供する

DispatcherServletがセットアップされ、リクエストがその特定のDispatcherServletに来ると、処理が開始される。下記のリストは、リクエストが来てDispatcherServletによって処理される場合の完全な処理手順である。

  1. WebApplicationContextが検索され、コントローラや処理に利用される他の要素で利用するために、リクエストにバインドされる。デフォルトでは、DispatcherServlet.WEB_APPLICATION_CONTEXT_ATTRIBUTEというキーにバインドされる。
  2. リクエスト処理中(ビューのレンダリング、データの適用等)に、処理中の要素がロケールを解決するのに使うロケールリゾルバがリクエストにバインドされる。もしリゾルバを使わない場合は何もしないので、もしロケールを解決する必要がない場合はこれを使う必要はない。
  3. ビューのような要素がどのテーマを使うかを決定するためにテーマリゾルバがリクエストにバインドされる。もし使わなければ、テーマリゾルバは何もしないので、テーマが必要なければ無視してもよい。
  4. マルチパートリゾルバが指定されている場合、リクエストはマルチパートを探し出し、見つかれば、他の要素により後で行われる処理用にMultipartHttpServletRequestにラップする。(マルチパート処理に関する更なる情報については、節[MultipartResolverを使う]を参照のこと)
  5. 適切なハンドラが検索される。ハンドラが見つかると、ハンドラ(プリプロセッサ、ポストプロセッサ、コントローラ)に関連づけられた例外のチェーンがモデルを準備するために実行される。
  6. モデルが返されると、WebApplicationContextで設定されたビューリゾルバを使ってビューがレンダリングされる。もし、返されるモデルがない場合(これは、プリプロセッサ、ポストプロセッサがリクエストをインターセプトするために生じる。例えば、セキュリティ的な理由による)、ビューはレンダリングされない。

リクエスト処理中に投げられるかもしれない例外はWebApplicationContext中で宣言されているhandlerexceptionリゾルバにピップアップされる。この例外リゾルバを使えば、その例外が投げられた場合の独自の振る舞いを定義することができる。

SpringのDispatcherServletには、サーブレットAPIで指定されている最終更新日を返す機能もサポートされている。特定のリクエストに対し最終更新日を決定する処理手順は単純だ。DispatcherServletは最初に適切なハンドラマッピングをルックアップし、その見つかったハンドラがLastModifiedインタフェースが実装されているかをテストする。実装されていれば、long型のgetLastModified(request)の値がクライアントに返される。

SpringのDispatcherServletweb.xmlファイルのコンテキストパラメータや、サーブレットの初期化パラメータを追加することで、カスタマイズできる。この方法でカスタマイズできるものを下記に挙げる。

13.2 DispatcherServletの初期化パラメータ
パラメータ 説明
contextClass WebApplicationContextを実装するクラス。これはコンテキストをインスタンス化するのにこのサーブレットにより用いられる。このパラメータが指定されていない場合は、xmlWebApplicationContextが使われる。
contextConfigLocation コンテキストがどこで見つかったかを示すために(contextClassで指定される)コンテキストインスタンスに渡される文字列。この文字列は、(複数のコンテキストロケーション、二重に定義されるビーンの場合には最後のものが取得される)多重コンテキストをサポートするために複数の文字列に(デリミタにカンマを使って)分割される。
namespace WebApplicationContextの名前空間。デフォルトは[サーバ名]-servlet

13.3 コントローラ

コントローラという考え方は、MVCデザインパターンの一部である。コントローラはアプリケーションの振る舞いを定義する、あるいは少なくともアプリケーションの振る舞いへのアクセスを提供する。コントローラはユーザの入力を解釈し、その入力を ビューによってユーザに提示された知覚可能なモデルに変換する。Springではコントローラという概念が、様々な種類のコントローラを実装できるようにとても抽象的な方法で実装されている。Springには、formcontrollercommandcontroller、あるいはウィザード形式のロジックを実行するコントローラなどが含まれている。

Springのコントローラアーキテクチャの基本はorg.springframework.web.servlet.mvc.Controllerインタフェースで、下記にソースリストを挙げる

public interface Controller {

    /**
     * Process the request and return a ModelAndView object which the DispatcherServlet
     * will render.
     */
    ModelAndView handleRequest(
        HttpServletRequest request,
        HttpServletResponse response)
    throws Exception;
}

ここからわかるように、このControllerインタフェースではリクエストを処理し、適切なモデルやビューを返すことができる単一のメソッドを必要とする。この3つの概念、モデルとビュー、そしてコントローラは、SpringのMVC実装の基礎をなすものである。Controllerインタフェースは完全に抽象的なものではあるが、Springでは必要になるであろう、多くの機能を持ったコントローラが多数用意されている。Controllerインタフェースリクエストを処理し、モデルやビューを返すという、は全てのコントローラで必要とされる共通的な機能を定義しただけのものだ。

AbstractControllerとWebContentGenerator

もちろん、コントローラインタフェースだけでは全然十分ではない。基本的なインフラを提供するために、SpringのControllerは全てAbstractControllerから継承し、キャッシュのサポートや例えばMIMEタイプの設定を提供する。

AbstractControllerで提供される機能
機能 説明
supportedMethods このコントローラが受け入れるのはどのメソッドかを示す。通常これは、GETPOSTの両方が設定されているが、サポートしたいメソッドを反映するのに修正することができる。もし、サポートされていないリクエストがコントローラに受信されるとクライアントは、(ServletExceptionを使って)これが通知される。
requiredSession このコントローラが動作するのにセッションを必要とするかどうかを示す。この機能は全てのコントローラに提供されている。このようなコントローラがリクエストを受信したときにセッションが存在しない場合、ユーザにはServletExceptionを使って通知される。
synchronizeSession ユーザのセッションで、このコントローラでの処理を同期させたい場合にこれを使う。特に、handleRequestInternalメソッドを上書きするコントローラにこの変数を指定すれば、そのコントローラが同期される。
cacheSeconds HTTPレスポンスでコントローラにキャッシングディレクティブを生成させたい場合にここに正の整数値を指定する。デフォルトでは、キャッシングディレクティブが含まれないように-1が設定されている。
useExpiresHeader HTTP 1.0互換の"Expires"ヘッダを指定するようにコントローラを微調整する。デフォルトでは、変更しなくてもいいようにtrueが設定されている。
useCacheHeader HTTP 1.1互換の"Cache-Control"ヘッダを指定するようにコントローラを微調整する。デフォルトでは変更しなくてもいいようにtrueが設定されている。

最後の2つのプロパティは実際にはAbstractControllerのスーパクラスであるWebContentGeneratorの一部であるが、完全を期するためにここに入れた。

AbstractControllerを自前のコントローラのベースクラスとして使う場合(あなたがさせたいことを行うかもしれないいろんなコントローラがすでに用意されているのでこれは、おススメしない)、handleRequestInternal(httpServletRequesthttpServletResponse)をオーバライドして、ロジックを実装し、ModelAndViewオブジェクトを返すだけだ。ここでは、ウェブアプリケーションコンテキストにあるクラスと宣言からなる短い例を挙げる。

package samples;

public class SampleController extends AbstractController {

    public ModelAndView handleRequestInternal(
        HttpServletRequest request,
        HttpServletResponse response)
    throws Exception {
        ModelAndView mav = new ModelAndView("foo");
        mav.addObject("message", "Hello World!");
        return mav;        
    }
}
<bean id="sampleController" class="samples.SampleController">
    <property name="cacheSeconds" value="120"/>
</bean>

この非常に単純なコントローラを動作させるには、ウェブアプリケーションコンテキスト中の上述のクラスと宣言が、ハンドラマッピング(節[ハンドラマッピング]参照)の設定と合わせて必要なことすべてだ。このコントローラは、再度チェックするまえの2分間、キャッシュするようにクライアントに指令するキャッシングディレクティブを生成する。このコントローラは、ハードコーディングされたビュー(あんまりよくない)、名前づけられたインデックス(ビューに関する詳細は節[ビューとビューの解決]を参照のこと)を返す。

他の単純なコントローラ

AbstractControllerを拡張することは可能だが、Springでは単純なMVCアプリケーションで共通的に利用される機能を持った数多くのコントローラの実装が用意されている。ParameterizableViewControllerは、返すビューの名前をウェブアプリケーションコンテキストの中で指定できる(つまり、ビュー名をハードコーディングする必要はないよ)ことを除いては基本的には上述の例と同じだ。

UrlFilenameViewControllerはURLを精査し、リクエストされたファイルのファイル名(http://www.springframework.org/index.htmlのファイル名はindex)を検索し、ビュー名として利用する。これ以上は何もない。

MultiActionController

Springでは複数のアクションを1つのコントローラにまとめて、機能をひとまとめにするための多重アクションコントローラが用意されている。この多重アクションコントローラは別個のパッケージ、org.springframework.web.servlet.mvc.multiactionにあり、リクエストをメソッド名にマッピングし、正しいメソッド名を起動することができる。1つのコントローラ中に、多数の共通的な機能を持たせ、例えば振る舞いを微調整するために複数のエントリポイントをコントローラに設定したい場合は多重アクションコントローラを使うのが手っ取り早い。

MultiActionControllerで用意されている機能
機能 説明
delegate MultiActionControllerには利用シナリオが二つある。1つはMultiActionControllerのサブクラスを作って、MethodNameResolverで解決するメソッドをそのサブクラスに指定する(この場合、delegateを指定する必要はない)、あるいは委譲オブジェクトを定義し、Resolverで解決されるそのオブジェクトのメソッドが起動される。このシナリオを選ぶ場合は、この設定パラメータをコントローラとして使って委譲を定義する必要がある
methodNameResolver なんらかの方法で来たリクエストに基づいてMultiActionControllerが起動すべきメソッドを解決する必要がある。この設定パラメータを使ってこの動作を行うリゾルバを定義することができる。

多重アクションコントローラに定義されているメソッドは、下記のシグネチャに従う必要がある:

// actionName can be replaced by any methodname
ModelAndView actionName(HttpServletRequest, HttpServletResponse);

メソッドのオーバロードは許されていない。MultiActionControllerを混乱させるからだ。また、指定したメソッドが投げる例外をハンドリングできる例外ハンドラを定義することができる。例外ハンドラメソッドはちょうど他のアクションメソッドのようにModelAndViewオブジェクトを返し、下記のシグネチャに従う必要がある。

// anyMeaningfulName can be replaced by any methodname
ModelAndView anyMeaningfulName(HttpServletRequest, HttpServletResponse, ExceptionClass);

このExceptionClassjava.lang.Exceptionjava.lang.RuntimeExceptionのサブクラスと同様、任意の例外になりえる。

MethodNameResolverは受信したリクエストに基づくメソッド名を解決するために用意されている。自由に使えるリゾルバが3つあるが、もちろん、自分の欲しいものを自分で実装することも可能だ。

ここにいくつかの例がある。1つめは、ParameterMethodNameResolverdelegateプロパティの例で、これはリクエストを含むパラメータつきのURLへのリクエストを受け、retrieveIndexに設定される:

<bean id="paramResolver" \
    class="org....mvc.multiaction.ParameterMethodNameResolver">
  <property name="paramName"><value>method</value></property>
</bean>

<bean id="paramMultiController" \
    class="org....mvc.multiaction.MultiActionController">
<property name="methodNameResolver"><ref bean="paramResolver"/></property>
  <property name="delegate"><ref bean="sampleDelegate"/></property>
</bean>

<bean id="sampleDelegate" class="samples.SampleDelegate"/>

下記も一緒に

public class SampleDelegate {

    public ModelAndView retrieveIndex(
        HttpServletRequest req,
        HttpServletResponse resp) {

        return new ModelAndView("index", "date", new Long(System.currentTimeMillis()));
    }
}

上述したような委譲を用いる場合、URLを定義したメソッドにマッチングさせるのにPropertiesMethodNameResolverを使うこともできる:

<bean id="propsResolver" \
    class="org....mvc.multiaction.PropertiesMethodNameResolver">
  <property name="mappings">
    <props>
      <prop key="/index/welcome.html">retrieveIndex</prop>
      <prop key="/**/notwelcome.html">retrieveIndex</prop>
      <prop key="/*/user?.html">retrieveIndex</prop>
    </props>
  </property>
</bean>

<bean id="paramMultiController" \
    class="org....mvc.multiaction.MultiActionController">
<property name="methodNameResolver"><ref bean="propsResolver"/></property>
    <property name="delegate"><ref bean="sampleDelegate"/></property>
</bean>

CommandControllers

SpringのCommandControllerはSpring MVCパッケージの基礎的な部分だ。コマンドコントローラはデータオブジェクトとインタラクションし、動的にHttpServletRequestからのパラメータを指定されたデータオブジェクトへバインドする手段を提供する。これらは、StrutsのActionFormと似たような役割をなすが、Springでは、データオブジェクトはフレームワーク向けのインタフェースを実装する必要はない。まず、どんなコマンドコントローラが利用できるか、それを使って何ができるかを概観しよう。

おそらく、少なくともsetPages()setCommandName()を呼ばないといけない契約を書きたいであろう。前者は、String型の配列を1つ引数にとる。この配列は、ウィザードを含むビューのリストだ。後者は、String型を1つ引数にとる。これは、ビューからコマンドオブジェクトを参照するのに利用される。

AbstractFormControllerの例のように、コマンドオブジェクト - フォームからデータが挿入されるJavaBean - を用いることが必要とされる。これは2つあるうちの一方のやり方でできる。一方は、コマンドオブジェクトクラスのコンストラクタからsetCommandClass()を呼ぶ、あるいはformBackingObject()メソッドを実装する方法だ。

AbstractWizardFormControllerには、オーバライドするかもしれない多数の具象メソッドが用意されている。この中で最も有用だと思われるものは、モデルデータをMapの形式でビューに渡すのに使うことができるreferenceData、もしウィザードがページの順序を変更する必要がある、あるいは動的にページを省くためのgetTargetPage、それに、組み込みバインディングとバリデーションのワークフローをオーバライドしたいときのためのonBindAndValidateだろう。

最後に、ユーザが今のページでバリデーション違反をしている場合でもウィザードの中で前に戻ったり先に進んだりできるようにするgetTargetPageから呼ぶことができるsetAllowDirtyBacksetAllowDirtyForwardは指摘しておく価値があるだろう。

メソッドの全リストについては、AbstractWizardFormControllerのJavaDocを参照してほしい。jPetStoreのこのウィザードの実装例は、Springの配布物:org.springframework.samples.jpetstore.web.spring.OrderFormControllerに含まれている。

ハンドラマッピング

ハンドラマッピングを使えば、入ってくるウェブのリクエストを適切なハンドラへマッピングできる。例えば、SimpleUrlHandlerMappingBeanNameUrlHandlerMappingのようにそのまま利用できるハンドラマッピングがいくつか用意されているが、まずは、handlerMappingの一般的な概念を学ぶことにしよう。

基本的なHandlerMappingが提供する機能は、HandlerExceptionChainを配布することだ。これには、入ってくるリクエストに合致するハンドラが含まれていなければならない。またリクエストに応答するハンドラインタセプタのリストも含まれている。リクエストが来ると、DispatcherServletがハンドラマッピングに渡し、リクエストを識別し、適切なHandlerExecutionChainに対応づける。そして、DispatcherServletが(もしあれば)このチェインの中でハンドラとインタセプタを実行する。

オプションで、(実際にハンドラが実行された前、あるいは後、もしくはその両方で実行される)インタセプタを含めることができるコンフィグ可能なハンドラマッピングの概念は、とても強力だ。多くの支援機能を独自のHandlerMappingsに組み込むことができる。リクエストのURLだけでなく、そのリクエストと関連するセッションの特定の状態に基づいてハンドラを選択する独自のハンドラマッピングを考えてみてほしい。

本セクションでは2つのSpringで最も共通的に利用されるハンドラマッピングについて述べる。これは両方とも、AbstractHandlerMappingを拡張したもので、下記のプロパティをともに持つ。

(注:最後の4つのプロパティはorg.springframework.web.servlet.handler.AbstractUrlHandlerMappingのサブクラスでしか利用できない。)

BeanNameUrlHandlerMapping

とても単純で、しかしとても強力はハンドラマッピングはBeanNameUrlHandlerMappingで、これは、受信するHTTPリクエストをウェブアプリケーションコンテキストで定義されているビーン名にマップする。ユーザがアカウントを入力するのを可能にしたくて、フォームをレンダリングするための適切なFormController(CommandFormControllerに関する詳細についてはmvc-controller-command_ja.htmlを参照してほしい)やJSPのビュー(あるいはVelocityのテンプレート)がすでに提供されている。BeanNameUrlHandlerMappingを使う場合、http://samples.com/editaccount.formのURLのHTTPリクエストを以下のように、適切なFormControllerにマップする。

<beans>
<bean id="handlerMapping" \
    class="org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping"/> \

<bean name="/editaccount.form" \
    class="org.springframework.web.servlet.mvc.SimpleFormController">
    <property name="formView"><value>account</value></property>
    <property name="successView"><value>account-created</value></property>
    <property name="commandName"><value>Account</value></property>
    <property name="commandClass"><value>samples.Account</value></property>
  </bean>
<beans>    

/editaccount.formのURLへのリクエストは全て、ここでは上述したソースリストにあるFormControllerで処理される。もちろん、.formで終わるリクエスト全てに通用させるために、同じように、web.xmlにサーブレットマッピングを定義しないといけない。

<web-app>
    ...
    <servlet>
        <servlet-name>sample</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <load-on-startup>1</load-on-startup>
    </servlet>

   <!-- Maps the sample dispatcher to /*.form -->
    <servlet-mapping>
        <servlet-name>sample</servlet-name>
        <url-pattern>*.form</url-pattern>
    </servlet-mapping>
    ...
</web-app>

注:もしBeanNameUrlHandlerMappingを使いたい場合は、(上述したように)ウェブアプリケーションコンテキストに定義する必要はない。デフォルトでは、ハンドラマッピングがコンテキスト中に見つからなかった場合、DispatcherServletがあなたのためにBeanNameUrlHandlerMappingを生成する。

SimpleUrlHandlerMapping

さらに(あるいははるかに)強力なハンドラマッピングは、SimpleUrlHandlerMappingだ。このマッピングは、アプリケーションコンテキストの中で設定ができて、Antスタイルのパスマッチングの機能を持っている(org.springframework.util.PathMatcherを参照のこと)。例を挙げよう:

<web-app>
    ...
    <servlet>
        <servlet-name>sample</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <load-on-startup>1</load-on-startup>
    </servlet>

    <!-- Maps the sample dispatcher to /*.form -->
    <servlet-mapping>
        <servlet-name>sample</servlet-name>
        <url-pattern>*.form</url-pattern>
    </servlet-mapping>
    <servlet-mapping>
        <servlet-name>sample</servlet-name>
        <url-pattern>*.html</url-pattern>
    </servlet-mapping>
    ...
</web-app>

.html.formで終わるリクエストは全てsampleディスパッチャサーブレットで処理される。

<beans>
    <bean id="handlerMapping"
class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
        <property name="mappings">
            <props>
<prop key="/*/account.form">editAccountFormController</prop>
<prop key="/*/editaccount.form">editAccountFormController</prop>
                <prop key="/ex/view*.html">someViewController</prop>
                <prop key="/**/help.html">helpController</prop>
            </props>
        </property>
    </bean>

    <bean id="someViewController"
class="org.springframework.web.servlet.mvc.UrlFilenameViewController"/>

    <bean id="editAccountFormController"
          class="org.springframework.web.servlet.mvc.SimpleFormController">
        <property name="formView"><value>account</value></property>
<property name="successView"><value>account-created</value></property>
        <property name="commandName"><value>Account</value></property>
<property name="commandClass"><value>samples.Account</value></property>
    </bean>
<beans>

このハンドラマッピングは任意のディレクトリのhelp.htmlへのリクエストをUrlFilenameViewControllerであるhelpControllerにルーティングする(コントローラに関しての詳細は、節[13.3 コントローラ]にある)。viewではじまるリソースへのリクエストや、ディレクトリexでの.htmlで終わるリクエストはsomeViewControllerにルーティングされる。2つのさらなるマッピングがeditAccountFormController用に定義されている。

HandlerInterceptorを追加する

Springのハンドラマッピングメカニズムはハンドラインタセプタの概念がある。これは例えば、リーダのチェックなど、特定の機能をあるリクエストに適用したい場合、にとても役に立つ。

ハンドラマッピングに置かれるインタセプタはorg.springframework.web.servletパッケージからHandlerInterceptorを実装しなければならない。このインタフェースは、3つのメソッドが定義されている。ひとつは、実際のハンドラが実行される前に呼ばれる。1つは、ハンドラが実行された後で呼ばれる。もうひとつは、リクエストが完全に完了した後で呼ばれる。この3つのメソッドで、全ての前処理、後処理を行うのに十分な柔軟性が提供されるはずだ。

このpreHandleメソッドはブール値を返す。このメソッドは、実行チェーンの処理を途切れさせたりつなげたりするのに使うことができる。このメソッドがtrueを返したら、ハンドラの実行チェーンは、継続される。falseを返したら、DispatcherServletはインタセプタ自身がリクエストを処理するとみなし(例えば、適切なビューをレンダリングするとか)、他のインタセプタや、実行チェーンにある実際のハンドラの実行を継続しない。

下記の例は、時刻が朝の9時から夕方6時の間でなければ、全てのリクエストをインターセプトし、ユーザを特定のページに転送する。

<beans>
    <bean id="handlerMapping"
class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
        <property name="interceptors">
            <list>
                <ref bean="officeHoursInterceptor"/>
            </list>
        </property>
        <property name="mappings">
            <props>
                <prop key="/*.form">editAccountFormController</prop>
                <prop key="/*.view">editAccountFormController</prop>
            </props>
        </property>
    </bean>

    <bean id="officeHoursInterceptor"
          class="samples.TimeBasedAccessInterceptor">
        <property name="openingTime"><value>9</value></property>
        <property name="closingTime"><value>18</value></property>
    </bean>
<beans>

パッケージのサンプル:

public class TimeBasedAccessInterceptor extends HandlerInterceptorAdapter {

    private int openingTime;
    private int closingTime;
    public void setOpeningTime(int openingTime) {
        this.openingTime = openingTime;
    }
    public void setClosingTime(int closingTime) {
        this.closingTime = closingTime;
    }
    public boolean preHandle(
            HttpServletRequest request,
            HttpServletResponse response,
            Object handler)
    throws Exception {
        Calendar cal = Calendar.getInstance();
        int hour = cal.get(HOUR_OF_DAY);
        if (openingTime <= hour < closingTime) {
            return true;
        } else {
            response.sendRedirect("http://host.com/outsideOfficeHours.html");
            return false;
        }
    }
}

現在時刻が営業時間外の場合、リクエストがTimeBasedAccessInterceptorによってインターセプトされ、ユーザはスタティックなhtmlファイルにリダイレクトされ、例えば、このウェブサイトは、オフィスアワー中しかアクセスできない、と伝える。

見てきたように、Springには、HandlerInterceptorを簡単に拡張できるようなアダプタが用意されている。

ビューとビューの解決

ウェブアプリケーション用のMVCフレームワークでは、全てビューをアドレスする方法が提供されている。Springではビューリゾルバが提供されていて、特定のビューテクノロジを新たに習得することなくモデルをブラウザにレンダリングできる。そのまんまでSpringでは、例えばJava Server Pagesや、Velocityテンプレートや、XSLTビューを利用することができる。<a href="#chap14"></a>では様々なビューテクノロジとの統合に関して詳しく述べる。

Springがビューを扱う上で重要な2つのインタフェースが、ViewResolverViewだ。ViewResolverはビュー名と実際のビューとのマッピングを提供する。Viewインタフェースはリクエストに対する準備をアドレスし、ビューテクノロジの中の1つにリクエストを渡す。

ViewResolvers

節[13.3 コントローラ]で議論したように、SpringウェブMVCフレームワークのコントローラはすべて、MOdelAndViewインスタンスを返す。SpringにおけるViewはビュー名でアドレスされ、ビューリゾルバによって解決される。Springにはかなり多くのビューリゾルバが用意されている。そのほぼ全てを挙げ、例を2,3挙げよう。

Viewリゾルバ
ビューリゾルバ 説明
AbstractCachingViewResolver キャッシングされたビューを扱う抽象的なビューリゾルバである。ビューは使われる前に準備が必要になることがある。このビューリゾルバを拡張すればビューをキャッシングすることができる。
XmlViewResolver Springのビーンファクトリと同じDTDのXMLで書かれたコンフィグレーションファイルを受け付けることができるViewResolverの実装である。デフォルトのコンフィグレーションファイルは、/WEB-INF/views.xmlである。
ResourceBundleViewResolver bundle basenameで指定されたResourceBundleでビーン定義を利用するViewResolverの実装である。このbundleは通常クラスパスにあるプロパティファイルの中で定義されている。デフォルトのファイル名はviews.propertiesである。
UrlBasedViewResolver シンボリックなビュー名を、明示的なマッピング定義なしでURLへ直接解決できるViewResolverの簡単な実装である。これは、シンボリック名が簡単な方法で任意のマッピングを必要とせずにビューリソースの名前にマッチする場合であればこれが適切である。
InternalResourceViewResolver InternalResourceViewをサポートするUrlBasedViewResolverの便利なサブクラス(つまり、サーブレットとJSP)であり、JstlViewTilesViewのようなサブクラスである。リゾルバにより生成されるビュークラスはすべてsetViewClassで指定することができる。詳細については、UrlBasedViewResolverのJavadocを参照して欲しい。
VelocityViewResolver / FreeMarkerViewResolver VelocityView(つまり、Velocityテンプレート)、あるいはFreeMarkerViewのそれぞれをサポートするUrlBasedViewResolverの便利なサブクラスであり、それらの独自サブクラスである。

例として、JSPをビューテクノロジとして使う場合にはUrlBasedViewResolverを使うことができる。このビューリゾルバはビュー名をURLに変換し、ビューを表示するためにリクエストをRequestDispatcherに渡す。

<bean id="viewResolver"
      class="org.springframework.web.servlet.view.UrlBasedViewResolver">
    <property name="prefix"><value>/WEB-INF/jsp/</value></property>
    <property name="suffix"><value>.jsp</value></property>
</bean>

ビュー名としてtestを返すと、このビューリゾルバはリクエストをRequestDispatcherに渡し、これが/WEB-INF/jsp/test.jspに渡す。

異なるビューテクノロジを1つのウェブアプリケーションで組み合わせる場合は、ResourceBundleViewResolverを使うことができる:

<bean id="viewResolver"
class="org.springframework.web.servlet.view.ResourceBundleViewResolver">
    <property name="basename"><value>views</value></property>
    <property name="defaultParentView"><value>parentView</value></property>
</bean>

ResourceBundleViewResolverbasenameで識別されたResourceBundleを精査し、解決される各ビューについて、ビュークラスとして[ビュー名].classというプロパティの値が、ビューのURLとして[ビュー名].urlというプロパティの値が用いられる。お分かりのように、プロパティファイルにある全てのビューから親のビューを識別することができる。このように、例えば、デフォルトのビュークラスを識別することができる。

キャッシュに関する注 - AbstractCachingViewResolverキャッシュビューインスタンスのサブクラスは解決できる。特定のビューテクノロジを用いた場合、パフォーマンスは劇的に改善される。キャッシュのプロパティをfalseに設定すれば、キャッシュをオフにできる。さらに、あるビューを実行時にリフレッシュできるようにしたい場合(例えばVelocityテンプレートが修正された場合)は、removeFromCache(String viewName, Locale loc)メソッドを使えばよい。

ViewResolverをつなぐ

Springでは複数のビューリゾルバがサポートされている。これにより、リゾルバをつないだり、例えばある状況で特定のビューをオーバライドしたりすることができる。ビューリゾルバをつなぐというのはとても単純だ。 - アプリケーションコンテキストに複数のリゾルバを追加して、必要であれば順序を指定するためにorderプロパティを設定する。注意して欲しいのは、orderのプロパティが高ければ高いほど、チェーンの中でのビューリゾルバの位置は後ろになる。

下記の例では、ビューリゾルバのチェインは、InternalResourceViewResolver(これは常に自動的にチェインの中で最後に設定される)と、Excelのビュー(これは、InternalResourceViewResolverではサポートされていない)を指定するXmlViewResolver2つのリゾルバからなる:

<bean id="jspViewResolver" \
    class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="viewClass" \
    value="org.springframework.web.servlet.view.JstlView"/>
  <property name="prefix" value="/WEB-INF/jsp/"/>
  <property name="suffix" value=".jsp"/>
</bean>

<bean id="excelViewResolver" \
    class="org.springframework.web.servlet.view.XmlViewResolver">
  <property name="order" value="1"/>
  <property name="location" value="/WEB-INF/views.xml"/>
</bean>

### views.xml

<beans>
  <bean name="report" class="org.springframework.example.ReportExcelView"/>
</beans>

特定のビューリゾルバ解決すべきビューがなかった場合、Springは他のビューリゾルバが設定されていないかコンテキストを精査する。他にビューリゾルバがあれば、これを引き続き精査する。もしなければ例外を投げる。

他にも気をつけておくべきことがある。- ビューリゾルバの仕様では、ビューリゾルバはビューが見つからないことを示すためにnullを返すことができる、とされている。でも全てのビューリゾルバがそうだとは限らない。これは、場合によっては、リゾルバが単にビューが存在するかどうかを判断できないからだ。例えば、InternalResourceViewResolverは内部でRequestDispatcherを使ってディスパッチするのがJSPが存在する場合の1つのやり方であり、これが唯一の方法だ。同じことはVelocityViewResolver他にについても当てはまる。もし、ビューが存在しない場合にそれを知らせないビューリゾルバを扱っているのであれば、ビューリゾルバのJavadocをチェックしてほしい。つまり、チェインの最後以外にInternalResourceViewResolverを置くのは、結果としてチェーンを全て精査されることはない、ということだ。というのは、InternalResourceViewResolver常にビューを返すからだ。

ビューをリダイレクトする

すでに述べられているように、コントローラは通常、ビューリゾルバが特定のビューテクノロジ用に解決する論理的なビュー名を返す。JSPのような実際にはサーブレット/JSPエンジンで処理されるビューテクノロジでは、サーブレットAPIのRequestDispatcher.forward()あるいはRequestDispatcher.include()をつかって、最終的にはInternalResourceViewResolver/InternalResourceViewforward、あるいはincludeされる。Velocity、XSLT等、他のビューテクノロジでは、ビュー自身がコンテンツをレスポンスストリームに生成する。

ビューがレンダリングされる前に、クライアントにHTTPをリダイレクトするのが望ましいことがある。これは、例えば、あるコントローラがPOSTされたデータで呼ばれ、レスポンスが実際には他のコントローラに委譲される(例えば、フォームのサブミットの成功とか)場合には望ましい。この場合、通常の内部的なフォワードは、他のコントローラが同じPOSTデータを参照するということであり、他の予期されるデータと混同する可能性がある場合は潜在的な問題となる。結果を表示する前にリダイレクトを行うほかの理由は、これによりユーザがフォームデータのサブミットを二度行うのを防ぐことにある。ブラウザが最初にPOSTを送ると、リダイレクトされたされたものが表示され、GETを実行する。これは関係ないので、カレントのページにはPOSTの結果が反映されるのではなく、GETの結果が反映される。よって、画面をリフレッシュすることで、ユーザが意図せず同じデータを再度POSTしてしまうことはない。このリフレッシュは、最初のPOSTデータを再送信するのではなく、結果のページをGETするするだけになる。

RedirectView

コントローラのレスポンスの結果として強制的にリダイレクトする方法の1つは、コントローラがSpringのRedirectViewのインスタンスを生成して返すようにすることだ。この場合、DispatcherServletは通常のビュー解決メカニズムを利用するのではなく、(リダイレクトする)ビューが与えられ、動作するように依頼するだけだ。

RedirectViewは単にHttpServletResponse.sendRedirect()を呼ぶ。これはクライアントブラウザにHTTPリダイレクトとして返される。モデルの属性はすべて単にHTTPのクエリパラメータとして示される。これはつまり、モデルは、文字列形式のHTTPクエリパラメータに変換できる(一般的には文字列あるいは文字列に変換できる)オブジェクトだけを含むべきであるというだ。

RedirectViewを使い、ビューはコントローラ自身から生成される場合、少なくともリダイレクトURLはコントローラに注入されるのが通常は望ましい。というのは、コントローラに埋め込まれるのではなく、ビュー名などと同様にコンテキストに設定されるほうが好ましいからだ。

redirect:プレフィクス

RedirectViewの利用がうまくいくのであれば、コントローラ自身がRedirectViewを生成している場合、コントローラにはリダイレクトが発生していることに気づくことはない。これは、本当に次善の策であり、コントローラはどのようにレスポンスが処理されるのかを意識するべきでない。通常注入されるビュー名という観点からのみ意識するべきなのだ。

特別なredirect:プレフィクスであれば、これが実現できる。もしredirect:プレフィクスをもつビュー名が返されると、 UrlBasedViewResolver(とそのサブクラスすべて)はリダイレクトが必要であるという特別な表示として認識する。残りのビュー名は、リダイレクトのURLとして扱われる。

正味の効果は、 コントローラがRedirectViewを返す場合と同じだが、コントローラ自身は 論理的なビュー名の観点で扱うことができる。redirect:http://myhost.com/some/arbitrary/path.htmlは絶対URLへリダイレクトするのに対し、redirect:/my/response/controller.htmlのような論理的なビュー名は相対的なカレントのサーブレットコンテキストへリダイレクトする。重要な点は、このリダイレクトビュー名は他の論理的ビュー名と同様にコントローラに注入されるがコントローラにはリダイレクトが発生していることはわからない、という点だ。

forward:プレフィクス

ビュー名がUrlBasedViewResolverやそのサブクラスによって解決されるように、特別なforward:プレフィクスを使うことができる。これはすべてURLとみなすビュー名に対しInternalResourceView(最終的にはRequestDispatcher.forward()が実行される)を生成する。したがって、とにかく(例えばJSP用に)InternalResourceViewResolver/InternalResourceViewを使う場合、このプレフィクスを使うことはないが、他のビューテクノロジをメインに使っていて、しかしサーブレット/JSPエンジンで処理されるリソースにフォワードが強制的に起こるようにしたい場合に用いることができる。もしこれを多用する必要があるのであれば、複数のビューリゾルバをチェインにしてもよいだろう。

redirect:プレフィクスと同様、このプレフィクスがついたビュー名がコントローラに注入されても、そのコントローラはレスポンスの処理において何か特別なことが起こることは意識しない。

ロケールを使う

Springのアーキテクチャがサポートする国際化の大部分は、SpringウェブMVCフレームワークでサポートされているものだ。DispatcherServletでは、自動的にクライアントのロケールを使ってメッセージを解決することができる。これはLocaleResolverオブジェクトで行われる。

リクエストが来たら、DispatcherServletはロケールリゾルバを探し、もしそれが見つかると、ロケールを設定するためにそれを使おうとする。RequestContext.getLocale()メソッドを使って、常にロケールリゾルバで解決されるロケールを検索することができる。

自動のロケール解決と合わせて、ハンドラマッピングにインタセプタをアタッチすることもでき(ハンドラマッピングインタセプタの詳細については節[HandlerInterceptorを追加する]を参照してほしい)、特定の状況で、例えばリクエストのパラメータに基づいてロケールを変更することができる。

ロケールリゾルバやインタセプタは全てorg.springframework.web.servlet.i18nパッケージで定義されており、通常の方法で、アプリケーションコンテキストで設定することができる。ここでは、Springに含まれているロケールリゾルバを列挙する。

AcceptHeaderLocaleResolver

このロケールリゾルバは、クライアントのブラウザによって送信されたリクエストにあるaccept-languageヘッダを識別する。通常、このヘッダフィールドにはクライアントのオペレーティングシステムのロケールが設定されている。

このロケールリゾルバはロケールを特定するために、クライアントに埋め込まれたクッキーを識別する。クッキーがあれば、ロケールを識別するために利用する。このロケールリゾルバのプロパティを使ってクッキー名を、最大の有効期限とともに識別することができる。

<bean id="localeResolver">
    <property name="cookieName"><value>clientlanguage</value></property>
<!-- in seconds. If set to -1, the cookie is not persisted (deleted when \
    browser shuts down) -->
    <property name="cookieMaxAge"><value>100000</value></property>
</bean>

これは、CookieLocaleResolverを定義する例である。

WebApplicationContextの特殊なビーン
プロパティ デフォルト 説明
cookieName classname + LOCALE クッキー名
cookieMaxAge Integer.MAX_INT クッキーがクライアントに永続化される有効期限。-1の場合は、クッキーは永続化されない。これは、クライアントがブラウザをシャットダウンするまでのみ有効である。
cookiePath / このパラメータを使って、あなたのサイトの特定部分にのみクッキーを見せることができる。cookiePathが指定されていると、そのクッキーはそのパスとその配下に対してのみ有効になる。

SessionLocaleResolver

SessionLocaleResolverを使えば、そのユーザのリクエストと関連付けられているセッションからロケールを検索することができる。

LocaleChangeInterceptor

LocaleChangeInterceptorを使ってロケールの変更処理を組み込むことができる。このインタセプタはハンドラマッピングの1つに追加する必要がある(節[ハンドラマッピング]を参照のこと)。これはリクエストにあるパラメータを検出し、ロケールを変更する(コンテキストにあるLocaleResolversetLocale()を呼ぶ。)

<bean id="localeChangeInterceptor"
      class="org.springframework.web.servlet.i18n.LocaleChangeInterceptor">
    <property name="paramName"><value>siteLanguage</value></property>
</bean>

<bean id="localeResolver"
      class="org.springframework.web.servlet.i18n.CookieLocaleResolver"/>

<bean id="urlMapping"
class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
    <property name="interceptors">
        <list>
            <ref bean="localeChangeInterceptor"/>
        </list>
    </property>
    <property name="mappings">
        <props>
            <prop key="/**/*.view">someController</prop>
        </props>
    </property>
</bean>

siteLanguageという名前のパラメータを含んだ*.viewリソースへの呼び出しはすべてロケールを変更する。よって、http://www.sf.net/home.view?siteLanguage=nlという呼び出しは、サイトの言語をオランダ語に変更する。

テーマを使う

イントロ

SpringウェブMVCフレームワークで提供されるテーマのサポートにより、アプリケーションのルックアンドフィールをテーマにあわせることで、ユーザエクスペリエンスを大幅に拡張することができる。テーマは基本的にアプリケーションのビジュアル的なスタイルに効果のある静的なリソースを集めたもので、よくあるものは、スタイルシートと画像だ。

テーマを定義する

ウェブアプリケーションにテーマを使いたい場合、org.springframework.ui.context.ThemeSourceを設定する必要がある。WebApplicationContextインタフェースはThemeSourceを拡張するが、実装に関する責任は委譲する。デフォルトでは、この委譲はクラスパスのルートからプロパティファイルを読むorg.springframework.ui.context.support.ResourceBundleThemeSourceになる。自前のThemeSourceの実装を使いたい、あるいはResourceBundleThemeSourcebasenameプレフィクスを設定する必要がある場合は、アプリケーションコンテキストに予約名"themeSource"でビーンを登録する。ウェブアプリケーションコンテキストは自動的にこのビーンを検出し、利用する。

ResourceBundleThemeSourceを使う場合、テーマは、単純なプロパティファイルに定義される。このプロパティファイルには、テーマを構成するリソースのリストが列挙されている。これは、その例だ。

styleSheet=/themes/cool/style.css
background=/themes/cool/img/coolBg.jpg

プロパティのキーは、ビューのコードからテーマを構成する要素を参照するに使われる名前だ。JSPではこれは通常、spring:themeというカスタムタグを用いて行われるが、これは、spring:messageタグととても似たものだ。下記のJSPの断片では、ルックアンドフィールをカスタマイズするために上述したテーマを使っている。

<taglib prefix="spring" uri="http://www.springframework.org/tags"%>
<html>
   <head>
<link rel="stylesheet" href="<spring:theme code="styleSheet"/>" \
    type="text/css"/>
   </head>
   <body background="<spring:theme code="background"/>">
      ...
   </body>
</html>

デフォルトでは、ResourceBundleThemeSourceは空のbasenameプレフィクスを使う。結果、プロパティファイルはクラスパスのルートから読み込まれ、テーマ定義cool.propertiesをクラスパスのルート、つまり/WEB-INF/classesに配置する必要がある。ここで注意して欲しいことは、ResourceBundleThemeSourceは標準のJavaリソースバンドル読み込みメカニズムを使っているので、テーマの完全な国際化が可能である、ということだ。例えば、オランダ語のテキストを表示するための特別な背景画像を参照する/WEB-INF/classes/cool_nl.propertiesを使うことができる。

テーマリゾルバ

テーマを定義したら、あとは、そのテーマを使うかどうかを決めるだけだ。DispatcherServletはどのThemeResolverの実装を使うかを見つけるために"themeResolver"という名前のビーンを探す。テーマリゾルバはLocaleResolverと同じ方法で動く。特定のリクエスト用に使うべきテーマを検出し、リクエストのテーマを変更することもできる。下記のテーマリゾルバがSpringで提供されている。

ThemeResolverの実装
クラス 説明
FixedThemeResolver 固定のテーマを選択し、"defaultThemeName"プロパティを使って設定する。
SessionThemeResolver テーマはユーザのHTTPセッションで維持される。各セッションごとに一度設定する必要があるだけだが、セッション間で永続化はされない。
CookieThemeResolver 選択されたテーマはクライアントのマシン上のクッキーに保存される。

Springでは、ThemeChangeInterceptorも用意されていて、簡単なリクエストパラメータを含めることにより各リクエストごとにテーマを変更することができる。

Springのマルチパート(ファイルアップロード)サポート

イントロ

Springではウェブアプリケーションで、ファイルアップロードを扱うマルチパートのサポートが組み込まれている。マルチパートサポートの設計は、org.springframework.web.multipartパッケージで定義されている、取り外しが可能なMultipartResolverオブジェクトで行われている。Commons FileUpload(http://jakarta.apache.org/commons/fileupload)やCOS FileUpload(http://servlets.com/cos)でそのまま使えるMultipartResolverがSpringでは提供されている。以降では、ファイルがどのようにアップロードされるかを述べる。

デベロッパの中には、自分でマルチパートの処理を行いたい人がいるように、デフォルトでは、Springではマルチパートの処理は行われない。マルチパートリゾルバをウェブアプリケーションコンテキストに追加することでこれが可能になる。追加すれば、各リクエストがマルチパートを含んでいるかどうかを見るために精査されるようになる。マルチパートが見つからなければ、リクエスト期待通りに継続される。しかしながら、マルチパートがリクエスト中に見つかれば、コンテキストですでに宣言されているMultipartResolverが利用される。そして、リクエスト中のmultipart属性が他の属性と同じように扱われる。

MultipartResolverを使う

下記の例は、CommonsMultipartResolverの使い方を示したものである:

<bean id="multipartResolver"
class="org.springframework.web.multipart.commons.CommonsMultipartResolver">

<!-- one of the properties available; the maximum file size in bytes -->
    <property name="maxUploadSize">
        <value>100000</value>
    </property>
</bean>

この例では、CosMultipartResolverを使った例だ:

<bean id="multipartResolver"
    class="org.springframework.web.multipart.cos.CosMultipartResolver">

<!-- one of the properties available; the maximum file size in bytes -->
    <property name="maxUploadSize">
        <value>100000</value>
    </property>
</bean>

マルチパートリゾルバが動作するにはもちろん、適切なjarをクラスパスで指定する必要がある。CommonsMultipartResolverの場合、commons-fileupload.jarを使う必要があるし、CosMultipartResolverの場合は、cos.jarを使う必要がある。

Springでマルチパートなリクエストを扱うためにどのように設定するかを見てきた。ここでは、実際にどうやって使うかの話をしよう。SpringのDispatcherServletがマルチパートなリクエストを検出すると、コンテキスト中に宣言されているリゾルバがアクティベートされ、リクエストに渡される。基本的なことは、カレントのHttpServletRequestがマルチパートをサポートするMultipartHttpServletRequestにラップされるということだ。MultipartHttpServletRequestを使ってこのリクエストに含まれているマルチパートに関する情報を取得し、実際のマルチパートそのものをコントローラに取得される。

フォームでファイルアップロードをハンドリングする

MultipartResolverが動作を終えると、リクエストは他と同じように処理される。これを使うために、アップロードフィールドのあるフォームを生成し、Springにフォームのあるファイルをバインドする。自動的にStringやプリミティブな型への変換は行われない他のプロパティと同様、バイナリデータをビーンに取り込むために、ServletRequestDatabinderでカスタムエディタを登録しないといけない。

したがって、ウェブサイトのフォームを使ってファイルのアップロードをできるようにするには、リゾルバ、ビーンを処理するコントローラにマッピングされるURL,そしてそのコントローラそのものを宣言する。

<beans>

    ...

    <bean id="multipartResolver"
class="org.springframework.web.multipart.commons.CommonsMultipartResolver"/>

<bean id="urlMapping" \
    class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
        <property name="mappings">
            <props>
                <prop key="/upload.form">fileUploadController</prop>
            </props>
        </property>
    </bean>

    <bean id="fileUploadController" class="examples.FileUploadController">
<property \
    name="commandClass"><value>examples.FileUploadBean</value></property>
        <property name="formView"><value>fileuploadform</value></property>
        <property name="successView"><value>confirmation</value></property>
    </bean>

</beans>

そして、コントローラとファイルプロパティを保持する実際のビーンを生成する。

// snippet from FileUploadController
public class FileUploadController extends SimpleFormController {

    protected ModelAndView onSubmit(
        HttpServletRequest request,
        HttpServletResponse response,
        Object command,
        BindException errors)
        throws ServletException, IOException {

        // cast the bean
        FileUploadBean bean = (FileUploadBean)command;

        // let's see if there's content there
        byte[] file = bean.getFile();
        if (file == null) {
            // hmm, that's strange, the user did not upload anything
        }

        // well, let's do nothing with the bean for now and return:
        return super.onSubmit(request, response, command, errors);
    }

    protected void initBinder(
        HttpServletRequest request,
        ServletRequestDataBinder binder)
        throws ServletException {
        // to actually be able to convert Multipart instance to byte[]
        // we have to register a custom editor (in this case the
        // ByteArrayMultipartEditor
        binder.registerCustomEditor(byte[].class, new ByteArrayMultipartFileEditor());
        // now Spring knows how to handle multipart object and convert them
    }

}

// snippet from FileUploadBean
public class FileUploadBean {
    private byte[] file;

    public void setFile(byte[] file) {
        this.file = file;
    }

    public byte[] getFile() {
        return file;
    }
}

これでわかるように、FileUploadBeanには、ファイルを保持するbyte[]型のプロパティがある。Springにリゾルバが見つけたマルチパートオブジェクトをビーンで指定されたプロパティに変換する方法を知らせるために、コントローラはカスタムエディタを登録する。この例では、ビーン自身にbyte[]プロパティは何もしないが、何でもできる(データベースに保存したり、誰かにメールしたり)。

でも、まだ終わりじゃない。実際にユーザが何かをアップロードするには、フォームを作らないといけない:

<html>
    <head>
        <title>Upload a file please</title>
    </head>
    <body>
        <h1>Please upload a file</h1>
<form method="post" action="upload.form" enctype="multipart/form-data">
            <input type="file" name="file"/>
            <input type="submit"/>
        </form>
    </body>
</html>

これでわかるように、byte[]を保持するビーンのプロパティにちなんだ名前のフィールドを作った。さらに、encoding属性を追加し、ブラウザがマルチパートフィールドをどうやってエンコードすればいいかを知らせる必要がある(これを忘れてはいけない)。これですべてだ。

例外をハンドリングする

Springでは、リクエストに合致したコントローラでリクエストが処理される間に期待していない例外が発生するのを防ぐためにHandlerExceptionResolverが用意されている。HandlerExceptionResolverはウェブアプリケーションデスクリプタweb.xmlで定義できる例外マッピングにいくらか似ている。しかしながら、これは例外をハンドリングするのに柔軟な方法を提供する。これは、例外が投げられたときに、どのハンドラが実行されるかについての情報を提供する。さらに、例外処理のプログラム的な方法によりリクエストが他のURLに転送される前に適切にレスポンスするためのオプションが提供される(例外マッピングに特化したサーブレットを使う場合と同じだ)。

resolveException(Exception, Handler)メソッドの実装とModelAndViewを返すというだけのHandlerExceptionResolverの実装と合わせて、SimpleMappingExceptionResolverも使うことができる。このリゾルバは投げられるかもしれない任意の例外のクラス名を取り、ビュー名にマッピングすることができる。これは、機能的には、サーブレットAPIの例外マッピング機能と同じではあるが、他のハンドラよりも、より精度の高い例外マッピングを実装することができる。