Spring MVC の概要と特徴 2009/6/14 浅井 良
Spring MVC の存在価値 「 車輪の再発明は無駄 」が Spring の基本思想 それにもかかわらず、初期のバージョンから独自の MVC フレームワークを提供しているのは、作者の Rod Johnson が 既存のどの Web フレームワークにも満足できなかった から DI コンテナとしての中核機能の中に埋もれてしまっているところがあるが、実は MVC フレームワークは最も慎重に設計された Spring Framework のサブコンポーネントの一つである
Spring MVC の特徴 伝統的なリクエストベースフレームワーク ⇒既存のフレームワークのよいところは取り入れつつ、問題点を克服 基本的には Struts1 と同様、開発者に既に広く受けいられている、サーブレット HTTP リクエストベースの MVC フレームワークである Web リクエストを抽象化するなど、他にも特徴的な Web フレームワークも存在するが、必ずしも一般に広く受け入れられていないのが実情 イベントベース ( JSF 、 Tapestry ) コマンドベース ( WebWork2 、 Struts2 ) Struts1 や WebWork2 の持つ設計上の欠陥の多くを解決 OOP 原則の徹底 ⇒「 インタフェースに対してプログラミング 」 「 開放/閉鎖原則 」「 制御の反転( IoC ) 」という OOP の原則が徹底して実践されている モデル 、 ビュー 、 コントローラ の間で真の 責務の分離 Strategy インタフェース による多数の 拡張ポイント アジャイル開発 ⇒ Spring 2.5 以降 (2008 年以降)登場した通称 @MVC と呼ばれる新しいプログラミングモデルにより、設定ファイルフリーの、より軽量な開発が可能に
Spring MVC の特徴(2) 全レイヤにおける設計の一貫性 ⇒サービス層を含めたアプリケーションの全レイヤが共通の設計思想に基づいたものになる 同様の方式で、バリデーションロジックを任意のレイヤで実装できる DI や AOP の機能を Web 層でも同様に利用できる 設定ファイルの形式が全レイヤで一貫して共通化される 他の FW との連携のための拡張性 JSP 以外に PDF や Excel 、 Velocity などさまざまなビュー FW と連携可能 Struts1 、 Struts2 、 JSF など別の MVC フレームワークと連携可能 HTML ・ JSP 開発に対する非侵略性 独自 Tag ライブラリの個数は Struts1 、 Struts2 に比べるとごく少ない 大部分は標準の JSTL を利用(⇒車輪の再発明はしない) HTML タグ自体をほとんどブラックボックス化しない Web サービスへの対応 SOAP Web サービス、 REST Web サービス、 HTTP Invoker 、 Hessian など Web アプリケーション以外でも HTTP を使う処理は DispatcherServlet の共通のしくみを利用 ドキュメントが非常に充実(ただし大部分英語) 詳細な Java DOC 記述 リファレンスマニュアルや多数の書籍が利用可能
Spring MVC の全体アーキテクチャ
サービス層の Bean 定義を含んだ親 DI コンテナ 特定の DispatcherServlet 専用の子 DI コンテナ (個々のコントローラ、各種 Strategy の実装が格納される) フロントコントローラとしての DispatcherServlet
Controller インタフェース public   interface  Controller { ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response)  throws  Exception; } Struts のアクションに相当し、 HTTP リクエストを処理して結果をビューに転送する責務を持つ ModelAndView クラスは、転送先のビューの論理名とビューに与えるモデルデータのマップをカプセル化 通常は Controller の実装を直接作成する必要はなく、目的に応じて抽象ベースクラスを継承して作成すればよい フォームを表示して POST バックする単純なフローの場合 SimpleFormController 単純に Controller を実装する場合 AbstractController 単一のコントローラクラスで複数のメソッドを実装したい場合 MultiActionController 順次ページをめくる複数画面から構成される画面フローの場合 AbstractWizardController 利用するケース ベースクラス名
Controller クラス階層
「 Command 」オブジェクト Spring MVC では伝統的にフォーム Bean に相当するオブジェクトのことを 「 Command 」オブジェクト と呼ぶ この奇妙な命名の由来は、おそらく( Struts2 の前身の) WebWork を意識し過ぎたことによる誤用と考えられる WebWork のようにリクエストごとにインスタンスが生成されて、パラメータがフィールドにバインドされることによると思われるが、 execute() に相当するメソッドが通常はないため、 Command パターンではない! WebWork ( Struts2) と同様、 任意の POJO クラス をコマンドオブジェクトとして利用可能 一方、 Struts1 では ActionForm という特定のベースクラスを継承する必要がある 型チェックエラーとなると実行時例外となり、上手く回復できないので、入力項目は実質的に String 以外のフィールドにバインドできない Date 、 Enum 、 BigDecimal 、 Money などの「 ValueObject 」に直接パラメータをバインドできない! サービス層にデータを渡すために別の DTO に対してわざわざデータの詰め替えを行う必要がある 通常はフォーム Bean⇒DTO⇒ エンティティと 2 回の詰め替えが必要 !!
Command オブジェクトの実装例 public   class  AccountForm { private  Account  account ; private   boolean   newAccount ; private  String  repeatedPassword ; public  AccountForm(Account account) { this . account  = account; this . newAccount  =  false ; } public  AccountForm() { this . account  =  new  Account(); this . newAccount  =  true ; } public  Account getAccount() { return   account ; } public   boolean  isNewAccount() { return   newAccount ; } public   void  setRepeatedPassword(String repeatedPassword) { this . repeatedPassword  = repeatedPassword; } public  String getRepeatedPassword() { return   repeatedPassword ; } } 特定のクラスを継承する必要はない エンティティ自身をコマンドにすることもできるが、このようにエンティティを入れ子フィールドとしてラップすることで、画面固有のフィールドを追加できる コマンドという名前が連想させるように Strtus2 のアクションと同様 HTTP パラメータがバインドされてくる。ただし、通常はデータ Bean であり、 execute() に相当するメソッドはない String 以外の任意の型に直接バインド可能
SimpleFormController の実装例 public   class  AccountFormController  extends  SimpleFormController { private  PetStoreFacade petStore; ・・・ public   void  setPetStore(PetStoreFacade petStore) { this .petStore = petStore; } ・・・ protected  Map referenceData(HttpServletRequest request)  throws  Exception { Map model =  new  HashMap(); model.put( "languages" , LANGUAGES); model.put( "categories" , petStore.getCategoryList()); return  model; } protected  void onSubmit(Object command)  throws  Exception { AccountForm accountForm = (AccountForm) command; if  (accountForm.isNewAccount()) { petStore.insertAccount(accountForm.getAccount()); }  else  { petStore.updateAccount(accountForm.getAccount()); } } 親クラスで Template メソッドが提供されるので、特定のフックメソッドをオーバーライド Comand クラス以外の参照データをモデルに追加して画面から参照する場合オーバーライド フォームの POST バック時の処理を実装 値を詰め替えずに直接サービス層にデータを渡すことが可能
無駄なデータつめかえの弊害 余分な詰め替えロジックによる 偶発的複雑性 の増加 変更の分散コーディングスメル による メンテナンスコスト増大 特に、 Hibernate や JPA を使う場合、詰め替えはアーキテクチャ上、致命的は欠陥をもたらす JPA では「 アプリケーショントランザクション 」の継続中、エンティティを POJO として直接 Web 層で保持するモデルを推奨(というかこれがほとんど前提のアーキテクチャ?) エンティティを保持する( Detach-Merge パターンか Extended PersistenceContext パターンのどちらかの処理パターン)ことにより 、楽観ロックチェックを含めてほとんどコーディングなしで、 DB 更新処理を自動的に実現できる 詰め替えを行った場合、通常の DAO を利用した場合とほとんど同じような面倒なコーディングが必要になり、 JPA 本来のメリットがほとんど失われてしまう! Struts1 と JPA との相性が悪いことが Seasar で JPA が S2Dao に比べて人気がない原因か?
複雑なクラス階層の利点と欠点 Controller のクラス階層を適切に継承することで、 TemplateMethod パターン による 差分プログラミング が実現される 特にウィザードのように画面遷移パターンが決まっている場合は、 Template Method による穴埋め問題化はメリットが非常に大きい ただし、 TemplateMethod は、静的なクラス階層に依存し、処理の流れを理解するためにある程度親クラスの実装を理解する必要があり(いわゆる グレーボックス継承 )、特に、 OOP の初心者にとってはどの親クラスを継承したらよいか判断にまようなど、敷居が高いという欠点がある Spring 2.5 以降では @MVC というまったく別のコントローラ作成方式が提供されている @MVC では従来のコントローラクラス階層を一切使用しない!(コントローラクラスは JUnit4 と同様、親クラスを継承する必要すらなくなった) 通常、 Spring2.5 以降では、 8 割~ 9 割の大部分のコントローラを新方式で作成し、パターン化が有効な少数の画面にのみ伝統的な TemplateMethod 方式を併用すればよい( @MVC については後述)
ModelMap クラス Spring MVC ではモデルに相当するデータは Map に格納してビューに渡す 特定のインタフェースを使って型を特定していないのは、ビューが Java コードとは限らず、最大限の柔軟性を持たせたいから( Variable State 実装パターン の応用) 対照的に Struts1 ではモデルデータが HttpServletRequest や HttpSession などの属性として格納される前提となっているため、そのままでは JSP 以外から簡単にアクセスすることができないことに! その結果 PDF ダウンロードを本来コントローラであるはずの Action クラスの中で直接実装することになってしまい、ビューとコントローラの分離が曖昧になってしまう Struts2 では ValueStack という考えの元で、モデルデータをサーブレット API から切り離しているので、 Struts1 のこの問題はない
View インタフェース Spring MVC ではサーブレットの出力を生成するロジックを View というインタフェースとして抽象化している View は JSP 画面に限定されない! Struts1 では View に相当する概念は存在しない Struts2 では Result が View に近い概念 以下の単純なインタフェースさえ実装すれば、 PDF 、 Excel 、 Csv ファイルなど任意の出力を生成させることが可能 public   interface  View { String getContentType(); void  render(Map model, HttpServletRequest request, HttpServletResponse response)  throws  Exception; } ( VariableState 実装パターンを使って) Map に格納された任意のデータを使って、出力を行えばよい
ViewResolver インタフェース コントローラから渡された ModelAndView に含まれる「論理ビュー名」を元に View のインスタンスを取得するロジックをカプセル化する public   interface  ViewResolver { View resolveViewName(String viewName, Locale locale)  throws  Exception; } DispatcherServlet の DI コンテナ上には複数の ViewResolver の実装を格納できる 複数の ViewResolver が格納されていた場合、 Order インタフェースの返す数値が示す優先順位にしたがって、ビューが順次解決される Struts では( Global フォワードを除き)わざわざリクエストごとにフォワード名と JSP を設定でマップする必要があるのに比べてスマート 典型的な ViewReso l ver の設定例 < bean   class = &quot;org.springframework.web.servlet.view.XmlViewResolver&quot; > < property   name = &quot;order&quot;   value = &quot;1&quot; /> < property   name = &quot;location&quot;   value =  &quot; /WEB-INF/views.xml&quot; /> </ bean > < bean   class = &quot;org.springframework.web.servlet.view.InternalResourceViewResolver&quot;   > < property   name = &quot;order&quot;   value = &quot;2&quot; />  < property   name = &quot;prefix&quot;   value = &quot;/WEB-INF/jsp/ &quot; /> < property   name = &quot;suffix&quot;   value = &quot;.jsp&quot; /> </ bean > 論理名に相当するビューが views.xml に定義されていたらそれを優先的使う views.xml で解決できない場合は、論理名に prefix 、 suffix を付加したパスを元に、 JSP ファイルをビューとして使う
M-V-C の連携のまとめ
Strategy による多数の拡張点 ViewResolver 以外にも、 Dispatcher サーブレットの動作をカスタマイズする多数の Strategy インタフェースの実装を DI コンテナ上に定義できる ハンドラを特定のインタフェースに適合させる HandlerAdapter ビューの論理名からビューの実体を検索する ViewResolver HTTP リクエストから該当するハンドラを解決する HandlerMapping ハンドラの呼び出し前後でフィルタ処理を実装する HandlerInterceptor 国際化のためのロケールを決定する LocaleResolver Strategy が実装すべき拡張ポイントの内容 インタフェース名 ハンドラの発生させた例外を処理する HandlerExceptionResolver アップロードファイルを解決する MartipartFileResolver スキンなどのテーマを決定する ThemeResolver
HandlerMapping インタフェースと HandlerExecutionChain クラス public   interface  HandlerMapping { HandlerExecutionChain getHandler(HttpServletRequest request)  throws  Exception; } HttpRequest の内容( URL パターンやパラメタなどどんな実装も可)に基づいてリクエスト処理を行うハンドラを解決する public   class  HandlerExecutionChain { private   final  Object handler; private  HandlerInterceptor[] interceptors; } ハンドラ呼び出し前後の処理をいくつでも追加できる Controller 型でなく Object 型になっている点に注目! 実は、正確には DispatcherServlet は Controller インタフェースに非依存 Convention Over Configuration により、設定ファイルなしで URL とコントローラのクラス名を自動的に対応させる( Spring2 以降ではおすすめ) ControllerClassNameHandlerMapping DI 設定 XML ファイルで URL と Bean との対応を指定 SimpleUrlHandlerMapping URL を DI コンテナ中の Bean 名にとみなす BeanNameUrlHandlerMapping ロジックの概要 実装クラス名
HandlerAdapter インタフェース 実は、 Spring MVC ではリクエストを行うハンドラは必ずしも Controller インタフェースを実装している必要がない HandlerAdapter によって、任意のオブジェクトを「リクエストハンドラ」として利用できる このしくみにより、たとえば、 Struts の Action を直接 DispatcherServlet から呼び出したり、後述の @MVC のように、任意の POJO をコントローラとして呼び出したりすることが可能になる public   interface  HandlerAdapter { boolean  supports(Object handler);  ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler)  throws  Exception; long  getLastModified(HttpServletRequest request, Object handler); } HandlerAdapter 経由で呼び出すことで、 Controller などの特定のインタフェースを実装する必要がなくなり、任意のクラスをリクエストハンドラとして利用できる @MVC を実現するために、特定のアノテーションを付けられたメソッドをハンドラとしてリフレクション経由で呼び出す AnnotationMethodHanderAdapter Controller インタフェースを実装したクラスを単に呼び出す SimpleControllerHandlerAdapter Struts2 のようにリクエストごとに新規のハンドラインスを生成(コマンドパターン)してリクエストを処理を行う ThrowawayControllerHandlerAdapter ロジックの概要 実装クラス名
Dispatcher サーブレットを中心としたリクエスト処理の流れの概要
@MVC による 新プログラミングモデル 2008 年春に登場した Spring2.5 以降、アノテーションを利用したまったく新しいコントローラ作成方法が登場 中身の仕組みは従来と変わっていないが、(エンドユーザとしての)プログラマの目に触れるプログラミングインタフェース上はまったく別の FW のように見える 設定ファイルの項目を単にアノテーション化したのではない Convention Over Configuration 機能と組み合わせることで、ほとんど XML の設定ファイルを書くことなく、非常に簡単にリクエスト処理メソッドを追加できる @MVC を上手に使うことで、 Ruby on Rails や SAStruts とほぼ同等のアジャイルな開発が Spring MVC 上で可能に ウィザードなど明らかにフローが決まっている場合は、従来の Controller クラス階層を継承する方法も併用できる(ただし、 MultiActionController はほとんど不要に!)
@MVC を使ったコントローラの例 @Controller public   class  ClinicController { @Autowired  ClinicService clinicService;  @Autowired  OwnerValidator ownerValidator; @RequestMapping public   void  openWelcome() {} @RequestMapping public  List<Owner> findOwners() { return   clinicService .findAllOwners()); } @RequestMapping public  Owner selectOwner( @RequestParam ( &quot;ownerId&quot; )  int  ownerId) { return   clinicService .loadOwner(ownerId); } @RequestMapping public  String updateOwner( @ModelAttribute  Owner owner, BindingResult bindingResult) { ValidationUtils.invokeValidator(owner, bindingResult) if  (bindingResult.hasErrors()) { return   &quot;selectOwner&quot; ; }  else  { clinicService .storeOwner(owner); return   &quot;ownerStored&quot; ; } } } 特定の親クラスを継承することは不要 コントローラクラスに複数のリクエスト処理メソッドを自由に定義できる デフォルトでは規約により URL 文字列とメソッドが対応 ハンドラの戻り値が ModelAndView や文字列以外だったら、ハンドラメソッド名が論理ビュー名になる。この場合 /WEB-INF/jsp/clinic/findOwners.jsp が表示されることに わざわざコマンドにバインドする必要がない場合は単一のパラメータとして引数に渡すことが可能 コマンドオブジェクトはそのまま引数で指定すれば自動的にバインドされてくる。バインド結果のエラーリストもパラメタに指定可能 ハンドラメソッドの戻り値が文字列だったら、これがビュー名になる
柔軟なハンドラメソッドのシグネチャ 以下の何れかを、任意の個数ハンドラメソッドのパラメータにとることが可能 サーブレットリクエスト、レスポンス HTTPSession オブジェクト @RequestParam のついたパラメータ 入出力ストリーム Map 、 ModelMap コマンドオブジェクト Errors 、 BindingResilt (バインド・バリデーションエラーが格納される) 戻り値は以下のいずれかの形式が可能 ビュー名の文字列 ModelAndView ビューオブジェクト Map (モデルとして解釈) その他のオブジェクトはモデルの属性として解釈される 特定のインタフェースを実装するのではなく、動的言語のような特性がある(その代わり、型安全性などはない)
@MVC 参考情報 http://www.infoq.com/jp/articles/spring-2.5-ii-spring-mvc http://static.springframework.org/spring/docs/2.5.x/reference/mvc.html# mvc-annotation

