Spring MVCのススメ
先日、Struts1に代わるWebフレームワークの選択 - 達人プログラマーを目指してにて、現状アクションベースのMVCフレームワークとしてはSpring MVCが有望ということを書いたのですが、今までStrutsの影に隠れてあまり人気がないようですね。*1これから何が流行りそうかというマーケティング上の問題はおいておくとして、純粋に技術的な観点から、私がSpring MVCで気に入っているいくつかの点について説明します。
インターフェースに対するコーディングの徹底による拡張性の高さ
Spring MVCはDIコンテナーとしてのSpringのコア機能に隠れてあまり有名でないかもしれませんが、実は、Springが開発された当時から存在するコンポーネントです。ですから歴史的には意外に古く2003年くらいから存在しているということになります。(その原型は実践J2EEシステムデザインのサンプルコードにすでに存在している。)
そういう背景からか、Rod Johnsonの「インターフェースに対するプログラミング」というオブジェクト指向の基本原則が最初から徹底された(場合によってはちょっと過剰なくらいに?)設計となっています。したがって、フレームワークの基本的な機能の大部分がインターフェースとして提供されており、必要に応じて任意の実装を与えて拡張することが容易な設計となっています。つまり、Strategyデザインパターンがいたるところで応用されているということです。実際、以下のようなStrategyインターフェースが拡張ポイントとして定義されています。
Strategyインターフェース | 拡張ポイントの意味 |
---|---|
View | 画面表示を行うビューのロジックを実装 |
ViewResolver | ビューの論理名からビューのインスタンスを探すロジックを定義 |
HandlerMapping | URLリクエストとリクエストハンドラーとのマッピングロジックを定義 |
HandlerInterceptor | リクエストハンドラーに対する前後処理の定義 |
HandlerAdapter | リクエストハンドラーを特定のインターフェースに適合させる |
LocaleResolver | ロケールを解決するロジックを定義 |
HandlerExceptionResolver | 例外処理を行う方法を定義 |
たとえば、ViewResolverインターフェースは以下のように定義されています。
public interface ViewResolver { View resolveViewName(String viewName, Locale locale) throws Exception; }
定義を見ればこのインターフェースの実装すべき責務がなんとなく推測できますが、ビューの名前(とロケール)を受け取ってViewのインスタンスを返すロジックを実装すればよいということです。Spring MVCではあらかじめCoC的にビューの名前からjspファイル名にマッピングする実装(InternalResourceViewResolver)やxmlで明示的に定義されたJavaBeanをViewとして返す実装(XmlViewResolver)などが提供されています。しかし、プロジェクトの用途に応じて上記のインターフェースを実装すれば想像力の許す限りどのような実装も可能なわけです。しかも、多くの場合複数のStrategyインターフェースを複数組み合わせて利用することが可能になっています。つまり、各実装に優先度をつけることができ、優先順位の高いものから順に処理を委譲し、最初に結果を返すことのできるビューレゾルバーが実際の処理を行うというような動作をさせることができます。(これはChain of Responsibilityパターンの応用例と考えることもできるでしょう。)以下の例のように宣言すると、views.xmlにて論理名に対応するIDのBeanが宣言されている場合については、そのBeanが返されます。それ以外の場合はデフォルトで/WEB-INF/jsp/論理名.jspでマップされるjspファイルがビューとして利用されます。
<!-- - XmlViewResolverを使ってビュー名をviews.xml中で宣言されているBean名として解決します。 - 対応するBeanが定義されていない場合、後続のInternalResourceViewResolverで処理されます。 --> <bean class="org.springframework.web.servlet.view.XmlViewResolver" p:order="1"/> <!-- - ビュー名に対してprefixとsuffixをつけることでjspファイルにマッピングします。 - デフォルトで、このリゾルバー経由でJSP画面が表示されます。 --> <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver" p:prefix="/WEB-INF/jsp/" p:suffix=".jsp" p:order="2"/>
order属性が小さなもの程、優先順位が高いので先に問い合わせられますが、最終的にいずれかひとつの実装がビューを返します。なお、PetClinicというサンプルアプリケーションではコンテンツタイプによってビューを選択するような、より複雑なビュー解決ロジックを使った例が使われています。
M、V、Cの真の分離
Spring MVCでは名前のとおり、モデル、ビュー、コントローラを明確に分離することができます。フレームワークによってはMVCと言ってもモデルとコントローラの境界があいまいなケースも少なくありません。たとえば、Struts2はCommandパターンに基づいており、アクションクラスがコントローラとモデルの役割を兼任します。つまり、アクションが画面に対する入出力データの保持とロジックの両方の責務を持つためコントローラーとモデルの境界があいまいになっています。*2
また、Struts1ではViewに対する抽象化が不十分なため、JSP以外のコンテンツを返すような場合は、通常アクションクラス内にダウンロードのロジックを直接記述することになります。一方、Spring MVCの場合、Viewインターフェースは以下のように定義されています。
public interface View { String RESPONSE_STATUS_ATTRIBUTE = View.class.getName() + ".responseStatus"; String getContentType(); void render(Map<String, ?> model, HttpServletRequest request, HttpServletResponse response) throws Exception; }
つまりビューとはMapに格納されたモデルデータを使って、HttpServletResponseに書き出すことをする抽象的な「もの」であるとして、責務が明確に定義されています。*3ただし、逆にこのインターフェースさえ実装すればJSPでもCSVダウンロードでも、さらに、PDF帳票生成でも何でもよいということです。Springの最新バージョンではこの拡張性の高さを活かして、Atomフィードを行ったりDTOをJSONに変換するなどのビュー実装が追加されています。コントローラーの責務は業務ロジックを呼び出してから、結果をビューに渡すまでとなり、あとはビューの実装が画面描画やデータダウンロードの処理を多態的に実行します。
このような分割で、結局何がうれしいかですが、コントローラの処理と画面生成の処理が分離されることで、特殊な画面生成ロジックがカプセル化されて再利用性が高くなる一方で、個々のコントローラの処理は簡単になるということが大きいです。スキルの高いプログラマーが帳票ダウンロードなど特殊なビューの生成ロジックを共通部品として一箇所で実装しておけば、個々のリクエストごとのハンドリングは平均的なプログラマーに任せられるため、チームの編成も容易になります。
@MVCと呼ばれる新しいプログラミングモデルの採用
今のところSpring MVCについて記述された日本語の本では、既に時代遅れのバージョンのことしか書かれていないこともあり、Spring MVCというとSimpleFormControllerなどの特定の親クラスを継承してコントローラーを作成する(やや侵略的な)方法を思い浮かべる方も多いと思います。しかし、Spring3ではこの古い手法は既にDeprecated化されており、@MVCと呼ばれるまったく新しいプログラミングモデルで置きかわっています。この新しいモデルではアノテーションを活用することで設定ファイルの量が激減しますし、コントローラークラスもPOJOとなり、設計自由度が高くなっています。*4
先に紹介したSpringの拡張性を活用することで、内部的なアーキテクチャーには大きな変更がされていないのに、表面上ここまで別の方式を定義できてしまうというところはオブジェクト指向言語を活用したアーキテクチャー設計がうまく行われているのだと思います。*5
@MVCについては、ちょっと古い記事ですが以下が参考になると思います。
Spring 2.5:Spring MVCの新機能
マルチアクションコントローラー作成が容易
意外に盲点となりそうなところですが、Spring MVCの特徴として、1クラスで複数の異なるタイプのリクエスト処理メソッドを複数定義できるマルチアクションコントローラーの作成が容易であるということが挙げられます。Struts1ではDispatcherActionなどの特殊形を除くと基本的に1アクション1クラスなのですが、Struts2やSAStrutsなどの新しいフレームワークについても実質的には1アクション1クラスが原則となる傾向があります。それはこれらのフレームワークにおけるアクションクラスがCommandパターンに基づいており、1リクエストごとにActionのインスタンスを生成するしくみとなっているからです。したがって、リクエストパラメーターはアクションメソッドのパラメーターとしてではなくフィールドにバインドされます。これらのフレームワークでは、形式的にはアクションクラスに複数のアクションメソッドを定義することは一応可能なのですが、よほど関連性の高いメソッドでもない限り、複数のメソッドを定義するとリクエストごとに使われるフィールドとそうでないフィールドが同一クラス内に混在することになり、わかりにくいプログラムになってしまいます。
一方、Spring MVC(@MVC形式)の場合、コントローラークラスのアクションメソッドのパラメーターとしてリクエスト情報が渡されます。したがって、個々のメソッドの独立性を確保しながら容易に複数のメソッドを追加していくことが可能です。アクションメソッドのシグネチャーとしては、以下のようなさまざまなものを定義できます。
入力パラメーター
- サーブレットリクエスト、レスポンス
- HTTPSessionオブジェクト
- @RequestParamのついたパラメータ
- クッキーの値
- 入出力ストリーム
- Map、ModelMap
- フォームオブジェクト
- Errors、BindingResilt(バインド・バリデーションエラーが格納される)
戻り値
- ビュー名の文字列
- ModelAndView
- ビューオブジェクト
- Map(モデルとして解釈)
- その他のオブジェクトはモデルの属性として解釈される
完全に自由というわけにはいきませんが、動的言語を使ったRuby on Railsのような感覚で、1つのコントローラクラスに対して気軽に複数のメソッドを定義できるのは慣れるとやはり便利です。特にAjaxを併用してあるメソッドからJSONの結果を返却させるとか、Excelをダウンロードするなどという場合に、別々のクラスを定義せずに同じコントローラで処理できるのは凝集性の高さという観点からもわかりやすいプログラムが書きやすいです。
まだまだ発展途上のフレームワーク
Spring MVCの魅力のひとつとして、現在でもまだまだ進化の可能性が残されていると思われることがあります。実際、従来は弱点として認識されていた以下のような機能がSpring3では追加されています。
- JSR303(BeanValidation)を使った簡単な入力チェック
- REST的なURLに対する対応
- JSONなどへの変換
- コントローラークラスごとのローカルな例外ハンドリング
- HTTPのレスポンスボディーを直接返却
個人的には今のところSpring WebFlowとの統合が不十分で、会話管理の機能が弱いと思っているのですが、Spring 3.1になってCDIの機能が取り込まれれば、さらに、強化される可能性があるのではないかと期待しています。
Spring MVCのサンプルプログラム
Spring MVCを勉強するには、まず以下のSVNからサンプルを取得して動作させてみるのがよいと思います。
https://src.springframework.org/svn/spring-samples/
以下にSpring MVCの一通りの機能を示すデモの説明があります。
Spring MVC 3 Showcase
*1:日本でStrutsが使われるのが純粋に政治的理由が大きいと思いますが。Springなどという得体の知れないものを導入するための手段として「実績のある」Strutsと組み合わせることも多いと思うので。
*2:ModelDrivenを実装することで両者を分離することは一応可能ですが。
*3:Struts2にはResultというビューに対する抽象が存在しており、この点はSpring MVCと似ています。
*4:逆に柔軟性が高すぎてパターン化されていないため、初心者はどうやってコードを書いたらよいか戸惑うところもありますが。
*5:ちなみに、MVCに限らず、私がSpringでもっとも気に入っているところはアーキテクチャーの安定性がずっと長いこと保たれているところですね。設定ファイルの書き方やAPIなどの表面的な部分はバージョンアップするごとに大幅に進歩していますが、内部のアーキテクチャーは比較的安定しており、連続的に進化しています。ここがSeasar、Struts、Seam、Tapestry、(JavaEE仕様そのものも?)と比較した場合のSpringの大きなメリットだと思います。他のフレームワークはメジャーバージョンが上がると以前とまったく別といってよい程、しくみやアーキテクチャーが変わっていることが多いです。やはり、エンタープライズ開発だと、段階的に進化させることができるのは使う側としては非常にありがたいことだと思います。