アクションからアクションにフォワードした際のrequestスコープのActionFormについて
場合によってはアクションからアクションにフォワードしたい場合がある.で,この際にrequestスコープのActionFormがどうなるかというのを,はっきりさせてみた.なお,ここではStruts-1.1で実験した.Struts-1.0.2とは動きが割と異なるようなので注意.
struts-config.xmlのaction-mappingsが以下だったとする.
<action-mappings>
<action path="/Action1"
type="matsu.struts.sample.Action1"
scope="request"
name="Type1Form">
<forward name="success" path="/Out.do" />
</action>
<action path="/Action2"
type="matsu.struts.sample.Action2"
scope="request"
name="Type2Form">
<forward name="success" path="/Out.do" />
</action>
<action path="/Action3"
type="matsu.struts.sample.Action3"
scope="request"
name="Type2Form">
<forward name="success" path="/Out.do" />
</action>
<action path="/Out"
type="matsu.struts.sample.OutAction"
scope="request"
name="Type1Form">
<forward name="success" path="/out.jsp" />
</action>
</action-mappings>
4つのアクションがあり,Action1,2,3からOutへフォワードする.いずれのアクションもrequestスコープのActionFormである.これをもとにWebアプリケーションを作成して,以下を確認する.
- requestスコープの範囲
- JSPへフォワードする際と同様,HTTPセッションと同じ範囲と考えてよいと思うが,念のため.
- ActionFormの生成
- 上と被るような気がするが,フォワード元とフォワード先が同じクラスだった場合に,ActionFormは同じインスタンスが使用されるのか?それともAction毎にnewされるのか?
- ActionFormの初期化
- HTTPリクエストの値をActionFormに詰め込む作業の確認.
- 異なるActionFormを持つActionへのフォワード
- フォワード前のActionのActionFormインスタンスはフォワード後どうなる?
- ActionFormの引渡し
- フォワード前のActionでフォワード後のActionをnewして設定などしてフォワード後のActionに渡すことができるのか?
確認のためにサンプルを作成してみた.これ.ビルド方法はだいたい以下.
- 展開
- ant init
- libディレクトリの下にstrutsのjarファイルを置く.動作確認時は以下を置いた.
commons-beanutils.jar commons-lang.jar struts-legacy.jar commons-collections.jar commons-logging.jar struts.jar commons-digester.jar commons-validator.jar commons-fileupload.jar jakarta-oro.jar
- src/WEB-INFの下にstrutsのタグリブを置く.動作確認時は以下を置いた.
struts-form.tld struts-template.tld struts-html.tld struts-tiles.tld struts-bean.tld struts-logic.tld struts.tld struts-nested.tld
- ant warを実行すると,warファイルができる.
結論から言うと,servletと同様,HTTPセッションの範囲と同じである.すなわちHTTPリクエストを受けてからレスポンスを返すまでであり,HttpServletRequestの生存期間と一致する...というのは,ActionFormのインスタンスはrequestスコープならHttpServletRequest,それ以外ならHttpSessionにsetAttributeされる.以下はStrutsのソースから.
ActionForm instance = null;
HttpSession session = null;
if ("request".equals(mapping.getScope())) {
instance = (ActionForm) request.getAttribute(attribute);
} else {
session = request.getSession();
instance = (ActionForm) session.getAttribute(attribute);
}
結論から言うと,フォワード元とフォワード先が同じクラスだった場合に,ActionFormは同じインスタンスが使用される.ただし,各Actionのexecuteを実行する前に,毎回ActionFormを初期化する.サンプルでこれを確認できる.まずAction1.
そしてOut.
さらにType1Form.
Action1,2,3へのPOSTメソッドを投げるためのページindex.jsp.
最後に最終的なフォワード先out.jsp.
index.jspのからmessageの値を"ACION1"と設定して投げてみた.以下catalina.out.
Type1Form created : 3
Type1Form 3 setMessage "ACTION1"
===== Action1 execute START =====
GET Type1Form message : "ACTION1"
Type1Form 3 setMessage "Action1 checked. ACTION1"
Type1Form 3 setMessage2 "Not over write"
from request : matsu.struts.sample.Type1Form@1c4a5ec
parameter form : matsu.struts.sample.Type1Form@1c4a5ec
===== Action1 execute END =====
Type1Form 3 setMessage "ACTION1"
===== OutAction execute START =====
GET type1Form message : "ACTION1"
Type1Form 3 setMessage "OutAction checked. ACTION1"
parameter form : matsu.struts.sample.Type1Form@1c4a5ec
Session Attribute Names : org.apache.struts.action.LOCALE
Request Attribute Names : org.apache.struts.action.MESSAGE
Values : org.apache.struts.util.PropertyMessageResources@178aae1
Request Attribute Names : org.apache.struts.action.mapping.instance
Values : ActionConfig[path=/Out,name=Type1Form,scope=request,type=matsu.struts.sample.OutAction
Request Attribute Names : Type1Form
Values : matsu.struts.sample.Type1Form@1c4a5ec
Request Attribute Names : org.apache.struts.action.MODULE
Values : org.apache.struts.config.impl.ModuleConfigImpl@11abd68
===== OutAction execute END =====
"matsu.struts.sample.Type1Form@1c4a5ec"というのはオブジェクトを一意に示す文字列であるが,Action1とOutのexecuteの引数formでこれが一致し,さらにAction1とOutにおいてrequestからキー"Type1Form"でgetAttributeしたものも一致する.さらにType1Formのコンストラスタで出力する"Type1Form created : n"という文字列も1HTTP要求で一回しか出て来ない.ソース(主にorg.apache.struts.util.RequestUtils.java)の雰囲気からするとActionServletのActionForm作成判断動作は以下である(DynaFormBeanっぽい処理はここでは無視).
- HttpServletRequest(requestスコープでなければHttpSession)にstruts-config.xmlのactionタグのname属性をキーにgetAttribute.
- あればそのクラスをチェックしstruts-config.xmlと矛盾しなければ,それを使用.
- なければnew.
ブラウザでは以下が出た.
messageの値を見ると,Action1におけるmessageに対する処理が入っていない.もう一度catalina.outを見ると,
Type1Form 3 setMessage "ACTION1" ===== Action1 execute START ===== ... ===== Action1 execute END ===== Type1Form 3 setMessage "ACTION1" ===== OutAction execute START =====となっており,各アクションのexecute呼び出しの前にType1FormのsetMessageメソッドが呼ばれていることが分かる.ここで,先のサンプルでのmessage2の値に注目してみる.これはAction1で設定し,out.jspでも出力された.HTTP要求のパラメータはmessageの値だけでmessage2の値はなかったので,Action1での設定が上書きされることなくOutまで届き,そしてout.jspでも出力された.
サンプルではAction2はActionFormにType2Formを持ち,Type1Formを持つアクションOutにフォワードする.Action2.javaは以下である.
Action2のActionFormであるType2Formは以下である.
index.jspを使用してAction2に要求してみた.以下はcatalina.out.
意外にも(?)Type2Formに格納された値がType1Formにも設定されている.catalina.outをもう一度みてみると,要求のmessageの値がAction2のexecute前にType2Formに設定され,
Type2Form created : 2
Type2Form 2 setMessage "ACTION2"
===== Action2 execute START =====
GET type2Form message : "ACTION2"
Type2Form 2 setMessage "Action2 checked. ACTION2"
from request : matsu.struts.sample.Type2Form@3eec1a
parameter form : matsu.struts.sample.Type2Form@3eec1a
===== Action2 execute END =====
Type1Form created : 4
Type1Form 4 setMessage "ACTION2"
===== OutAction execute START =====
GET type1Form message : "ACTION2"
Type1Form 4 setMessage "OutAction checked. ACTION2"
parameter form : matsu.struts.sample.Type1Form@fedfb6
Session Attribute Names : org.apache.struts.action.LOCALE
Request Attribute Names : org.apache.struts.action.MESSAGE
Values : org.apache.struts.util.PropertyMessageResources@178aae1
Request Attribute Names : org.apache.struts.action.mapping.instance
Values : ActionConfig[path=/Out,name=Type1Form,scope=request,type=matsu.struts.sample.OutAction
Request Attribute Names : Type2Form
Values : matsu.struts.sample.Type2Form@3eec1a
Request Attribute Names : Type1Form
Values : matsu.struts.sample.Type1Form@fedfb6
Request Attribute Names : org.apache.struts.action.MODULE
Values : org.apache.struts.config.impl.ModuleConfigImpl@11abd68
===== OutAction execute END =====
そしてブラウザ出力.
Type2Form created : 2 Type2Form 2 setMessage "ACTION2" ===== Action2 execute START =====さらにOutのexecute前にType1Formに設定されている.
Type1Form created : 4 Type1Form 4 setMessage "ACTION2" ===== OutAction execute START =====すなわち,HTTP要求のパラメータは全て,各Actionのexecute前にActionFormに設定される.一つ目のActionのActinFormにアクセサがあっても,二つ目のActionのActionFromになければ,HTTP要求のパラメータに含めることができない(やるとこける).
前節のcatalina.outの出力で,フォワード前のActionFormがフォワード後のActionのexecute時にも参照できることが確認できる.
index.jspからAcion3.doに要求してみた.以下はcatalina.out.
Action3はType2FormをActionFormとし,execute内で
Request Attribute Names : Type2Form
Values : matsu.struts.sample.Type2Form@3eec1a
Request Attribute Names : Type1Form
Values : matsu.struts.sample.Type1Form@fedfb6
ということで,フォワード前Actionのexecuteでフォワード後ActionのActionFormを作成し,突っ込むことはできそうである.すなわちキーをstruts-config.xmlのactionタグのname属性の値として,HttpSevletRequestにsetAttributeする.サンプルではAction3でこれを行っている.
Type2Form created : 2
Type2Form 2 setMessage "ACTION3"
===== Action3 execute START =====
Type1Form created : 2
Type1Form 2 setMessage "Action3 checked."
Type1Form 2 setMessage2 "Action3 checked."
from request : matsu.struts.sample.Type1Form@300429
===== Action3 execute END =====
Type1Form 2 setMessage "ACTION3"
===== OutAction execute START =====
GET type1Form message : "ACTION3"
Type1Form 2 setMessage "OutAction checked. ACTION3"
parameter form : matsu.struts.sample.Type1Form@300429
Session Attribute Names : org.apache.struts.action.LOCALE
Request Attribute Names : org.apache.struts.action.MESSAGE
Values : org.apache.struts.util.PropertyMessageResources@1e2481b
Request Attribute Names : org.apache.struts.action.mapping.instance
Values : ActionConfig[path=/Out,name=Type1Form,scope=request,type=matsu.struts.sample.OutAction
Request Attribute Names : Type2Form
Values : matsu.struts.sample.Type2Form@ebf3f0
Request Attribute Names : Type1Form
Values : matsu.struts.sample.Type1Form@300429
Request Attribute Names : org.apache.struts.action.MODULE
Values : org.apache.struts.config.impl.ModuleConfigImpl@dc4c81
===== OutAction execute END =====
そしてブラウザ出力.
Type1Form type1Form = new Type1Form();
type1Form.setMessage("Action3 checked.");
type1Form.setMessage2("Action3 checked.");
request.setAttribute("Type1Form", type1Form);
等として,フォワード後のOutのためのActionFormをnewしてrequestに格納している.これは例によって,Outのexecute前に値を設定されているが,
Type1Form 2 setMessage "ACTION3" ===== OutAction execute START =====それに注意すれば,ActionFormは無事フォワード後のActionに渡っている.
from request : matsu.struts.sample.Type1Form@300429
===== Action3 execute END =====
...
Request Attribute Names : Type1Form
Values : matsu.struts.sample.Type1Form@300429
以上をまとめると,ActionからActionへフォワードする際のrequestスコープのActinFormについて,以下が言える.
- requestスコープのActionFormはHTTP要求受信時に作成され,HTTPServletRequestにて保持される.
-
- キーはactionタグの属性nameで指定した値である.
- フォワード前後で異なるActionFormにした場合,フォワード後のActionFormでは両方のActionFormが参照できる.
- 明示的に消すという操作はないらしく(HttpServletRequestが消える時にActionFormも消える),フォワード前にフォワード後のActionFormをsetAttributeすることができる.
- requestスコープのActionFormは各アクションのexecute前に毎回HTTP要求のパラメータで初期化される.
-
- フォワード前のexecuteでHTTP要求のパラメータにあったActionFormのフィールドを上書きしても,フォワード後のexecuteまでに再びHTTP要求のパラメータで上書きされてしまう.
- HTTP要求のパラメータにないものは,フォワード前後のActionで保持される(execute前に上書きされない).
- 上はフォワード前後のActionFormのクラスの違いに関係なく行われるので注意が必要(例えばフォワード前のActionFormにはアクセサがあるが,フォワード後のActionFormにアクセサがないパラメータがHTTP要求にあるとこける).

