taediumの日記

2008-06-22

[] オレオレStruts拡張

S2Strutsをメンテナンスしているのですが、メンテナンスしてみて思ったのは「S2Strutsって意外と機能が多くて間単に使いたいだけの人にとっては全体を把握しにくいだろうなぁ」ということです。

もっとシンプルなのがいいな、ということでこんな感じのオレオレStruts拡張を作ってみました。

  • ActionやActionFormは通常のStrutsと同じように継承して使う。
  • Actionのメソッドの呼び出しはパラメータ名に基づいて行う。
  • アノテーションでActionFormのバインディングを行う。
  • アノテーションでHttpServletRequest/HttpSessionのgetAttribute/setAttributeを自動化する。
  • Strutsにだけ依存するようにする。
  • Seasar(2.3系/2.4系)やSpring(1系/2系)など任意のDIコンテナと連携可能にする。
  • Struts1.2でも1.3でも動作可能にする。

シンプルが売りなのでHOT deployとか無設定とかはなし。地道にstruts-config.xmlを書きます。action要素のpath属性の値とコンポーネント名をつき合わせてDIコンテナ管理下のコンポーネントを呼び出します。struts-config.xmlを書くのは明らかに面倒くさいですが、Strutsを知っている人にはわかりやすいと思う。

ActionやActionFormにはユーティリティ的なメソッドが用意されているのでこれをそのまま利用できるよう継承を前提とします(Actionについては必須ではないけれど)。POJOにすると、これらのメソッドに相当する機能や規約が別個に必要になっちゃうのがいやなのです。


以下、例です。

jspとActionFormとActionは1対1対1で作成します。S2Strutsとかで使うInitActionの役割はActionに持たせます。

今回作ったオレオレStruts拡張ではデフォルトで「*」で始まるパラメータをメソッド名だとみなします。従業員一覧のlist.jspから次のURLのリンクをクリックするとします。ここでは「/example」をコンテキストパスとします。

http://localhost:8080/example/emp/list.do?*edit&empno=7369

パラメータに「*」ではじまる「*edit」が存在するので、editメソッドを呼び出します。「/emp/list」がstruts-config.xmlでListActionにマッピングされているとすると、ListActionのeditメソッドを呼び出します。ListActionのコンポーネントは「/emp/list」という名前でSeasar2やSpringのコンポーネントとして登録されている必要があります。

ListAction
public class ListAction extends Action {
    @Form
    public ListForm listForm;

    public String edit() {
        return "edit";
    }
}

@FormはActionFormであることを示すアノテーションでActionに1つだけ指定できます(ActionFormがない場合は指定しないことも可)。editメソッドを呼び出す直前で設定され、呼び出された後は、デフォルトでフィールド名をキーとしてリクエストスコープに格納されます。

戻り値の「edit」という文字列はActionForwarの名前です。ここでは、struts-config.xmlでedit.jspにフォワードするように指定されているとします。

フォワード先のjspでは、html:formタグの内側でglue:prepareタグを指定しておきます。役割はS2Strutsのinitタグとほとんど同じ。画面の表示に必要なデータを取得します。

edit.jsp
<html:form action="/emp/edit">
  <glue:prepare />
 ・・・
  <html:submit property="*confirm">確認</html:submit>
</html:form>

glue:prepareタグは、formタグのaction属性に対応するActionのprepareメソッドを呼び出します(任意の名前のメソッド名を指定することも可)。「/emp/edit」がEditActionにマッピングされているとすると、EditActionのprepareメソッドを呼び出します。

EditAction
public class EditAction extends Action {

    @Binding(bindingType = BindingType.MUST)
    protected EmpService empService;

    @Form
    protected EditForm editForm;

    @RequestAttribute
    protected ListForm listForm;

    public void prepare() {
        empno = Integer.valueOf(listForm.getEmpno());
        Emp emp = empService.getEmp(empno);
        BeanUtil.copyProperties(editForm, emp);
    }

    public String confirm() {
        return "confirm";
    }
}

@RequestAttributeは、フィールド名をキーとしてリクエストスコープの値をバインディングするアノテーションで、バインディングはprepareメソッドの呼び出し前に行われます。そのためprepareメソッドではフォワード元ですでに値が詰まっているActionFormの情報(listForm)を利用してこれから表示するデータを用意できます。@RequestAttributeがついたフィールドの値は、フィールド名をキーとして呼出し後にリクエストスコープに格納されます。

DIはDIコンテナまかせ。ここではempServiceをDIしています。どのDIコンテナでもsetterメソッドがあればDIされると思いますが、ここではSeasar2.4の例で@Bindingを使ってDIしています。

edit.jspから生成されたHTMLで確認ボタンを押すと、「*confirm」というパラメータが渡されEditActionのconfirmメソッドが呼び出されます。そして、「confirm」という名前のActionFowardが利用されてconfirm.jspにフォワードし、confirm.jspに記述されたglue:prepareタグからConfirmActionのprepareメソッドが呼ばれ。。。という流れになります。

2005-01-28

[] Struts メモ

http://husted.com/struts/

Strutsってちゃんとつかったことがないけど、近い将来もしかしたら必要になるかもしれないのでメモ。

2004-04-14

[][] Struts' DispatchAction vs. Spring's MultiActionController

まだ読んでいないけどおもしろそうだ。Web層でStrutsとStringどっちが使いやすいのか?ということが書いてあると思う。

2004-03-24

[][] Struts Spring Plugin

ソースファイルがたった2つオープンソースプロダクト。これなら理解できる!ということでひとこと。

これを使うとActionをBeanFactoryで管理できるようになる。すなわちActionはSpringのAPIを使わずにSpringのBeanFactoryによってInjectionされた値を使うことができる。

org.twdata.struts.SpringPlugIn
StrutsのPlugInインタフェースをimplementsしたクラス。設定ファイルからBeanFactoryをつくってServletContextにくっつける。
org.twdata.struts.SpringAction
Actionを拡張したクラス。ProxyパターンのProxy役(だと思う)。struts-config.xmlで呼び出されるように設定する。呼び出されると次の手順を踏む。1)ServletContextからBeanFactoryを取得する。2)BeanFactoryからActionを取得する。3)本物のActionを実行する。

簡単に書けばこういうことが実現できるようになる。

public class HogeAction extends Action {
    // BeanFactoryによってinjectionされる
  private Hoge hoge = null;    

    public void setHoge(Hoge hoge) {
        this.hoge = hoge;
    }    

    public ActionForward execute
        (ActionMapping mapping,
         ActionForm form,
         HttpServletRequest request,
         HttpServletResponse response) throws Exception {
        
        String something = hoge.getSomething();
    }
}

どうだろう?便利かな?