Strutsでは、サーブレットはStrutsが用意したクラスを使い、
プログラマーはそのクラスから呼ばれるActionクラスを実装する。
Actionクラスは1インスタンスが複数スレッドから呼ばれる為、MTセーフになるようにコーディングしなければならない。
<web-app> 〜 <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> </servlet> <servlet-mapping> <servlet-name>action</servlet-name> <url-pattern>*.do</url-pattern> </servlet-mapping> <welcome-file-list> 〜 </welcome-file-list> </web-app>
web.xmlのこの辺りの部分は、最初に一回書いておけば基本的に変更することは無い。
入力が何も無く、出力(データ)が何も無いアクションの例。
package jp.hishidama.sample.struts.sample0; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.apache.struts.action.Action; import org.apache.struts.action.ActionForm; import org.apache.struts.action.ActionForward; import org.apache.struts.action.ActionMapping;
public class Sample0Action extends Action { @Override public ActionForward execute(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception { return new ActionForward(mapping.getForward()); } }
アクションは、Actionクラスを継承する。
このアクションが実行されるときは、execute()が呼ばれる。
戻り値はフォワード先(遷移先)のURIを指定したActionForwardを返す。
上記の例では、mapping.getForward()は、struts-config.xmlのactionで指定したforward属性、すなわち「/index_struts.jsp」が返ってきているので、そこに遷移する。
もしexecute()でnullを返すと、どこにもフォワードされない。
<?xml version="1.0" encoding="Windows-31J"?> <!DOCTYPE struts-config PUBLIC "-//Apache Software Foundation//DTD Struts Configuration 1.3//EN" "http://struts.apache.org/dtds/struts-config_1_3.dtd"> <struts-config> <action-mappings> <action path="/index" type="jp.hishidama.sample.struts.sample0.Sample0Action" forward="/index_struts.jsp" /> </action-mappings> <message-resources parameter="MessageResources"/> </struts-config>
URIに「/index.do」が指定されたとき、このaction-mappingsに従って、Sample0Action#execute()が実行される。
上記の例ではaction要素にforward属性を指定していたが、普通はそれは使わず、forward要素を使う。
<action path="/index" type="jp.hishidama.sample.struts.sample0.Sample0Action"> <forward name="success" path="/index_struts.jsp" /> </action>
public class Sample0Action extends Action { @Override public ActionForward execute(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception { return mapping.findForward("success"); } }
むしろ、全く処理を行わないならActionクラスを指定する必要が無い。
<action path="/index" forward="/index_struts.jsp" />
JSPファイルに値を出力する例。
JSPファイルでは、Strutsのカスタムタグ(bean:writeタグ等)を使って値を出力する。
これはリクエストやセッションに放り込んだ値であれば何でも使えるが、Strutsでは基本的にデータ保持用のクラス(JavaBean)を作り、それを使用する。
package jp.hishidama.sample.struts.sample1; import java.io.Serializable; @SuppressWarnings("serial") public class Sample1InitBean implements Serializable { protected String text1; public void setText1(String s) { text1 = s; } public String getText1() { return text1; } }
package jp.hishidama.sample.struts.sample1;
〜
public class Sample1InitAction extends Action {
@Override
public ActionForward execute(ActionMapping mapping, ActionForm form,
HttpServletRequest request, HttpServletResponse response) throws Exception {
Sample1InitBean initBean = new Sample1InitBean();
initBean.setText1("Sample1InitActionでセットした文字列");
request.setAttribute("sample1InitBean", initBean);
return mapping.findForward("success");
}
}
<action-mappings> 〜 <action path="/sample1init" type="jp.hishidama.sample.struts.sample1.Sample1InitAction"> <forward name="success" path="/sample1.jsp" /> </action> </action-mappings>
URIに「/sample1init.do」が指定されたとき、このaction-mappingsに従って、Sample1InitAction#execute()が実行される。
<%@ page contentType="text/html; charset=Windows-31J"%> <%@taglib uri="/struts-bean" prefix="bean"%> <html> <head> <title>struts sample1</title> </head> <body> <h1>sample1</h1> <bean:write name="sample1InitBean" property="text1" /> </body> </html>
JSP(実際は生成されたHTML)でユーザーによって入力された値をActionで受け取る例。
まず、入力を受け取るためのクラスを用意する。このクラスはActionFormを継承しておく必要がある。
package jp.hishidama.sample.struts.sample2; import org.apache.struts.action.ActionForm; @SuppressWarnings("serial") public class Sample2Form extends ActionForm { protected String text2; public void setText2(String s) { text2 = s; } public String getText2() { return text2; } }
package jp.hishidama.sample.struts.sample2;
〜
public class Sample2Action extends Action {
@Override
public ActionForward execute(ActionMapping mapping, ActionForm form,
HttpServletRequest request, HttpServletResponse response) throws Exception {
Sample2Form bean = (Sample2Form) form;
System.out.println("Sample2Actionの入力:\t" + bean.getText2());
return mapping.findForward("success");
}
}
<struts-config> <form-beans> <form-bean name="sample2Form" type="jp.hishidama.sample.struts.sample2.Sample2Form" /> </form-beans> 〜 <action-mappings> 〜 <action path="/sample2init" forward="/struts/sample2.jsp" /> <action path="/sample2submit" type="jp.hishidama.sample.struts.sample2.Sample2Action" name="sample2Form"> <forward name="success" path="/index_struts.jsp" /> </action> </action-mappings> <message-resources parameter="MessageResources"/> </struts-config>
action要素では、そのアクションが実行された時に値を入れるためのクラスをname属性で指定する。
(クラス名を直接書くのではなく、form-beanに名前を付けて、その名前を指定する。
form-beanでは、普通にFormクラスを指定するだけでなく、パラメーターを指定したりすることも出来るので、別途定義できるように分かれているのだろう)
<%@ page contentType="text/html; charset=Windows-31J"%> <%@taglib uri="/struts-html" prefix="html"%> <html:html> <head> <title>struts sample2</title> </head> <body> <html:form action="/sample2submit"> <h1>sample2</h1> text2:<html:text property="text2" /><br> <html:submit value="サブミットボタン" /> </html:form> </body> </html:html>
html:formタグでaction属性を指定すると、HTMLが生成される際にformタグのaction要素にURIとして展開される。
このため、struts-config.xmlのaction要素が正しく記述されていなければならない。
また、formタグのname属性にstruts-config.xmlのaction要素のname属性の値(上記の例では「sample2Form」)が使われるので、action要素にname属性を指定しておく必要がある。
(指定が無いと、JSP生成時にエラーとなる)
<html> <head> <title>struts sample2</title> </head> <body> <form name="sample2Form" method="post" action="/contextroot/sample2submit.do"> <h1>sample2</h1> text2:<input type="text" name="text2" value=""><br> <input type="submit" value="サブミットボタン"> </form> </body> </html>
サブミットすると「/sample2submit.do
」がURIとして送られるので、struts-config.xmlの指定に従って
Sample2Formにリクエストの値がセットされ、Sample2Actionが呼び出される。
今までの例では、「JSPへ出力する為のBean」と「HTMLからの出力(ユーザーの入力)を入れるForm」を分けて書いてみた。
HTMLからの出力、すなわちActionへの入力には、Action#execute()の引数がActionFormとなっているので必ずFormでなければならないが、
JSPへの値の受け渡しは別にFormである必要は無い。
しかし、普通のStrutsのコーディングでは、どちらもFormを使うようだ。
html:textやbean:writeといったカスタムタグでは、name属性とproperty属性を持っている。
基本的にname属性にはBean(Form)名を指定し、property属性にはそのBean(Form)のプロパティー名を指定する。
bean:writeはJSPへの出力専用だからあまり気にすることは無いが、
html:textの場合、JSPへの出力とリクエストの生成(Formへの出力)の双方を兼ねる。
html:textのname属性はJSPへの出力の際にしか関係ないが、property属性の方はBeanのプロパティー名であると同時に、サブミット時のリクエストパラメーター名になる。
したがって、BeanとFormを分けたとすると、両方に同じ名前のプロパティー(セッター・ゲッターメソッド)が必要になる。
また、同じ画面を再表示するような場合(入力精査エラーになったとか、検索画面で再検索とか)は、Formに入っているものをBeanに詰め直すという作業が必要になる。
最初から同じFormに入っていればその必要は無くなる。
といった辺りの理由で、Formしか使われていないのかもしれない。
しかし概念的には二通りの使い方をしていると思うので、上記の例では敢えてFormでないBeanを使ってみた。
StrutsでActionを作る場合、そのActionには目的が二種類ある。
ひとつは、画面を生成するためのデータ初期設定。(例えばDBからのデータ取得)
もうひとつは、その画面でサブミットされた際の処理。(例えばDBへのデータ更新)
StrutsはMVCモデルなので、ViewであるJSPの中でデータ取得やDB更新を行うことは有り得ない。
上記のサンプルではそれを意識して、表示の為のクラスには「init」を付けてみた。
この考え方の場合、画面遷移は2つのActionを経由することになる。
設定類の記述 | 処理 | ||
---|---|---|---|
1 | HTML | <form action="/contextpath/submitA"> |
画面Aでサブミット |
2 | struts-config.xml | <action path="/submitA"〜> |
URIに合致するactionが探される。 |
3 | struts-config.xml | <action path="/submitA" name="formA"〜> <form-bean name="formA" type="FormA"/> |
FormAのインスタンスが作られ、 値がセットされる。 |
4 | struts-config.xml | <action path="/submitA" type="ActionA"〜> |
ActionAのexecute()が呼ばれる。 |
5 | ActionA#execute() | form.getXxx()を使って処理return mapping.findForward("success"); |
|
6 | struts-config.xml | <action name="/submitA"〜> |
フォワードする。(initBへ) |
7 | struts-config.xml | <action name="/initB" type="InitActionB"> |
ActionBのexecute()が呼ばれる。 |
8 | InitActionB#execute() | 初期値を取得してBeanへセットreturn mapping.findForward("success"); |
|
9 | struts-config.xml | <action name="/initB"〜> |
フォワードする。(b.jspへ) |
10 | b.jsp | 画面Bが表示される。 |
ActionA#execute()の中で画面Bの為のデータ取得まで、やろうと思えば出来る。
<action name="/submitA" type="ActionA" name="formA"> <forward name="success" path="b.jsp" /> </action>
しかし、b.jspへ遷移する画面が他にもある場合、それぞれで画面Bの表示用データを取得するようコーディングするなんて、馬鹿らしい。
画面B初期化用の共通ルーチンを作るという手も考えられるが、
サーブレットにはせっかくフォワードという機能があるんだから、素直にそれを使えばいいじゃん。
(どこの画面から来たのかを判別して多少処理を変えたい場合は、ちょっと考慮がいるけど。
一番楽そうなのは、リクエストに遷移元が判別できる情報(値)でも入れておくことかね)
ひとつの画面上に複数のサブミットボタンを配置し、処理は少しずつ変えたい場合、EventDispatchActionが使えるらしい。
(つまり、どのサブミットボタンが押されたかを判別してくれる)
参考: ほそさんのLookupDispatchActionとEventDispatchActionの比較
しかし気になるのは、処理ごとにフォームの精査内容を変えたい場合にどうするかということ。
例えば何かの登録画面で登録ボタンとキャンセルボタン(いずれもtype=submit)がある場合、登録ボタンが押された場合には入力項目の精査をする必要があるが、キャンセルボタンの場合は当然不要。
そういった場合はどうするんだろうなぁ?
Actionクラスのexecute()メソッドにはActionMapping mapping
という引数が渡されてくる。
これには、struts-config.xml(のaction-mappings)のaction要素の内容(自分自身のActionが呼ばれた際のもの)が入っている。
属性・要素 | 取得方法 | 内容 | ||
---|---|---|---|---|
<action | ||||
path="/sample1init" | mapping.getPath() |
アクションパス | ||
type="jp.hishidama.sample.struts.Sample1InitAction" | mapping.getType() |
Actionクラス名 | ||
forward="/sample1.jsp" | mapping.getForward() |
フォワード先(action要素のforward属性) | ||
name="sample1Form" | mapping.getName() |
フォームビーンの名前 | ||
scope="request" | mapping.getScope() |
フォームのスコープ(省略時はsession) | ||
> | ||||
<set-property key="prop1" value="zzz" /> | mapping.getProperty("prop1") |
1.3 | プロパティー(struts-config.xml上に書かれた固定値) | |
<forward name="success" path="/sample1.jsp" /> | mapping.findForward("success") |
フォワード先 | ||
</action> |
<set-property key="">
およびmapping.getProperty("")
は、Struts1.3以降で使用可能。[2009-11-30]
Struts1.2までは(1.3でも使えるが)<set-property property="">
と記述し、mappingクラスを独自に作る必要があった。
(参考: @ITのStrutsのアクションマッピングに独自パラメータを追加)
<action 〜 type="jp.hishidama.sample.struts.SamplePropertyAction" className="jp.hishidama.sample.struts.SampleActionMapping"> <set-property property="prop2" value="abc" /> 〜 </action>
action要素のclassName属性で、独自のActionMappingを指定する。
そのActionMappingにはセッターメソッドを用意しておく。<set-property property="prop2">
の場合、セッターメソッド名は「setProp2()」となる。
package jp.hishidama.sample.struts; import org.apache.struts.action.ActionMapping; @SuppressWarnings("serial") public class SampleActionMapping extends ActionMapping { private String prop2; public void setProp2(String s) { this.prop2 = s; } public String getProp2() { return prop2; } }
package jp.hishidama.sample.struts; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.apache.struts.action.Action; import org.apache.struts.action.ActionForm; import org.apache.struts.action.ActionForward; import org.apache.struts.action.ActionMapping; public class SamplePropertyAction extends Action { @Override public ActionForward execute(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception { SampleActionMapping map = (SampleActionMapping)mapping; System.out.println("prop2: " + map.getProp2()); return mapping.findForward("success"); } }
Action#execute()に渡ってくるmappingの実体はstruts-config.xmlのclassNameで指定したクラスなので、キャストしてやればよい。
なお、struts-config.xmlに<set-property property="prop3"〜>
と書き、該当するセッターメソッドがActionMappingに無かった場合、以下の例外が発生する。
java.lang.NoSuchMethodException: Bean has no property named prop3
ServletContextを取得するには、一旦サーブレットを取得し、そこから取り出す。[2009-10-21]
@Override public ActionForward execute(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception { ServletContext context = super.getServlet().getServletContext(); dumpActions(context); }