単純なプログラムを書換えよう(WebWork2編)−モデルドリブン
はじめに
ビューからコントローラ経由でモデルのメソッドを呼ぶのではなく,ビューから直接モデルのメソッドを使うやり方もある.WebWork2ではモデルドリブン(Model Driven)と呼ぶ.ビューのupdateメソッドの中でモデルから値を取り出すawtで作ったObserverと近いかもしれない.
コントローラとモデルの分離のソースをモデルドリブンで書換えてみた.
ファイル/ディレクトリ構成
プログラムを動かすために,以下のファイルを用意する.
- counter04-1.jsp/counter04-2.jsp
- ブラウザに表示するためのjsp.counter04-1.jspは最初に表示する画面,counter04-2.jspはプログラム起動後に遷移する画面.
- Controller.class/Model.class
- javaのクラス
- xwork.xml
- WebWork2の画面遷移等を指定するファイル
ディレクトリ構成は以下のようになる.各ファイルの内容はそれぞれ後述する.
${TOMCAT_HOME}/webapps/counter/counter04-1.jsp /counter04-2.jsp /WEB-INF/classes/counter04/Controller.class /Model.class /xwork.xml /lib/commons-logging.jar /ognl-2.6.3-modified.jar /oscore-2.2.1.jar /velocity-dep-1.3.1.jar /webwork-2.0.jar /xwork-1.0.jar /web.xml
URL
アクセスするためのURLは以下の通りとなる.
http://localhost:[ポート番号]/counter/counter04-1.jsp
ポート番号が8080の場合は,
http://localhost:8080/counter/counter04-1.jsp
となる.
counter04-1.jsp/counter04-2.jsp
counter03-1.jsp/counter03-2.jspを一部修正する.
<%-- counter04-1.jsp --%> <html> <head> <title>counter04-1</title> </head> <body> value:0<br/> <form action="Counter04.action"> <%-- xwork.xmlの記述にあわせてaction属性にはCounter04を指定 --%> <input type="hidden" name="value" value="0"/> <input type="submit" name="action" value="inc"/> <input type="submit" name="action" value="dec"/> </form> </body> </html> <%-- end --%> <%-- counter04-2.jsp --%> <%@ taglib prefix="ww" uri="webwork" %> <%-- WebWorkのカスタムタグを使うのでtaglibを宣言 --%> <html> <head> <title>counter04-2</title> </head> <body> value:<ww:property value="value"/><br/> <%-- Controller.getValue()ではなくModel.getValue()メソッドを使って結果を取出す.propertyはWebWorkのカスタムタグ --%> <form action="Counter04.action"> <%-- xwork.xmlの記述にあわせてaction属性にはCounter04を指定 --%> <input type="hidden" name="value" value="<ww:property value="value"/>"/> <%-- propertyタグがアクセスするのはModelのgetValue/setValue --%> <input type="submit" name="action" value="inc"/> <input type="submit" name="action" value="dec"/> <%-- inputタグがアクセスするのはControllerのgetAction/setAction --%> </form> </body> </html> <%-- end --%>
Controller.java
ModelDrivenインタフェースをimplementsし,モデルのアクセッサを呼ぶメソッドの代わりに,モデル自体を返すアクセッサ(getModel)を定義する.
//Controller.java package counter04; import com.opensymphony.xwork.*; public class Controller extends ActionSupport implements ModelDriven{ //ModelDrivenインタフェースをimplements private Model model=new Model(); private String action; public Object getModel(){ //モデルのアクセッサを呼ぶメソッドの代わりにgetModelメソッドを一つ用意する. return this.model; } public void setAction(String action){ this.action=action; } public String getAction(){ return action; } public String execute(){ if(getAction().equals("inc")){ model.inc(); } else{ model.dec(); } return SUCCESS; } } //end
Model.java
jspから呼ぶ,String型の返却値を持つアクセッサ(setValue/getValue)を定義する.
//Model.java package counter04; public class Model{ private int value; public Model(){ this(0); } public Model(int value){ this.setValue(value); } public void setValue(String value){ //jspから呼ぶString型の引数を持ったsetter this.setValue(Integer.parseInt(value)); } public void setValue(int value){ this.value=value; } public String getValue(){ //jspから呼ぶString型の返却値を持ったgetter return Integer.toString(this.getIntValue()); } public int getIntValue(){ return value; } public int inc(){ return inc(1); } public int inc(int value){ setValue(getIntValue()+value); return getIntValue(); } public int dec(){ return dec(1); } public int dec(int value){ setValue(getIntValue()-value); return getIntValue(); } } //end
xwork.xml
xwork.xmlに以下の修正を加える
- interceptor“model-driven”を追加
- Counter04を追加
interceptor“model-driven”を指定しないとモデルドリブンは使えない.エラーにはならないが,モデルのアクセッサを使用しての値の取得ができない.
<!-- xwork.xml --> <?xml version="1.0" encoding="ISO-8859-1"?> <!DOCTYPE xwork PUBLIC "-//OpenSymphony Group//XWork 1.0//EN" "http://www.opensymphony.com/xwork/xwork-1.0.dtd"> <xwork> <include file="webwork-default.xml"/> <package name="default" extends="webwork-default"> <default-interceptor-ref name="defaultStack"/> <!-- Counter04を追加 --> <action name="Counter04" class="counter04.Controller"> <result name="success" type="dispatcher"> <param name="location">counter04-2.jsp</param> </result> <interceptor-ref name="model-driven"/> <!-- interceptor“model-driven”を指定 --> <interceptor-ref name="defaultStack"/> </action> </package> </xwork> <!-- end -->
interceptor-stackを定義することも可能.複数のactionで同じinterceptorを使う場合は,こちらの書き方のほうが記述量が少ないかもしれない.
<!-- xwork.xml --> <?xml version="1.0" encoding="ISO-8859-1"?> <!DOCTYPE xwork PUBLIC "-//OpenSymphony Group//XWork 1.0//EN" "http://www.opensymphony.com/xwork/xwork-1.0.dtd"> <xwork> <include file="webwork-default.xml"/> <package name="default" extends="webwork-default"> <!-- モデルドリブンで動かすプログラムで使うinterceptor-stackを定義 --> <interceptors> <interceptor-stack name="defaultModelDrivenStack"> <interceptor-ref name="model-driven"/> <interceptor-ref name="defaultStack"/> </interceptor-stack> </interceptors> <default-interceptor-ref name="defaultStack"/> <!-- Counter04を追加 --> <action name="Counter04" class="counter04.Controller"> <result name="success" type="dispatcher"> <param name="location">counter04-2.jsp</param> </result> <interceptor-ref name="defaultModelDrivenStack"/> <!-- 個々のinterceptorではなくinterceptor-stackを指定 --> </action> </package> </xwork> <!-- end -->
補足
もう少し解説しよう.WebWork2はjspを表示する際にValueStackというスタックにActionインタフェースを実装(ActionSupportクラスを継承)したオブジェクト(以下アクションクラス)を配置する.jspとクラスはこのスタックを経由してデータのやり取りを行う.モデルドリブンではないプログラムの場合,スタックの先頭にはアクションクラスがある.モデルドリブンの場合は,アクションクラスの上にgetModelメソッドで返すオブジェクトを配置する.
jspからアクセッサを呼び出した場合,スタックを上から検索して該当するメソッドが見つかったらそれを実行する.今回の例の場合,次のようになる.
- Controllerよりもスタックで上にあるModelが,propertyタグで使用するgetValue/setValueを持っているため,propertyタグはModelのメソッドを使用する.
- inputタグで使用するsetAction/getActionメソッドはModelはもっていない.したがって,スタックの下にあるControllerのメソッドを使用する.
もし,ControllerにもModelと同じgetValue/setValueメソッドを持っていたとしても,スタックの一番上にあるのがModelであるため,Controllerの同名メソッドを使うことはない.明示的にControllerのメソッドを使いたい場合は,以下のように記述する.
<%-- counter04.jsp --%> <%@ taglib prefix="ww" uri="webwork" %> <html> <head> <title>counter04</title> </head> <body> value:<ww:property value="[1].value"/><br/> <%-- スタックの2番目の要素を指定する.先頭が[0]なので2番目は[1] --%> <form action="Counter04.action"> <input type="hidden" name="value" value="<ww:property value="[1].value"/>"/> <%-- スタックの2番目の要素を指定する.先頭が[0]なので2番目は[1] --%> <input type="submit" name="action" value="inc"/> <input type="submit" name="action" value="dec"/> </form> </body> </html> <%-- end --%>