Legacy Struts Best Practices~汎用Action継承クラス~

現在、Java1.4、Struts1.2というレガシーな環境に身を置いています。

この限られた(?)環境の中で

「如何にフレームワークのポテンシャルを引き出せるのか?」

をテーマに、ベストプラクティスを日々模索しています。

フレームワークは様々なケースで使われるように

汎用的な部品を提供してくれています。

実際の開発においては、

利用することになったフレームワークを

まず第一に、システム要件に「特化」させ、

開発自体を容易にすべきだ、というのが僕の考えです。

僕の現環境は、

・Java1・4

・Struts1.2

・中小規模Webアプリケーション開発中心(企業グループ内イントラ)

という訳で、上記に適したStrutsの拡張を考えてみました。

今更、といった感じですが、

まだStrutsの案件を抱えている方も中にはいるかもしれないので、

その方(自分)の為に・・・

様々な拡張点が考えられますが、

少しずつエントリに上げていきたいと思います。

おかしな所あったら容赦なく指摘して下さい。

(その為に大それたタイトル付けました)

まずはタイトルにもある、これから。

汎用Action継承クラス

Strutsを使うにあたって、何が一番鬱かというと、

・struts-config.xmlのactionタグのコーディング

・Action継承クラスのコーディング

これに限ると思います。

ボタン毎にAction継承するクラスを定義するのも

struts-config.xmlにaction要素を記述するのもアレなので、

汎用的なActionを定義して、それを使い回す事にします。

また、「struts-config.xml」=「画面遷移図の実装」と位置付け、

単なるフレームワーク上の設定ファイルから昇華させます。

struts-config.xmlを見るだけで、どの画面に行って、

その画面からどういう処理が走って、どの画面に遷移するかが判れば、

ムダにJSPファイルと睨めっこしなくて済みます。

「汎用的」とは何か?

僕は「struts-config.xmlの複数のactionタグのtype属性で使いまわす」事だと考えました。

しかし、「一フォーム内での複数サブミットボタンによる処理の振り分け」で困った事になります。

「一フォーム内での複数サブミットボタンによる処理振り分け」、

という問題に対処する為に

Strutsで容易されてるAction継承クラス、

DispachAction・LookupDispachAction・MappingDispachActionでは、

汎用的なActionを作りづらいからです。

どれも、それぞれのボタンに合わせたメソッドを定義する必要があります。

DispachActionはリクエストパラメータの値を、

LookDispachActionはリクエストパラメータの値をリソースバンドルから逆引きしたキーを、

MappingDispachActionはstruts-config.xmlのactionタグparameter属性の値を、

それぞれメソッド名として定義せねばなりません。

要するに、一つの上記Action継承クラスを作った場合、

Actionクラスの中に押されたボタンに対応するメソッドを定義する為、

「struts-config.xmlの複数のactionタグのtype属性で使いまわす」事ができないのです。

できればexecuteメソッド内で押されたボタンを判別したいので、

「押されたボタンを判別する新たな方法」を編み出す必要があります。

JavaScriptでhiddenタグにonClickイベントで値を埋め込んでやれば簡単なのですが、

JavaScriptをオフにされた時を考えると、それは却下せざるを得ません。

(このAjax万歳な時代にはレアケースだと思いますが・・・)

と、いう訳で考えました。

題して、「DispachForwardパターン」&「DispachLogicパターン」

まずはJSPから。フォーム部分です。

/WEB-INF/jsp/item.jsp
<html:form action="/item">
  <input type="submit"name="search"value="検索">
  <input type="submit"name="entry"value="登録">
  <input type="submit"name="delete"value="削除">
  <html:cancel value="キャンセル"></html:cancel>
  <input type="submit"name="back"value="戻る">
</html:form>

これだけ。

ポイントはname属性。

どれかボタンが押されると、

リクエストに「{name}={value}」といった感じでパラメータが付きます。

これを利用します。

struts-config.xmlのaction-mappings。
<action 
    path="/item"
    type="zetta.web.base.DispachForwardAction"
    name="ItemForm">
  <forward name="search"path="/itemSearch.do"/>
  <forward name="entry"path="/itemEntry.do"/>
  <forward name="delete"path="/itemDelete.do"/>
  <forward name="cancel"path="/WEB-INF/jsp/item.jsp"/>
  <forward name="back"path="/WEB-INF/jsp/home.jsp"/>
