S-JIS[2009-10-16/2009-12-05] 変更履歴

Strutsモジュール

画面数が増えてくると、struts-config.xmlに書く量が増えていって、すごく大きなファイルになってしまう。
これをモジュールという単位で分割して、複数に分けることが出来る。(Struts1.1からの機能らしい)


概要

モジュール化されていない場合、URLにはコンテキストルートとアクション(アクションパス)を指定する。

http://ホスト/コンテキストルート/アクション
http://localhost/contextroot/action.do

モジュール分割は、サブのディレクトリー(階層)を作るようなイメージ。

http://ホスト/コンテキストルート/モジュール/アクション
http://localhost/contextroot/module/action.do

web.xmlへの指定

モジュール分割するには、web.xml内のStrutsのサーブレット(ActionServlet)のパラメーターに、分割したstruts-configのファイル名を指定する。

	<servlet>
		<servlet-name>action</servlet-name>
		<servlet-class>org.apache.struts.action.ActionServlet</servlet-class>

		<!-- 通常のstruts-config -->
		<init-param>
			<param-name>config</param-name>
			<param-value>/WEB-INF/struts-config.xml</param-value>
		</init-param>

		<!-- モジュール1 -->
		<init-param>
			<param-name>config/module1</param-name>
			<param-value>/WEB-INF/struts-config-module1.xml</param-value>
		</init-param>
	</servlet>

パラメーター名を「config/モジュール名」にする。
そのモジュール用のstruts-config.xmlはどんなファイル名でもいいようだが、慣例的にはモジュール名を付加するものと思われる。

モジュール名を特に指定していない(元々のstruts-config.xml)ものは「デフォルトモジュール」と呼ぶっぽい。


JSPファイル置き場

モジュール分割した場合、JSPファイルを置く場所は、モジュール名と同じ名前のディレクトリーの下になる。


struts-config-モジュール.xml

モジュール用のstruts-configの中でのパスは、モジュールディレクトリー内の相対パスであるかのように扱う。

例えばWebContent/module1/sample1.jspをstruts-config-module1.xml内で指定するのは、以下のようになる。

		<action path="/sample1init" type="〜" input="/sample1.jsp">
			<forward name="success" path="/sample1.jsp" />
		</action>

アクションパス(/sample1init)もmodule1内の相対パスのように扱われる。

		<action path="/sample1submit" type="〜" name="module1Form">
			<forward name="success" path="/sample2init.do" />
			<forward name="failure" path="/sample1init.do" />
		</action>

		<action path="/sample2init" type="〜">
			<forward name="success" path="/sample2/index.jsp" />
		</action>

他モジュールを指定する方法


<global-exceptions>は、デフォルトのstruts-config.xmlとは別にいちいち記述する必要がある。
デフォルトのstruts-config.xmlの内容を引き継いでくれたりはしない)

※global-exceptionsは、モジュール毎に有効な為。[2009-12-05]
(モジュール内でstruts-config.xmlを分割した場合は、その中のどれかのファイルに書いておけばよい)


JSPファイル内のデフォルトパス

