Upload
ryo-asai
View
5.810
Download
4
Embed Size (px)
Citation preview
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、 Hessi
anなど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の実装を直接作成する必要はなく、目的に応じて抽象ベースクラスを継承して作成すればよいベースクラス名 利用するケース
AbstractController 単純に Controllerを実装する場合
MultiActionController 単一のコントローラクラスで複数のメソッドを実装したい場合
SimpleFormController フォームを表示して POSTバックする単純なフローの場合
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="org.springframework.web.servlet.view.XmlViewResolver"> <property name="order" value="1"/> <property name="location" value= " /WEB-INF/views.xml"/></bean>
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver" > <property name="order" value="2"/> <property name="prefix" value="/WEB-INF/jsp/ "/> <property name="suffix" value=".jsp"/></bean>
論理名に相当するビューがviews.xmlに定義されていたらそれを優先的使う
views.xmlで解決できない場合は、論理名に prefix、suffixを付加したパスを元に、 JSPファイルをビューとして使う
M-V-Cの連携のまとめ
Strategyによる多数の拡張点 ViewResolver以外にも、 Dispatcherサーブレットの動作をカスタマイズする多数の Strategyインタフェースの実装を DIコンテナ上に定義できる
インタフェース名 Strategyが実装すべき拡張ポイントの内容
HandlerMapping HTTPリクエストから該当するハンドラを解決する
HandlerAdapter ハンドラを特定のインタフェースに適合させる
HandlerInterceptor ハンドラの呼び出し前後でフィルタ処理を実装する
ViewResolver ビューの論理名からビューの実体を検索する
LocaleResolver 国際化のためのロケールを決定する
ThemeResolver スキンなどのテーマを決定する
MartipartFileResolver アップロードファイルを解決する
HandlerExceptionResolver ハンドラの発生させた例外を処理する
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インタフェースに非依存
実装クラス名 ロジックの概要
SimpleUrlHandlerMapping DI設定 XMLファイルで URLと Beanとの対応を指定
BeanNameUrlHandlerMapping URLを DIコンテナ中の Bean 名にとみなす
ControllerClassNameHandlerMapping Convention Over Configurationにより、設定ファイルなしで URLとコントローラのクラス名を自動的に対応させる( Spring2以降ではおすすめ)
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などの特定のインタフェースを実装する必要がなくなり、任意のクラスをリクエストハンドラとして利用できる
実装クラス名 ロジックの概要SimpleControllerHandlerAdapter Controllerインタフェースを実装したクラスを単に呼び
出す
ThrowawayControllerHandlerAdapter Struts2のようにリクエストごとに新規のハンドラインスを生成(コマンドパターン)してリクエストを処理を行う
AnnotationMethodHanderAdapter @MVCを実現するために、特定のアノテーションを付けられたメソッドをハンドラとしてリフレクション経由で呼び出す
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("ownerId") int ownerId) { return clinicService.loadOwner(ownerId); }
@RequestMapping public String updateOwner(@ModelAttribute Owner owner, BindingResult bindingResult) { ValidationUtils.invokeValidator(owner, bindingResult) if (bindingResult.hasErrors()) { return "selectOwner"; } else { clinicService.storeOwner(owner); return "ownerStored"; } }}
特定の親クラスを継承することは不要
コントローラクラスに複数のリクエスト処理メソッドを自由に定義できる
デフォルトでは規約により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