</action>
<action 
    path="/item*"
    type="zetta.web.base.DispachLogicAction"
    name="ItemForm"
    parameter={1}>
  <forward name="entry"path="/WEB-INF/jsp/confirm.jsp"/>
  <forward name="success"path="/WEB-INF/jsp/item.jsp"/>
</action>
zetta.web.base.DispachForwardActionクラス

そして、Action継承クラスexecuteメソッドのオーバーライド。

public ActionForward execute(
    ActionMapping mapping
    , ActionForm form
    , HttpServletRequest request
    , HttpServletResponse response) throws Exception {

    if (isCancelled(request)) {
        ActionForward af = cancelled(mapping, form, request, response);
        if (af != null) {
            return af;
        }else {
            throw new DispachForwardException(
            "論理名'cancel'のForwardを定義して下さい。");
        }
    }

    Set<String> paramSet = request.getParameterMap().keySet();

    List forwardList = Arrays.asList(mapping.findForwards());

    if (paramSet.isEmpty() || forwardList.isEmpty())
        throw new DispachForwardException();

    String forwardName = null;
    for(Iterator ite = paramSet.iterator(); ite.hasNext();){
        String requestKey = (String)ite.next();
        if (!requestKey.equals("") && forwardList.contains(requestKey)){
            forwardName = requestKey;
        }
    }

    if (forwardName == null)
        throw new DispachForwardException(
            "submitButtonのnameの値と" +
            "Actionタグ内Forward要素のname値を一致させて下さい");

    ActionForward af = mapping.findForward(forwardName);
    if (af == null)
        throw new DispachForwardException("Forward先を確認して下さい");

    return af;
}

これは、Action振り分け用ActionとModelビジネスロジック実行用Actionとで

Actionの役割を分ける、「Actionリレー」と呼ばれるパターンを利用したものです。

zetta.web.base.DispachLogicActionはactionタグのpath属性値やparameter属性値等から

ビジネスロジックBeanを生成する「ID」を作成し、

それをFactoryMethodに渡してBeanを生成、処理を実行後、

Forwardタグのname属性にactionタグのparameter属性値が存在した場合は

そのForwardタグのpath属性値にforwardし、

無ければ「success」にForwardするだけです。

(上記の場合、「登録」ボタンを押したときのみ、/WEB-INF/jsp/confirm.jspに遷移します)

前画面からの遷移等は、/itemOpen.doなどで

初期処理を実装したビジネスロジックBeanを起動させればいいと思います。

簡単に「検索」ボタンを押された場合の流れを書くと・・・

  1. /WEB-INF/jsp/item.jspで「検索」ボタンを押す。

  2. ブラウザが「{ホスト}/item.do?search=検索」でサーバにリクエストを送る。

  3. zetta.web.base.DispachForwardActionクラスexecuteメソッド内で、

  リクエストパラメータの全キーとactionタグの子要素forwardタグname属性値をマッピング。

  1. 3で一致したforwardタグのpath属性値「/itemSearch.do」へフォワード。

  2. zetta.web.base.DispachLogicActionでビジネスロジックBeanの処理を実行。

  3. /WEB-INF/jsp/item.jspにフォワード。

この方法だと、

一つの画面に対して個別のAction継承クラスを作る事が無くなり、

struts-confing.xmlにはactionタグを2つ記述する事になります。

ボタンを増やしても、forward要素を追加して、対応するビジネスロジックBeanを実装するだけで済みます。

ただ、また新たな問題が発生します。

executeメソッドの前後処理の内容を変えたい場合

共通の前後処理はDispachForwardActionクラス又はDispachLogicActionクラスに記述すればいいのですが、

それを変えたい場合、1つ2つなら良いのですがそれ以上となると、

新たにクラスを作成する手間が増えます。

ビジネスロジックBeanでDB等から取得した値をどうやって画面に反映するか。

もちろん、ActionFormインスタンスをビジネスロジックBeanのメソッドに引数として渡す、

なんて事はしてはいけません。

せっかくの疎結合がぶち壊しになります。

僕なりの答えは、次のエントリで。

このエントリーをはてなブックマークに追加
comments powered by Disqus