画面数が増えてくると、struts-config.xmlに書く量が増えていって、すごく大きなファイルになってしまう。
これをモジュールという単位で分割して、複数に分けることが出来る。(Struts1.1からの機能らしい)
|
モジュール化されていない場合、URLにはコンテキストルートとアクション(アクションパス)を指定する。
http://ホスト/コンテキストルート/アクション http://localhost/contextroot/action.do
モジュール分割は、サブのディレクトリー(階層)を作るようなイメージ。
http://ホスト/コンテキストルート/モジュール/アクション http://localhost/contextroot/module/action.do
モジュール分割するには、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ファイルを置く場所は、モジュール名と同じ名前のディレクトリーの下になる。
モジュール用の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ファイル内のhtml:formやhtml:linkタグのaction属性は、同モジュール内のアクションパスとして扱われる。
(→モジュールを明示的に指定する方法)
<%@ 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-モジュール.xml内では、パスを「/」から始めて書いても自モジュール内を指すので、他モジュールへフォワードさせる為には特別な指定方法が必要となる。
その指定方法は、Struts1.1とStruts1.2以降で異なる。(→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ではcontextRelative属性を使う方法は非推奨になり、module属性を使うようになった。
Struts1.3ではcontextRelative属性は廃止になっていて、使えない。
<action path="/sample0submit" type="〜" name="〜">
<forward name="success" module="/module2" path="/init.do" />
</action>
なお、module属性に「/」のみを指定すると、デフォルトモジュールに遷移できる。
forward要素では、上記のように他モジュールを指定することが出来る。
しかしforward属性やinput属性に対しては他モジュールを指定する方法は用意されていないようだ。
(まぁinput属性は入力精査エラーがあった場合に自画面に戻る為のものだろうから、他モジュールへ遷移することは想定していないのかも)
そこで、モジュールを切り替える為のSwitchActionを経由してやる。
<action path="/switch" type="org.apache.struts.actions.SwitchAction" />
<action path="/sample0submit" forward="/switch.do?prefix=/module2&page=/init.do" />
SwitchActionは、リクエストパラメーターのprefixとpageを使用する。
prefixに切り替え先のモジュール名、pageに切り替え先モジュールでのアクションを指定する。
リクエストパラメーターなので、アクション(/switch.do)との間は「?」で区切り、他は「&」で区切る。
ただしここではstruts-config-モジュール.xmlという“XMLファイル”上に記述しているので、「&」は「&」にエスケープする必要がある。
参考: @ITのStrutsで別モジュールにアクセスする(SwitchAction)
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&page=/init.do">モジュール2へ(/switch.do)</html:link>
↓生成されるHTML
<a href="/contextroot/module1/switch.do?prefix=/module2&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アクションの定義が必要となる為、「別モジュールへの遷移の書き方」という意味では完全な解決にはなっていないかも。
<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>
<html:form action="/switchf.do?prefix=/module2&page=/init.do">
<html:submit value="モジュール2へ(サブミット)" />
</html:form>
↓生成されるHTML
<form name="dummyForm" method="post" action="/contextroot/module1/switchf.do?prefix=/module2&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で行う必要がある。
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.xmlを分割するには、モジュールを作るのではなく、ただ単にファイルを分割する方法もある。[2009-12-05]
web.xmlでstruts-config.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に書かれたかのように扱われる。
<global-exceptions>
<exception type="jp.hishidama.sample.m3.M31Exception" key="msg.key.exception1"
path="/error.jsp" handler="jp.hishidama.sample.m3.M3ExceptionHandler" />
</global-exceptions>
<global-exceptions>
<exception type="jp.hishidama.sample.m3.M32Exception" key="msg.key.exception1"
path="/error.jsp" handler="jp.hishidama.sample.m3.M3ExceptionHandler" />
</global-exceptions>
↓こういう風に定義したのと同じイメージ
<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間で同一の値の定義があった場合は、どちらかが生きると思う。(かぶらないように定義すべき)