Strutsメモ6

2002/2/15 作成
2003/8/8 更新

Struts1.1

Struts1.1の新機能について見ていこうと思います。

なお、このあたりは、Jakarata本(Jakarataプロジェクト徹底攻略) をかなり参考にしました。

Action#execute()

Actionクラスの実行メソッド、 Action#perform()がdeplecatedになり、 Action#execute()に変更になりました。

  public ActionForward perform(
    ActionMapping mapping,
    ActionForm form,
    HttpServletRequest request,
    HttpServletResponse response)
    throws IOException, ServletException {
    ...
  }
  public ActionForward execute(ActionMapping mapping,
    ActionForm form,
    HttpServletRequest request,
    HttpServletResponse response)
    throws Exception {
    ...
  }
何が変わったというと、太字のスローする例外の部分です。 これまで、ServletExceptionにラッピングして投げていたような例外を そのままスローできるようになりました。 システムエラーに近いSQLExceptionやRemoteExceptionなどの チェック例外もそのままスローできます。

struts-config.xmlとApplicationResource.propertiesに 例外の種類とメッセージ、遷移先を記述することで、 汎用的なエラー画面を作成できます。

struts-config.xml

    <global-exceptions>
        <exception
            key="error.sql"
            type="java.sql.SQLException"
            path="/error.jsp"/>
        <exception
            key="error.remote"
            type="java.rmi.RemoteException"
            path="/error.jsp"/>
        <exception
            key="error.any"
            type="java.lang.Exception"
            path="/error.jsp"/>
    </global-exceptions>

ApplicationResource.properteis

error.sql=データベースのエラー
error.remote=通信エラー
error.any=何だかエラー

web.xmlのexception要素でも似たようなことができますが、 ActionServlet側で制御するのと、Servlet側でチェック例外 が投げれるところがちょっと違うようです。

Struts1.1より前では、自前でActionServletの継承クラスやActionの基底クラスを作って、 汎用例外の共通処理を書いてた処理をフレームワーク側の設定ファイルで 書けるようになった、という感じです。

なお、Action#excute()でExceptionを投げているからと言って、 その先のビジネスロジックのメソッドで何でもかんでも throws Exceptionするのはやめといた方がいいでしょう (せっかくの例外チェック機構の恩恵が半減するので)。

Validator

これまで、ActionForm#validate()に書いていた入力チェックを 入力チェック用のxmlに記述するとこで、 汎用的で変更しやすい入力チェックが可能になります。

最初、複雑で何をどうするんじゃ〜という印象だったんですが、 簡単なチェックは以外とすんなりできました。行うことは以下です。

  1. フォームはActionFormでなく、ValidatorFormを継承する。
  2. validator.xmlに設定を書く
  3. 必要であれば、JSPでJavascriptチェックをONにする

1.フォームはValidatorFormを継承して作る

import org.apache.struts.validator.ValidatorForm;

public class BattleForm extends ValidatorForm {

  private String name;
  private String word;

//validateメソッドの内容をxmlに移動
//  public ActionErrors validate(ActionMapping mapping,
//                               HttpServletRequest request) {
//  ...
//  }

...
}

2.validator.xml(とstruts-config.xmlとApplicationResource.properties)に設定を書く

struts-config.xml(1.0.2と同じ)

    <form-bean      name="battleForm"
                    type="wb.BattleForm"/>

validator.xml

  <formset>
    <form name="battleForm">
        <field
            property="name"
            depends="required,minlength">
            <arg0 key="battleForm.name"/>
    </field>
        <field
            property="word"
            depends="required">
            <arg0 key="battleForm.word"/>
        </field>
    </form>
  </formset>

ApplicationResources_ja.properties(1.0.2と同じ)

errors.required={0} を入力してください.

ここまででValidatorを使ったサーバーサイドの入力チェックが可能です。

3.JSPでJavascriptチェックをONにする

JSP

  <html:errors/>

  <%--<html:form action="/battle">--%>
  <html:javascript formName="battleForm"/>
  <html:form action="/battle" onsubmit="return validateBattleForm(this)">
  ...
  </html:form>

なお、validateBattleFormは自動的に作られるJavaScript関数名です。 クライアント側の入力チェックを自動生成でき、しかも、 サーバーサイドと入力チェックと一貫性を保てるのは、興味いですね。

Struts付属サンプルのstruts-validator.warを見ると、 必須チェックの他、 文字数チェック、正規表現を使った妥当性チェックなど、 様々なチェックの例があります。

DynaActionForm

Struts1.1より前は必ず、Formからアクションを呼ぶ場合、 必ずActionFormクラスを継承してクラスを作らなければなりませんでした。 この手間を省くために(あるいは別のアプローチとして?) DynaBeanFormが提供されました。

DynaActionFormの利用は簡単です。

