Struts1 [DynaActionForm]

Struts1 [サンプル00]で書いたサンプル(bbs00.war)をベースに、DynaActionForm の利用とfilter を使った日本語対応を実装してみます。区別のため、前回のbbs00 フォルダを まるごとbbs01 としてコピーします。今回のサンプル(bbs01.war)
更新日 2016-02-13

DynaActionForm に置き換える。

アクションフォームはプロパティのみ(実際にはアクセスメソッドも記述しますが)を持つクラスでした。それに限定しているならば、 なんとなく自前でコンパイルしなくてもランタイムに準備できそうな気もします。だってJava って便利なんでしょ?そこで登場するのが DynaActionForm です。察しの通り、動的にプロパティが準備されるようです。実際に置き換えて見ましょう。

設定の編集

/WEB-INF/struts-config.xml を編集します。form-beans 要素の下にDyanActionForm クラスのインスタンス名(bbs_daf) を追加 しまう。
<!-- 以下を追加	-->
<form-bean name="bbs_daf" type = "org.apache.struts.action.DynaActionForm">
	<form-property name="str" type="java.lang.String" />
</form-bean>
次にこのアクションフォームを使うようaction エレメントのname 属性をbbs_daf に修正します。
<action path="/bbs"
                type="mypackage.bbsAction"
                name="bbs_daf"
                scope="request"
                validate="false">
	<forward name="success" path="/bbs.jsp"/>
	<forward name="error" path="/error.html"/>
</action>

アクションクラスの書き直し

アクションクラス内では、アクションフォームへのアクセスに型キャストを行っていました。 今回クラス名が変わった為、書き直しが必要です。
// Compile command. You must be current file's directry.
// javac -classpath "../lib/struts.jar;../../../../common/lib/servlet-api.jar;"
 -d "." bbsAction.java

package mypackage;

import org.apache.struts.action.Action;
import org.apache.struts.action.ActionForm;
import org.apache.struts.action.ActionMapping;
import org.apache.struts.action.ActionForward;
import org.apache.struts.action.DynaActionForm;		// 追加
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class bbsAction extends Action
{
	public ActionForward execute
	(
		ActionMapping mapping,
		ActionForm form,
		HttpServletRequest req,
		HttpServletResponse res
	)
	throws Exception
	{
		// アクションフォームにアクセスできます。
		DynaActionForm myForm = (DynaActionForm)form;	// 修正

		String str = (String)myForm.get( "str" );		// 修正
		if (str.length() == 0)
		{
			return mapping.findForward( "error" );
		}
		else
		{
			return mapping.findForward( "success" );
		}
	}
}
アクセサメソッドが変わっていますが、特に問題ないですね。コンパイルします。

bbs.jsp の修正

bean:write のname 属性をbbs_daf(DynaActionForm インスタンス名)に修正します。
ブラウザで表示して、bbs00 の時と変わらない事をご確認ください。

[Error] Class Cast Exception!

コレは参ってしまいます。なぜでしょう?ソースに間違いがなければTomcat の悪さでしょうか。実はTomcat は セッション情報をファイルに保存しており、再起動してもセッションが維持されている可能性があり、その 為に古いbbsActionForm インスタンスが作成(Serialize)されたものと思われます。セッション情報は $CATALINA_HOME/work/localhost/bbs00 の中のSESSIONS.serファイルに書き出されていますので、コレを消します。 このディレクトリはTomcat が利用するキャッシュなので、ディレクトリ毎削除したほう無難でしょう。というのも JSP のコンパイルキャッシュ等も残っていて、誤動作の原因になるからです。

日本語リクエストデータを扱えるようにする

テキストボックスに日本語を入れて送信すると文字化けが生じます。コレは、 アクションフォームへのデータ格納時に、それをどんな文字コードとして扱うかが 判別されないからです。格納直前によばれるresetメソッドで、文字コードを指定 ((setCharacterEncoding を実行) すれば解決します。
が、DynaActionForm ではreset メソッドを実装できません。だってランタイムですもの。
そこで調べてみると、struts サーブレットコンテナにフィルタという機能がありまして、そこで 日本語リクエストデータをコード変換する事が可能なようです!コレを利用しない手はありません!

SetCharacterEncodingFilter.class を用意する。

Tomcat のサンプルWeb アプリjsp-examples の中の/WEB-INF/classes/filters にあるので、bbs01/WEB-INF/classes にコピーします。手元になければダウンロードしましょう。SetCharacterEncodingFilter.javaSetCharacterEncodingFilter.class。/bbs01/WEB-INF/classes/filter ディレクトリを作り、コピーします。コンパイルしたければ以下で(一行でね)。
javac -classpath "../lib/struts.jar;../../../../common/lib/servlet-api.jar;" 
 -d "." SetCharacterEncodingFilter.java
クラスファイルは、filters ディレクトリに置いてください。

設定を変更する。

アクションサーブレットの動作設定になるので、/WEB-INF/web.xml を編集します。
まずDOCTYPE ですが、Servlet2.3 の仕様なので文中の2.2も2.3 に書き換えます。
<!DOCTYPE web-app
  PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
  "http://java.sun.com/j2ee/dtds/web-app_2_3.dtd">
次にルートであるweb-app エレメントの中にfilter 及びfilter-mapping エレメントを 追加します。追加場所はdisplay-name の次です。間違えると動きません。
<web-app>
	<display-name>Struts Blank Application</display-name>

	<!-- 追加 (フィルタの設定) -->
	<filter>
		<filter-name>EncodeFilter</filter-name>
		<filter-class>filters.SetCharacterEncodingFilter</filter-class>
		<init-param>
			<param-name>encoding</param-name>
			<param-value>Shift_JIS</param-value>
		</init-param>
	</filter>
	
	<!-- 追加 (全てのURL に対して適用する) -->
	<filter-mapping>
		<filter-name>EncodeFilter</filter-name>
		<url-pattern>/*</url-pattern>
	</filter-mapping>

</web-app>
filter エレメントではフィルタ名とフィルタのクラス名、パラメータとしてエンコード方式を 指示しています。
filter-mapping では、どのURL パターンにもフィルタを適用するよう指示しています。
コレで完成です。Tomcat を再起動して、日本語でも処理できる事を確認してください。

DynaActionForm のメリット・デメリット

DynaActionForm は疑いなく素晴らしいと思い込んでいましたが、やはり問題は存在しました。ひとつずつ 検証してみましょう。

reset メソッドが無い。

reset メソッドが無いと、フォームにチェックボックスなどが合った場合の処理に困ります。というのも、 チェックされてない場合は、データが送られてこないからです(明示的にチェック無しとやる必要が無かったから)。
この点は、アクションフォームをリクエストスコープにすれば解消できます。そもそもアクションフォームとは、そのような ものであるべきなんでしょうね。セッションスコープを使うようなデータは、アクションクラスから明示的にnew して セッションスコープに割り当てるべきなんでしょうね。
(DynaActionForm を継承する方法も見つけましたが、トリッキーな感じがするので敬遠しときます。)

validate メソッドが無い。

reset メソッド同様、実装できません。ただこれも、検証メカニズムを外部におくDynaValidatorForm という クラスが用意されています。これを使えば、validate 同様の検証が可能になります。
もっとも、既存の検証ルールに該当しない特殊なチェックを行う時は、派生クラスの作成とvalidate メソッドの実装が 必要になるでしょう。外部検証メカニズムと併用するならValidatorForm クラスから派生させればOKです。