単純なプログラムを書換えよう(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 --%>