一つのformで複数の操作ボタンを設置し,一つのアクションで受けるには,「どのボタンが押されたのか?」に基づいて処理する必要がある.Strutsにはこのようなパターンを想定したActionのサブクラスがある.
問題
DispatchAction
LookupDispatchAction
サンプル
注意点
問題
以下のような画面があったとする.
<input type="submit" name="command" value="足す">
この場合,Strutsにより,ActinFormのcommandに"足す"が設定される.もっとも単純な解決方法は,この値を参照,チェックして処理を分岐させる方法である.
if (form.getCommand().equals() == "足す")
...
だがこれだとボタンに表示させるメッセージを変更する度に,JSPとActionを変更させる必要が生じる.
※ ここでボタンを押してもどうにもならない.念のため.
DispatchAction
Actionクラスがorg.apache.struts.actions.DispatchActionを拡張している場合,submitボタンのvalueの値と同名のメソッドが,executeと同じパラメータで呼び出される.例えば
というボタンが押されれば,該当Actionクラス(extends DispatchAction)でユーザが定義した(※)
public ActionForward add(ActionMapping mapping,
ActionForm form,
HttpServletRequest request,
HttpServletResponse response)
throws Exception
が呼ばれる.ただし,この仕組みを使用するにはstruts-config.xmlにて該当actionタグの属性に
parameter=”command”
などと記述しなければならない.属性parameterの値はsubmitボタンのname属性の値(すなわちJSP上ではhtml:submitカスタムタグの属性propertyの値)と揃えなければならない.
※ 呼び出すメソッドが見付からなければunspedifiedメソッドが呼ばれる.
LookupDispatchAction
DispatchActionを使用した場合でもなお問題がある.submitボタンの属性valueの値をDispatchActionのサブクラスのメソッド名と想定して,そのメソッドを呼ぼうとするので,当然ながら,メソッド名として使える文字列しか,submitボタンの属性valueの値を使用できない.すなわちボタンに「足す」とか「引く」などと記述することはできない.この問題に対応できるものとして,org.apache.struts.actions.DispatchActionがある.これはsubmitボタンのvalueの値と呼び出すメソッド名の間にメッセージリソースをはさむことにより,submitボタンの属性valueの値の制約を無くす.まず以下のようなメッセージリソースファイルがあるとする.
button.cmd.plus=足す
button.cmd.minus=引く
button.cmd.unknown=未定義
error.cmd.unknown=未定義操作です.
これに対してJSPのsubmitボタンには以下のように記述する.
<!-- 操作1 plus -->
<html:submit property="command">
<bean:message key="button.cmd.plus"/>
</html:submit>
<!-- 操作2 minus -->
<html:submit property="command">
<bean:message key="button.cmd.minus"/>
</html:submit>
<!-- 未定義操作 -->
<html:submit property="command">
<bean:message key="button.cmd.unknown"/>
</html:submit>
html:submitの属性propertyの値はsubmitボタンのinputタグの属性nameの値となる.そしてhtml:submitの内容はsubmitボタンのinputタグのvalue値となる.これにbean:messageタグを使用してメッセージリソースのキーを指定している.これでJSP出力にはキーに紐付けられた値が出力される. ActionクラスであるLookupDispatchActionのサブクラスでは
protected Map getKeyMethodMap()
をオーバーライドする.返すMapには,
このメッセージリソースキーに対してこの名前のメソッド
という対応情報を格納する.もちろんその名前のメソッドも定義する.以上をまとめるとLookupDispatchActionを使用した場合の処理はおおよそ以下になる.
(JSPでカスタムタグhtml:submitでメッセージリソースのキーを指定)
JSP出力でキーに対応するメッセージリソースのメッセージが出力される.
ユーザがsubmitボタンを押す(属性name,valueの値が送信される).
Struts該当actionの属性parameterの値と送信されたname属性の値が一致するかをチェック(※).
ActionクラスのgetKeyMethodMapでMapを取得
送信されたvalue属性の値に対応するメッセージリソース上のキーを使用して(複数ある場合は最初のものが使用される),Mapから実行すべきメソッドを特定,実行する.
実行すべきメソッドがMapから特定できなければunspecifiedが呼ばれる.
メッセージリソースのキーと値が1対1とは限らないというのは悩ましい問題かもしれない.また,Mapで特定したメソッドが見付からないという状況はバグである.洩れなくメソッドを実装すること.
※ 実際にはsubmitボタンの
属性nameの値=属性valueの値
というクエリがブラウザによって生成されているので,そのキー名のチェックになる.
サンプル
LookupDispatchActionを使用して最初の問題を解決するサンプルを示す.まずstruts-config.xml(抜粋).
<struts-config>
<form-beans>
<form-bean name="MyActionForm"
type="matsu.struts.sample.MyActionForm"/>
</form-beans>
<action-mappings>
<action path="/SomeActions"
type="matsu.struts.sample.SomeActions"
scope="request"
name="MyActionForm"
parameter="command">
<forward name="success" path="/some_actions.jsp" />
</action>
</action-mappings>
</struts-config>
要素actionに属性parameterを付けるのを忘れない.次にActionForm.
package matsu.struts.sample;
import org.apache.struts.action.*;
import org.apache.struts.actions.*;
import javax.servlet.*;
import javax.servlet.http.*;
import java.util.*;
import java.io.*;
public class SomeActions extends LookupDispatchAction {
/**
* 押されたsubmitボタンのvalueの値に
* 該当するメッセージリソースのキーと
* 呼び出すメソッドとのマッピング情報を返す.
*/
protected Map getKeyMethodMap() {
System.err.println("=== getKeyMethodMap called");
Map map = new HashMap();
map.put("button.cmd.plus", "plus");
map.put("button.cmd.minus", "minus");
return map;
}
/**
* 押されたsubmitボタンのvalueの値に
* 該当するメッセージリソースのキーが
* button.cmd.plusの値と等しければ呼ばれる.
*/
public ActionForward plus(ActionMapping mapping,
ActionForm form,
HttpServletRequest request,
HttpServletResponse response)
throws Exception {
System.err.println("=== plus called");
MyActionForm myActionForm = (MyActionForm) form;
try {
int result = (Integer.parseInt(myActionForm.getParam1())
+ Integer.parseInt(myActionForm.getParam2()));
myActionForm.setResult(Integer.toString(result));
} catch (NumberFormatException e) {
myActionForm.setResult("NaN");
}
return mapping.findForward("success");
}
/**
* 押されたsubmitボタンのvalueの値に
* 該当するメッセージリソースのキーが
* button.cmd.minusの値と等しければ呼ばれる.
*/
public ActionForward minus(ActionMapping mapping,
ActionForm form,
HttpServletRequest request,
HttpServletResponse response)
throws Exception {
System.err.println("=== minus called");
MyActionForm myActionForm = (MyActionForm) form;
try {
int result = (Integer.parseInt(myActionForm.getParam1())
- Integer.parseInt(myActionForm.getParam2()));
myActionForm.setResult(Integer.toString(result));
} catch (NumberFormatException e) {
myActionForm.setResult("NaN");
}
return mapping.findForward("success");
}
/**
* 押されたsubmitボタンのvalueの値に
* 該当するメッセージリソースのキーが
* getKeyMethodMapで返されるMapのどのキーとも
* 一致しない場合に呼ばれる.
*/
public ActionForward unspecified (ActionMapping mapping,
ActionForm form,
HttpServletRequest request,
HttpServletResponse response)
throws Exception {
System.err.println("=== unspecified called");
MyActionForm myActionForm = (MyActionForm) form;
myActionForm.setResult("NaN");
// エラーメッセージの設定
ActionErrors actionErrors = new ActionErrors ();
ActionError actionError = new ActionError ("error.cmd.unknown");
actionErrors.add(ActionErrors.GLOBAL_ERROR, actionError);
saveErrors (request, actionErrors);
return mapping.findForward("success");
}
}
メッセージリソースファイルは先に示した.実行結果画面も先に示した.以下は実行時のcatalina.out.
===setCommand called : ??
=== plus called
===setCommand called : ??
=== minus called
===setCommand called : ???
=== unspecified called
なんか”?”と出ているが,これは次説のfilterによるものである(と思う).
注意点
本頁のサンプルをまとめてみた.これ.サンプルは
ant war
にてwarファイルができるので,それをデプロイする.環境によってはこれを実行する前に,
src/WEB-INF/
にStrutsのtldファイルを,
lib/
にStrutsのjarファイルを置く必要がある.実行時の注意を以下に示す(※).
native2ascii
サンプルでは
ant war
にてメッセージリソースをnative2asciiしている.こうしないとJSP出力で文字化けする.
フィルタの使用
リソースファイルはEUC_JPにて記述した.そして
ant war
にてnative2asciiされる.したがってフィルタを使用するなどしてブラウザから送信されたsubmitボタンのvalue値をEUC_JPにしなければ,実行メソッドの「Lookup」時にどれともマッチせずにunspecifiedが呼ばれてしまう.そこでサンプルのweb.xmlには以下を書いておいた.
<!DOCTYPE web-app
PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
"http://java.sun.com/dtd/web-app_2_3.dtd">
<web-app>
<filter>
<filter-name>Encode to EUC_JP</filter-name>
<filter-class>filters.SetCharacterEncodingFilter</filter-class>
<init-param>
<param-name>encoding</param-name>
<param-value>EUC_JP</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>Encode to EUC_JP</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
...省略...
あとはwarファイル展開後のディレクトリに入って
mkdir -p WEB-INF/classes/filters/
cp $CATALINA_HOME/webapps/examples/WEB-INF/classes/filters/SetCharacterEncodingFilter.class\
WEB-INF/classes/filters/
などとする.とにかくTomcatのexamplesのWEB-INF/classes/filters/SetCharacterEncodingFilter.classをサンプルでも使用できるように準備する.
※ もっともこのサンプルやLookupDispActionに限った注意点ではない.