struts-config.xml

  <!--普通のActionForm-->
  <!--
  <form-bean    name="battleForm"
                type="wb.BattleForm2"/>
  -->

  <!--DynaActionForm-->
  <form-bean    name="battleForm"
                type="org.apache.struts.action.DynaActionForm">
           <form-property
                name="name"
                type="java.lang.String"/>
           <form-property
                name="word"
                type="java.lang.String"/>
  </form-bean>

Actionクラス

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

//  BattleForm battleForm = (BattleForm)form;
//  String name = battleForm.getName();
//  String word = battleForm.getWord();

    DynaBean battleForm = (DynaBean)form;
    String name = (String)battleForm.get("name");
    String word = (String)battleForm.get("word");

  ...
  }

DynaBeanではプロパティーをキーにしてgetします。 validatorを組み合わせて、かなり便利だと思われます。 なんとなく、 request.getParameter("name")的なMapのアプローチに戻ってる ような気がするのは私だけでしょうか。。。

普通のActionFormとDynaActionFormの使い分けがちょっと問題ですね。 恐らく、単純なものや、Actionクラスから後ろのビジネスロジックに、 Formをそのまま渡さないような場合、DynaActionFormを使うといいかな? という気がします。あるいは設計方針によっては、全部、DynaActionFormというのもアリかもしれません。

PlugIn

Webアプリの初期化、終了処理を記述したPlugInクラスを 定義できるようになりました。 以下の手順で組み込みます。

PlugInインターフェイスを実装したクラスを作成

package wb;

import javax.servlet.ServletContext;
import javax.servlet.ServletException;

import org.apache.struts.action.ActionServlet;
import org.apache.struts.action.PlugIn;
import org.apache.struts.config.ModuleConfig;

public class WordBattlerPlugin implements PlugIn{

  public void init(ActionServlet servlet, ModuleConfig config)
    throws ServletException {
  
    ServletContext context = servlet.getServletContext();
    context.setAttribute("BATTLE_MANAGER",new BattleManager());  
  }

  public void destroy() {
  }
}

struts-config.xmlにplugin記述

  <plug-in className="wb.WordBattlerPlugin"/>

上の例だと、web.xmlでlistenerを定義しても同じことができますが、 Strutsに依存した処理(ActionServletから情報をとってどうこうとか?) の場合は、PlugInが使えそうです。

PlugInだけに付けたり取ったりが簡単なのか。。。 ValidatorやTilesもPluginが使われているようです。

NestedTag

NestedTagは、Strutsカスタムタグのネストをサポートします。nestedタグの内部では、name要素が省略できます。

NestedTag使用前

<logic:iterate id="word" name="BATTLE_MANAGER" property="words">
  <tr>
    <td><bean:write name="word" property="name"/>
    <td><bean:write name="word" property="word"/>
    <td><bean:write name="word" property="score"/>
  </tr>
</logic:iterate>

NestedTag使用後

<nested root="BATTLE_MANAGER">

  <nested:iterate property="words">
    <tr>
      <td><nested:write property="name"/>
      <td><nested:write property="word"/>
      <td><nested:write property="score"/>
    </tr>
  </nested:iterate>

</nested:root>

ほとんどのStrutsタグに対してnested:XXXが提供されています。これは、使えますね。なお、html:formの中ではフォーム名をネストルート名として使えますが、フォーム以外で使う場合はnested:rootでネストのルートを指定する必要があります。

適当な例かわかりませんが、nestedタグを使うと、以下のようなテーブル形式の入力画面も簡単に実現できます。

<html:form action="hoge">

    <html:submit/>

    <table>

    <nested:iterate 
         property="hogeBeanList" 
          indexId="index">

        <tr>
            <td>
                <bean:write name="index"/>
            </td>
            <td>
                <nested:text property="name"/>
            </td>
            <td>
                <nested:text property="age"/>
            </td>
        </tr>

    </nested:iterate>

    </table>

</html:form>

Formにmethod=getをつけると、 ....&hogeBeanList[3].name=no1&... というような形でリクエストが送られているのがわかります。 この形でリクエストパラメータが送られるので、HogeForm#getHogeForm().setName("no1")とフォームに値がセットされるわけです。

次ページ、前ページなどのページめくり機能がある場面でも、Form(Action)をセッションスコープ(デフォルトでセッションスコープ)にしておけば、iterateのoffsetとlengthを使って、ページをまたいで、Formに正しく値をセットすることができます。

wb-0.3

そしてバージョンアップされたワードバトラーがwb-0.3.zip (warじゃないです。容量がでかいのでlibの中のjarを消してます。struts-blank.warに上書きして実行できます)。 あんまり変わってないですが、 簡単にValidator、Plugin、NestedTagを組み込みました。


次のページへ


もどる