Spring mvc

  • 1.
    Spring MVC の概要と特徴2009/6/14 浅井 良
  • 2.
    Spring MVC の存在価値「 車輪の再発明は無駄 」が Spring の基本思想 それにもかかわらず、初期のバージョンから独自の MVC フレームワークを提供しているのは、作者の Rod Johnson が 既存のどの Web フレームワークにも満足できなかった から DI コンテナとしての中核機能の中に埋もれてしまっているところがあるが、実は MVC フレームワークは最も慎重に設計された Spring Framework のサブコンポーネントの一つである
  • 3.
    Spring MVC の特徴伝統的なリクエストベースフレームワーク ⇒既存のフレームワークのよいところは取り入れつつ、問題点を克服 基本的には Struts1 と同様、開発者に既に広く受けいられている、サーブレット HTTP リクエストベースの MVC フレームワークである Web リクエストを抽象化するなど、他にも特徴的な Web フレームワークも存在するが、必ずしも一般に広く受け入れられていないのが実情 イベントベース ( JSF 、 Tapestry ) コマンドベース ( WebWork2 、 Struts2 ) Struts1 や WebWork2 の持つ設計上の欠陥の多くを解決 OOP 原則の徹底 ⇒「 インタフェースに対してプログラミング 」 「 開放/閉鎖原則 」「 制御の反転( IoC ) 」という OOP の原則が徹底して実践されている モデル 、 ビュー 、 コントローラ の間で真の 責務の分離 Strategy インタフェース による多数の 拡張ポイント アジャイル開発 ⇒ Spring 2.5 以降 (2008 年以降)登場した通称 @MVC と呼ばれる新しいプログラミングモデルにより、設定ファイルフリーの、より軽量な開発が可能に
  • 4.
    Spring MVC の特徴(2)全レイヤにおける設計の一貫性 ⇒サービス層を含めたアプリケーションの全レイヤが共通の設計思想に基づいたものになる 同様の方式で、バリデーションロジックを任意のレイヤで実装できる DI や AOP の機能を Web 層でも同様に利用できる 設定ファイルの形式が全レイヤで一貫して共通化される 他の FW との連携のための拡張性 JSP 以外に PDF や Excel 、 Velocity などさまざまなビュー FW と連携可能 Struts1 、 Struts2 、 JSF など別の MVC フレームワークと連携可能 HTML ・ JSP 開発に対する非侵略性 独自 Tag ライブラリの個数は Struts1 、 Struts2 に比べるとごく少ない 大部分は標準の JSTL を利用(⇒車輪の再発明はしない) HTML タグ自体をほとんどブラックボックス化しない Web サービスへの対応 SOAP Web サービス、 REST Web サービス、 HTTP Invoker 、 Hessian など Web アプリケーション以外でも HTTP を使う処理は DispatcherServlet の共通のしくみを利用 ドキュメントが非常に充実(ただし大部分英語) 詳細な Java DOC 記述 リファレンスマニュアルや多数の書籍が利用可能
  • 5.
  • 6.
    サービス層の Bean 定義を含んだ親DI コンテナ 特定の DispatcherServlet 専用の子 DI コンテナ (個々のコントローラ、各種 Strategy の実装が格納される) フロントコントローラとしての DispatcherServlet
  • 7.
    Controller インタフェース public interface Controller { ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception; } Struts のアクションに相当し、 HTTP リクエストを処理して結果をビューに転送する責務を持つ ModelAndView クラスは、転送先のビューの論理名とビューに与えるモデルデータのマップをカプセル化 通常は Controller の実装を直接作成する必要はなく、目的に応じて抽象ベースクラスを継承して作成すればよい フォームを表示して POST バックする単純なフローの場合 SimpleFormController 単純に Controller を実装する場合 AbstractController 単一のコントローラクラスで複数のメソッドを実装したい場合 MultiActionController 順次ページをめくる複数画面から構成される画面フローの場合 AbstractWizardController 利用するケース ベースクラス名
  • 8.
  • 9.
    「 Command 」オブジェクトSpring MVC では伝統的にフォーム Bean に相当するオブジェクトのことを 「 Command 」オブジェクト と呼ぶ この奇妙な命名の由来は、おそらく( Struts2 の前身の) WebWork を意識し過ぎたことによる誤用と考えられる WebWork のようにリクエストごとにインスタンスが生成されて、パラメータがフィールドにバインドされることによると思われるが、 execute() に相当するメソッドが通常はないため、 Command パターンではない! WebWork ( Struts2) と同様、 任意の POJO クラス をコマンドオブジェクトとして利用可能 一方、 Struts1 では ActionForm という特定のベースクラスを継承する必要がある 型チェックエラーとなると実行時例外となり、上手く回復できないので、入力項目は実質的に String 以外のフィールドにバインドできない Date 、 Enum 、 BigDecimal 、 Money などの「 ValueObject 」に直接パラメータをバインドできない! サービス層にデータを渡すために別の DTO に対してわざわざデータの詰め替えを行う必要がある 通常はフォーム Bean⇒DTO⇒ エンティティと 2 回の詰め替えが必要 !!
  • 10.
    Command オブジェクトの実装例 public class AccountForm { private Account account ; private boolean newAccount ; private String repeatedPassword ; public AccountForm(Account account) { this . account = account; this . newAccount = false ; } public AccountForm() { this . account = new Account(); this . newAccount = true ; } public Account getAccount() { return account ; } public boolean isNewAccount() { return newAccount ; } public void setRepeatedPassword(String repeatedPassword) { this . repeatedPassword = repeatedPassword; } public String getRepeatedPassword() { return repeatedPassword ; } } 特定のクラスを継承する必要はない エンティティ自身をコマンドにすることもできるが、このようにエンティティを入れ子フィールドとしてラップすることで、画面固有のフィールドを追加できる コマンドという名前が連想させるように Strtus2 のアクションと同様 HTTP パラメータがバインドされてくる。ただし、通常はデータ Bean であり、 execute() に相当するメソッドはない String 以外の任意の型に直接バインド可能
  • 11.
    SimpleFormController の実装例 public class AccountFormController extends SimpleFormController { private PetStoreFacade petStore; ・・・ public void setPetStore(PetStoreFacade petStore) { this .petStore = petStore; } ・・・ protected Map referenceData(HttpServletRequest request) throws Exception { Map model = new HashMap(); model.put( &quot;languages&quot; , LANGUAGES); model.put( &quot;categories&quot; , petStore.getCategoryList()); return model; } protected void onSubmit(Object command) throws Exception { AccountForm accountForm = (AccountForm) command; if (accountForm.isNewAccount()) { petStore.insertAccount(accountForm.getAccount()); } else { petStore.updateAccount(accountForm.getAccount()); } } 親クラスで Template メソッドが提供されるので、特定のフックメソッドをオーバーライド Comand クラス以外の参照データをモデルに追加して画面から参照する場合オーバーライド フォームの POST バック時の処理を実装 値を詰め替えずに直接サービス層にデータを渡すことが可能
  • 12.
    無駄なデータつめかえの弊害 余分な詰め替えロジックによる 偶発的複雑性の増加 変更の分散コーディングスメル による メンテナンスコスト増大 特に、 Hibernate や JPA を使う場合、詰め替えはアーキテクチャ上、致命的は欠陥をもたらす JPA では「 アプリケーショントランザクション 」の継続中、エンティティを POJO として直接 Web 層で保持するモデルを推奨(というかこれがほとんど前提のアーキテクチャ?) エンティティを保持する( Detach-Merge パターンか Extended PersistenceContext パターンのどちらかの処理パターン)ことにより 、楽観ロックチェックを含めてほとんどコーディングなしで、 DB 更新処理を自動的に実現できる 詰め替えを行った場合、通常の DAO を利用した場合とほとんど同じような面倒なコーディングが必要になり、 JPA 本来のメリットがほとんど失われてしまう! Struts1 と JPA との相性が悪いことが Seasar で JPA が S2Dao に比べて人気がない原因か?
  • 13.
    複雑なクラス階層の利点と欠点 Controller のクラス階層を適切に継承することで、TemplateMethod パターン による 差分プログラミング が実現される 特にウィザードのように画面遷移パターンが決まっている場合は、 Template Method による穴埋め問題化はメリットが非常に大きい ただし、 TemplateMethod は、静的なクラス階層に依存し、処理の流れを理解するためにある程度親クラスの実装を理解する必要があり(いわゆる グレーボックス継承 )、特に、 OOP の初心者にとってはどの親クラスを継承したらよいか判断にまようなど、敷居が高いという欠点がある Spring 2.5 以降では @MVC というまったく別のコントローラ作成方式が提供されている @MVC では従来のコントローラクラス階層を一切使用しない!(コントローラクラスは JUnit4 と同様、親クラスを継承する必要すらなくなった) 通常、 Spring2.5 以降では、 8 割~ 9 割の大部分のコントローラを新方式で作成し、パターン化が有効な少数の画面にのみ伝統的な TemplateMethod 方式を併用すればよい( @MVC については後述)
  • 14.
    ModelMap クラス SpringMVC ではモデルに相当するデータは Map に格納してビューに渡す 特定のインタフェースを使って型を特定していないのは、ビューが Java コードとは限らず、最大限の柔軟性を持たせたいから( Variable State 実装パターン の応用) 対照的に Struts1 ではモデルデータが HttpServletRequest や HttpSession などの属性として格納される前提となっているため、そのままでは JSP 以外から簡単にアクセスすることができないことに! その結果 PDF ダウンロードを本来コントローラであるはずの Action クラスの中で直接実装することになってしまい、ビューとコントローラの分離が曖昧になってしまう Struts2 では ValueStack という考えの元で、モデルデータをサーブレット API から切り離しているので、 Struts1 のこの問題はない
  • 15.
    View インタフェース SpringMVC ではサーブレットの出力を生成するロジックを View というインタフェースとして抽象化している View は JSP 画面に限定されない! Struts1 では View に相当する概念は存在しない Struts2 では Result が View に近い概念 以下の単純なインタフェースさえ実装すれば、 PDF 、 Excel 、 Csv ファイルなど任意の出力を生成させることが可能 public interface View { String getContentType(); void render(Map model, HttpServletRequest request, HttpServletResponse response) throws Exception; } ( VariableState 実装パターンを使って) Map に格納された任意のデータを使って、出力を行えばよい
  • 16.
    ViewResolver インタフェース コントローラから渡されたModelAndView に含まれる「論理ビュー名」を元に View のインスタンスを取得するロジックをカプセル化する public interface ViewResolver { View resolveViewName(String viewName, Locale locale) throws Exception; } DispatcherServlet の DI コンテナ上には複数の ViewResolver の実装を格納できる 複数の ViewResolver が格納されていた場合、 Order インタフェースの返す数値が示す優先順位にしたがって、ビューが順次解決される Struts では( Global フォワードを除き)わざわざリクエストごとにフォワード名と JSP を設定でマップする必要があるのに比べてスマート 典型的な ViewReso l ver の設定例 < bean class = &quot;org.springframework.web.servlet.view.XmlViewResolver&quot; > < property name = &quot;order&quot; value = &quot;1&quot; /> < property name = &quot;location&quot; value = &quot; /WEB-INF/views.xml&quot; /> </ bean > < bean class = &quot;org.springframework.web.servlet.view.InternalResourceViewResolver&quot; > < property name = &quot;order&quot; value = &quot;2&quot; /> < property name = &quot;prefix&quot; value = &quot;/WEB-INF/jsp/ &quot; /> < property name = &quot;suffix&quot; value = &quot;.jsp&quot; /> </ bean > 論理名に相当するビューが views.xml に定義されていたらそれを優先的使う views.xml で解決できない場合は、論理名に prefix 、 suffix を付加したパスを元に、 JSP ファイルをビューとして使う
  • 17.
  • 18.
    Strategy による多数の拡張点 ViewResolver以外にも、 Dispatcher サーブレットの動作をカスタマイズする多数の Strategy インタフェースの実装を DI コンテナ上に定義できる ハンドラを特定のインタフェースに適合させる HandlerAdapter ビューの論理名からビューの実体を検索する ViewResolver HTTP リクエストから該当するハンドラを解決する HandlerMapping ハンドラの呼び出し前後でフィルタ処理を実装する HandlerInterceptor 国際化のためのロケールを決定する LocaleResolver Strategy が実装すべき拡張ポイントの内容 インタフェース名 ハンドラの発生させた例外を処理する HandlerExceptionResolver アップロードファイルを解決する MartipartFileResolver スキンなどのテーマを決定する ThemeResolver
  • 19.
    HandlerMapping インタフェースと HandlerExecutionChainクラス public interface HandlerMapping { HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception; } HttpRequest の内容( URL パターンやパラメタなどどんな実装も可)に基づいてリクエスト処理を行うハンドラを解決する public class HandlerExecutionChain { private final Object handler; private HandlerInterceptor[] interceptors; } ハンドラ呼び出し前後の処理をいくつでも追加できる Controller 型でなく Object 型になっている点に注目! 実は、正確には DispatcherServlet は Controller インタフェースに非依存 Convention Over Configuration により、設定ファイルなしで URL とコントローラのクラス名を自動的に対応させる( Spring2 以降ではおすすめ) ControllerClassNameHandlerMapping DI 設定 XML ファイルで URL と Bean との対応を指定 SimpleUrlHandlerMapping URL を DI コンテナ中の Bean 名にとみなす BeanNameUrlHandlerMapping ロジックの概要 実装クラス名
  • 20.
    HandlerAdapter インタフェース 実は、Spring MVC ではリクエストを行うハンドラは必ずしも Controller インタフェースを実装している必要がない HandlerAdapter によって、任意のオブジェクトを「リクエストハンドラ」として利用できる このしくみにより、たとえば、 Struts の Action を直接 DispatcherServlet から呼び出したり、後述の @MVC のように、任意の POJO をコントローラとして呼び出したりすることが可能になる public interface HandlerAdapter { boolean supports(Object handler); ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception; long getLastModified(HttpServletRequest request, Object handler); } HandlerAdapter 経由で呼び出すことで、 Controller などの特定のインタフェースを実装する必要がなくなり、任意のクラスをリクエストハンドラとして利用できる @MVC を実現するために、特定のアノテーションを付けられたメソッドをハンドラとしてリフレクション経由で呼び出す AnnotationMethodHanderAdapter Controller インタフェースを実装したクラスを単に呼び出す SimpleControllerHandlerAdapter Struts2 のようにリクエストごとに新規のハンドラインスを生成(コマンドパターン)してリクエストを処理を行う ThrowawayControllerHandlerAdapter ロジックの概要 実装クラス名
  • 21.
  • 22.
    @MVC による 新プログラミングモデル2008 年春に登場した Spring2.5 以降、アノテーションを利用したまったく新しいコントローラ作成方法が登場 中身の仕組みは従来と変わっていないが、(エンドユーザとしての)プログラマの目に触れるプログラミングインタフェース上はまったく別の FW のように見える 設定ファイルの項目を単にアノテーション化したのではない Convention Over Configuration 機能と組み合わせることで、ほとんど XML の設定ファイルを書くことなく、非常に簡単にリクエスト処理メソッドを追加できる @MVC を上手に使うことで、 Ruby on Rails や SAStruts とほぼ同等のアジャイルな開発が Spring MVC 上で可能に ウィザードなど明らかにフローが決まっている場合は、従来の Controller クラス階層を継承する方法も併用できる(ただし、 MultiActionController はほとんど不要に!)
  • 23.
    @MVC を使ったコントローラの例 @Controllerpublic class ClinicController { @Autowired ClinicService clinicService; @Autowired OwnerValidator ownerValidator; @RequestMapping public void openWelcome() {} @RequestMapping public List<Owner> findOwners() { return clinicService .findAllOwners()); } @RequestMapping public Owner selectOwner( @RequestParam ( &quot;ownerId&quot; ) int ownerId) { return clinicService .loadOwner(ownerId); } @RequestMapping public String updateOwner( @ModelAttribute Owner owner, BindingResult bindingResult) { ValidationUtils.invokeValidator(owner, bindingResult) if (bindingResult.hasErrors()) { return &quot;selectOwner&quot; ; } else { clinicService .storeOwner(owner); return &quot;ownerStored&quot; ; } } } 特定の親クラスを継承することは不要 コントローラクラスに複数のリクエスト処理メソッドを自由に定義できる デフォルトでは規約により URL 文字列とメソッドが対応 ハンドラの戻り値が ModelAndView や文字列以外だったら、ハンドラメソッド名が論理ビュー名になる。この場合 /WEB-INF/jsp/clinic/findOwners.jsp が表示されることに わざわざコマンドにバインドする必要がない場合は単一のパラメータとして引数に渡すことが可能 コマンドオブジェクトはそのまま引数で指定すれば自動的にバインドされてくる。バインド結果のエラーリストもパラメタに指定可能 ハンドラメソッドの戻り値が文字列だったら、これがビュー名になる
  • 24.
    柔軟なハンドラメソッドのシグネチャ 以下の何れかを、任意の個数ハンドラメソッドのパラメータにとることが可能 サーブレットリクエスト、レスポンスHTTPSession オブジェクト @RequestParam のついたパラメータ 入出力ストリーム Map 、 ModelMap コマンドオブジェクト Errors 、 BindingResilt (バインド・バリデーションエラーが格納される) 戻り値は以下のいずれかの形式が可能 ビュー名の文字列 ModelAndView ビューオブジェクト Map (モデルとして解釈) その他のオブジェクトはモデルの属性として解釈される 特定のインタフェースを実装するのではなく、動的言語のような特性がある(その代わり、型安全性などはない)
  • 25.
    @MVC 参考情報 http://www.infoq.com/jp/articles/spring-2.5-ii-spring-mvchttp://static.springframework.org/spring/docs/2.5.x/reference/mvc.html# mvc-annotation