JSPファイル内のhtml:formやhtml:linkタグのaction属性は、同モジュール内のアクションパスとして扱われる。
(→モジュールを明示的に指定する方法

WebContent/module1/sample1.jsp:

<%@ page contentType="text/html; charset=Windows-31J"%>
<%@taglib uri="/struts-html" prefix="html"%>
<%@taglib uri="/struts-bean" prefix="bean"%>
<html>
<head>
<title>モジュール1 sample1</title>
</head>
<body>
<h1>モジュール1 sample1</h1>
<html:form action="/sample1submit.do">
	<p><html:link action="/sample1submit.do">リンク</html:link></p>
	<p><html:submit value="サブミット" /></p>
</html:form>
</body>
</html>

↓生成されるHTML

<html>
<head>
<title>モジュール1 sample1</title>
</head>
<body>
<h1>モジュール1 sample1</h1>
<form name="module1Form" method="post" action="/contextroot/module1/sample1submit.do">
	<p><a href="/contextroot/module1/sample1submit.do">リンク</a></p>
	<p><input type="submit" value="サブミット"></p>
</form>
</body>
</html>

他モジュールの指定方法(struts-config)

struts-config-モジュール.xml内では、パスを「/」から始めて書いても自モジュール内を指すので、他モジュールへフォワードさせる為には特別な指定方法が必要となる。
その指定方法は、Struts1.1とStruts1.2以降で異なる。(→struts-config.xmlのバージョン

Struts1.1のstruts-config-モジュール.xml

Struts1.1では、forward要素のcontextRelative属性をtrueにすることで(自モジュール内の相対パスでなく)コンテキストルートからの相対パスとして扱えるようになるので、他モジュールを指すことが出来るようになる。

	<action path="/sample0submit" type="〜" name="〜">
		<forward name="success" path="/module2/init.do" contextRelative="true" />
	</action>

Struts1.2以降のstruts-config-モジュール.xml

Struts1.2ではcontextRelative属性を使う方法は非推奨になり、module属性を使うようになった。
Struts1.3ではcontextRelative属性は廃止になっていて、使えない。

	<action path="/sample0submit" type="〜" name="〜">
		<forward name="success" module="/module2" path="/init.do" />
	</action>

なお、module属性に「/」のみを指定すると、デフォルトモジュールに遷移できる。

forward属性やinput属性での指定方法

forward要素では、上記のように他モジュールを指定することが出来る。
しかしforward属性やinput属性に対しては他モジュールを指定する方法は用意されていないようだ。
(まぁinput属性は入力精査エラーがあった場合に自画面に戻る為のものだろうから、他モジュールへ遷移することは想定していないのかも)

そこで、モジュールを切り替える為のSwitchActionを経由してやる。

	<action path="/switch" type="org.apache.struts.actions.SwitchAction" />

	<action path="/sample0submit" forward="/switch.do?prefix=/module2&amp;page=/init.do" />

SwitchActionは、リクエストパラメーターのprefixとpageを使用する。
prefixに切り替え先のモジュール名、pageに切り替え先モジュールでのアクションを指定する。
リクエストパラメーターなので、アクション(/switch.do)との間は「?」で区切り、他は「&」で区切る。
ただしここではstruts-config-モジュール.xmlという“XMLファイル”上に記述しているので、「&」は「&amp;」にエスケープする必要がある。

参考: @ITのStrutsで別モジュールにアクセスする(SwitchAction)


他モジュールの指定方法(JSP)

JSPファイル内で別モジュールへ遷移する書き方。

html:linkの場合、Struts1.2以降では、Struts1.2でhtml:linkタグに追加されたmodule属性を使用する。

<html:link module="/module2" action="/init.do">モジュール2へ(html:linkのmodule指定)</html:link>

↓生成されるHTML

<a href="/contextroot/module2/init.do">モジュール2へ(html:linkのmodule指定)</a>

なお、module属性に「/」のみを指定すると、デフォルトモジュールに遷移できる。


Struts1.1ではmodule属性は無いので、モジュールを指定するアクション(contextRelative属性つきのforward要素)へ遷移させるか、SwichActionを利用するのが楽。

<html:link action="/switch.do?prefix=/module2&amp;page=/init.do">モジュール2へ(/switch.do)</html:link>

↓生成されるHTML

<a href="/contextroot/module1/switch.do?prefix=/module2&amp;page=/init.do">モジュール2へ(/switch.do)</a>

html:formタグにはどのバージョンでもmodule属性は無いが、たぶんHTTPの思想からすれば当たり前だろう。
すなわち、フォーム(サブミット)は主にPOSTメソッド、すなわち入力したデータの送信に使用するので、その画面の属するモジュールで処理するのが当然。
(逆にリンクはGETメソッド、すなわち様々な場所を指定してデータを取得する目的なので、自モジュールであろうが他モジュールであろうが、他コンテキスト(他アプリ)だろうが他サーバー だろうが、色々指定できて当然 )

なので、html:formタグで別モジュールに遷移するような動作は設計的に言っておかしいと思うが、
まぁそれでもhtml:formタグで別モジュールに遷移したい場合は、Struts1.1のhtml:linkと同様にSwitchAction等を利用する。
ただし、html:formのaction属性を使用する際は、それに伴うフォームビーンが存在していないといけない。(だからswitchアクションを使い回すのは無理かも?)
とすると、結局自モジュール内にswitchアクションの定義が必要となる為、「別モジュールへの遷移の書き方」という意味では完全な解決にはなっていないかも。

struts-config-モジュール.xml:

	<form-beans>
		<form-bean name="dummyForm" type="org.apache.struts.action.DynaActionForm" />
	</form-beans>

	<action-mappings>
		<action path="/switchf" type="org.apache.struts.actions.SwitchAction" name="dummyForm" />
	</action-mappings>

JSP:

<html:form action="/switchf.do?prefix=/module2&amp;page=/init.do">
	<html:submit value="モジュール2へ(サブミット)" />
</html:form>

↓生成されるHTML

<form name="dummyForm" method="post" action="/contextroot/module1/switchf.do?prefix=/module2&amp;page=/init.do">
	<input type="submit" value="モジュール2へ(サブミット)">
</form>

他アプリへリダイレクトする方法

Strutsの“フォワードによる遷移”というのはサーブレットのフォワード機能のことなので、他アプリへ遷移(フォワード)することは出来ない。
ここで言う「他アプリ」とは、「異なるWebアプリケーション」という意味のつもり。
つまり別のコンテキストルートのもの。WAR(Webアーカイブ)の単位で別のもの。(同一のAPサーバーで動いていたとしても)

フォワードは、概念的には「遷移」と言っているけれども、実際はJavaのメソッド呼び出し。
つまり、フォワードすると、フォワード先のサーブレットのメソッドが呼ばれて(StrutsならそこからAction#execute()が呼ばれて)処理されている。(JSPなら、JSPから変換されたサーブレットでHTMLを出力している)
したがって、別モジュールはただ単に“同一Webアプリ内でディレクトリーを分割して管理している”だけのことだから問題なく メソッド呼び出しで呼び出せる(フォワードできる)が、
コンテキストルートが異なるものは別アプリ、極端に言えば別サーバー(別JavaVM)で動いているもの(同一VMだとしても、少なくともクラスローダーは別になるはず)なので、メソッド呼び出しで呼び出せるはずが無い
(ついでに言えば、セッションオブジェクトもWebアプリ内で保持するものなので、別アプリなら別管理)


JSPファイル上でリンクを書く場合、html:linkにはhref属性もあるので、別アプリでも別サーバーでも指定できる。
html:formで別アプリを指定するのは…html:formで別モジュールを指定しようとするのと同じく、設計が変だと思う。


Strutsのアクション(処理)の結果、別アプリへ遷移したい事はあると思う。
上述の様に、フォワードではそもそも別アプリへ遷移できないので、リダイレクトすることになると思う。

struts-config.xmlでの リダイレクトの書き方としては、forward要素にredirect属性があるので、これにtrueを指定する。

		<forward name="success" path="/sample.jsp" redirect="true" />

しかしこれは、pathで指定されたアクションを(フォワードでなく)リダイレクトで呼ぶだけなので、遷移先自体は自アプリ内となる。
(パス指定が「/」から始まっていても、同モジュール、あるいは同アプリ内の遷移の指定にしかならない)

そこで、相対パスでコンテキストルートより上を指すようにしてやると、リダイレクトできるようだ。
(が、これがStrutsとして保証されたやり方なのかは不明…)

		<forward name="success" path="../othercontext/index.do" redirect="true" />

→リダイレクト先のURI(ブラウザーに表示されるアドレス)は「http://ホスト/othercontext/index.do」になる。

なので、一番確実なのは、Actionクラス内でリダイレクトしてしまうことだと思う。

/**
 * action要素のparameter属性に指定したURLにリダイレクトする。
 */
public class RedirectAction extends Action {

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

		String path = mapping.getParameter();
//		System.out.println("sendRedirect: " + path);

		response.sendRedirect(path); //自前でリダイレクトして
		return null;                   //nullを返しておく
	}
}
		<action path="/redirect" type="jp.hishidama.struts.RedirectAction"
		 parameter="/othercontext/index.do" />

→リダイレクト先のURI(ブラウザーに表示されるアドレス)は「http://ホスト/othercontext/index.do」になる。
 

/**
 * successのforward要素で指定されたパスにリクエストパラメーターを付けてリダイレクトする例
 */
public class RedirectSampleAction extends Action {

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

		ActionForward af = mapping.findForward("success");
		ActionRedirect ar = new ActionRedirect(af);
		ar.addParameter("name1", "value1");	//リクエストパラメーター(クエリー文字列)を付けてみる

//		System.out.println("sendRedirect: " + ar.getPath());
//		→「/othercontext/index.do?name1=value1」になる

		response.sendRedirect(ar.getPath()); //自前でリダイレクトして
		return null;                           //nullを返しておく
	}
}
		<action path="/redirect" type="jp.hishidama.struts.RedirectSampleAction">
			<forward name="success" path="/othercontext/index.do" />
		</action>

→リダイレクト先のURI(ブラウザーに表示されるアドレス)は「http://ホスト/othercontext/index.do?name1=value1」になる。

ActionRedirect#addParameter()で全角文字や記号(スペースとか)を指定した場合でも、ちゃんとURIで表記できるようにエンコードしてくれる。
ただし、UTF-8になってしまう。(ResponseUtils.encodeURL()が呼ばれ、さらにURLEncoder#encode()が呼ばれる)
ので、展開側もUTF-8で行う必要がある。


ModuleUtils

Struts内部では、モジュール関連はModuleUtilsクラスで操作しているようだ。[2009-10-21]

これを使って、全てのモジュールの情報を取得することが出来る。

全モジュールの全アクションを表示する例

import org.apache.struts.config.ActionConfig;
import org.apache.struts.config.ModuleConfig;
import org.apache.struts.util.ModuleUtils;
	public void dumpActions(ServletContext context) {

		ModuleUtils moduleUtils = ModuleUtils.getInstance();

		{ //デフォルトモジュールのstruts-configを取得
			ModuleConfig mconfig = moduleUtils.getModuleConfig("/", context);
			dumpActions(mconfig);
		}

		// デフォルト以外の全モジュールのstruts-configを取得
		String[] pres = moduleUtils.getModulePrefixes(context);
		if (pres != null) {
			for (String prefix : pres) {
				ModuleConfig mconfig = moduleUtils.getModuleConfig(prefix, context);
				dumpActions(mconfig);
			}
		}
	}

	protected void dumpActions(ModuleConfig mconfig) {
		if (mconfig == null) {
			return;
		}
		String prefix = mconfig.getPrefix();
		System.out.printf("●dumpActions[%s]%n", prefix);

		ActionConfig[] as = mconfig.findActionConfigs(); //全actionを取得
		for (ActionConfig aconfig : as) {
			String path = aconfig.getPath();
			System.out.println(prefix + path);
		}
	}

↓出力例

●dumpActions[]
/sample1init
/sample2init
/sample2submit
●dumpActions[/module2]
/module2/init
/module2/sample0init
/module2/move1
●dumpActions[/module1]
/module1/sample0init
/module1/sample0submit
/module1/move2
/module1/switch
/module1/switchf

struts-config-*.xml内の「<action path="/sample1init">」といったアクションが全て取得できる。

ModuleUtils#getModulePrefixes()で全モジュールのprefix(モジュール名)一覧が取得できるが、デフォルトモジュール「/」だけは取得できないようなので、別途取得する必要がある。

ModuleUtils#getModuleConfig()でModuleConfigを取得するためには、ServletContextが必要になる。

フィルターの場合、まだモジュールの初期化が行われていない可能性がある。(→フィルターとStrutsの連携
未初期化の場合、getModuleConfig()getModulePrefixes()はnullを返す。


struts-configファイル分割

struts-config.xmlを分割するには、モジュールを作るのではなく、ただ単にファイルを分割する方法もある。[2009-12-05]

web.xmlでstruts-config.xmlの場所を指定する際に、分割したファイル名を列挙するだけ。

web.xml:

	<servlet>
		<servlet-name>action</servlet-name>
		<servlet-class>org.apache.struts.action.ActionServlet</servlet-class>
		<init-param>
			<param-name>config</param-name>
			<param-value>/WEB-INF/struts-config.xml</param-value>
		</init-param>
		<init-param>
			<param-name>config/module3</param-name>
			<param-value>/WEB-INF/struts-config-m3-1.xml,/WEB-INF/m3/struts-config-m3-2.xml</param-value>
		</init-param>
	</servlet>

モジュール毎に、<param-value>にカンマ区切りでstruts-configファイル名を指定する。
struts-configファイルの場所は自由。WEB-INF直下でも、その下にサブディレクトリーを作って置いてもよい。

各struts-configファイルには、通常のstruts-config.xmlと同様に要素を書いていく。
実行時には、全てが統合されて1つのモジュールとして機能する。

例えば以下のようにglobal-exceptionsを別々に書いても、ひとつのstruts-config.xmlに書かれたかのように扱われる。

/WEB-INF/struts-config-m3-1.xml:

	<global-exceptions>
		<exception type="jp.hishidama.sample.m3.M31Exception" key="msg.key.exception1"
		 path="/error.jsp" handler="jp.hishidama.sample.m3.M3ExceptionHandler" />
	</global-exceptions>

/WEB-INF/m3/struts-config-m3-2.xml:

	<global-exceptions>
		<exception type="jp.hishidama.sample.m3.M32Exception" key="msg.key.exception1"
		 path="/error.jsp" handler="jp.hishidama.sample.m3.M3ExceptionHandler" />
	</global-exceptions>

↓こういう風に定義したのと同じイメージ

/WEB-INF/struts-config-m3.xml:

	<global-exceptions>
		<exception type="jp.hishidama.sample.m3.M31Exception" key="msg.key.exception1"
		 path="/error.jsp" handler="jp.hishidama.sample.m3.M3ExceptionHandler" />
		<exception type="jp.hishidama.sample.m3.M32Exception" key="msg.key.exception1"
		 path="/error.jsp" handler="jp.hishidama.sample.m3.M3ExceptionHandler" />
	</global-exceptions>

※複数のstruts-config間で同一の値の定義があった場合は、どちらかが生きると思う。(かぶらないように定義すべき)


Struts目次へ戻る
メールの送信先:ひしだま