* Struts2 メモ [#rcce0881]
Struts2は[[Struts:http://struts.apache.org/]]というよりは[[WebWork2:http://www.opensymphony.com/webwork/]]の動作を多く継承している印象。

- [[Struts2:http://struts.apache.org/2.x/]]
- [[Velocity:http://www.jajakarta.org/velocity/]]
- [[Spring:http://www.springframework.org/]]
- [[Apache Tomcat:http://tomcat.apache.org/]]
- [[WebWork:http://www.opensymphony.com/webwork/]]

目次
#contents

** 設定 [#o5ead497]
とりあえず、インスコとかEclipseとかの設定は割愛(気が向いたらメモるけど)。
Struts2を利用するというとき、Struts経験者は、まずその過去の経験を一度忘れる。WebWork経験者は結構そのままの知識で移行可能。

Struts2の設定の基本は次のファイル。
+ struts.properties 
+ web.xml
+ struts.xml 
+ struts-default.xml 
+ struts-plugin.xml 

開発時に頻繁に書き換えるのは3になるでしょう。

*** struts.xml [#md35108c]
#code("HTML"){{
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE struts PUBLIC
    "-//Apache Software Foundation//DTD Struts Configuration 2.0//EN"
    "http://struts.apache.org/dtds/struts-2.0.dtd">

<struts>

    <constant name="struts.enable.DynamicMethodInvocation" value="false" />
    <constant name="struts.devMode" value="true" />

    <include file="category1.xml"/>
    <include file="category2.xml"/>
    <include file="category3.xml"/>

    <!-- Add packages here -->

</struts>
}}

アクションやインターセプタなどの定義はこのファイルに書くことになるが、規模が大きくなるに連れてこのファイルに書く設定の量が肥大化してくる。そこで、あるカテゴリごと、機能ごとなどにxmlを分割して作成し、それをここにincludeすることができる。

例えば上記ではcategory1.xml、category2.xml、category3.xmlの3つの外部ファイルをincludeしている。

category1.xml の例
#code("HTML"){{
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE struts PUBLIC
        "-//Apache Software Foundation//DTD Struts Configuration 2.0//EN"
        "http://struts.apache.org/dtds/struts-2.0.dtd">

<struts>

    <package name="category1" namespace="/category1" extends="struts-default">

        <action name="HelloWorld" class="category1.HelloWorld">
            <result>/category1/HelloWorld.vm</result>
        </action>

        <action name="login" class="category1.LoginAction">
            <result name="input">/category1/login.vm</result>
            <result name="success">/category1/top.vm</result>
        </action>
        
        <!-- Add actions here -->

    </package>
</struts>
}}

*** Velocity [#w4fb8901]
Struts2 の result type はデフォルトで [[dispatcher:http://struts.apache.org/2.x/docs/dispatcher-result.html]] になっている。

これを テンプレートエンジンの [[Velocity:http://www.jajakarta.org/velocity/]] に渡したい場合は、struts.xml に次のように設定しておく。

#code("HTML"){{
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE struts PUBLIC
        "-//Apache Software Foundation//DTD Struts Configuration 2.0//EN"
        "http://struts.apache.org/dtds/struts-2.0.dtd">

<struts>

   <!-- Velocity の設定ファイルの場所 -->
   <constant name="struts.velocity.configfile"
      value="/conf/velocity/velocity.properties" />

   <!-- Velocity のツール設定ファイルの場所 -->
   <constant name="struts.velocity.toolboxlocation"
      value="/WEB-INF/classes/conf/velocity/toolbox.xml" />

   <!-- デフォルトテンプレートの設定 -->
   <constant name="struts.ui.theme" value="xhtml" />
   <constant name="struts.ui.templateDir" value="template/archive" />
   <constant name="struts.ui.templateSuffix" value="vm" />

   <package name="app-default" extends="struts-default">

      <!-- Velocity Result をデフォルトに変更 -->
      <result-type name="velocity"
         class="org.apache.struts2.dispatcher.VelocityResult" default="true" />

   </package>

</struts>
}}

Velocityの拡張子は "vm"。

*** ワイルドカードマッピング [#z0ef821d]
アクションが呼び出されるときの挙動例。

次のように書いたら、

#code("HTML"){{
 <action name="login" method="login" class="category1.LoginAction">
     <result name="input">/category1/login.vm</result>
     <result name="success">/category1/top.vm</result>
 </action>
}}
+ LoginActionクラスのloginというメソッドが呼び出される。
+ メソッドが失敗するとinputという文字列が返り、その場合はLogin.vmで示されるページが表示される。
+ メソッドが成功するとsuccessという文字列が返り、Top.vmで示されるページが表示される。

Struts2ではexecute()というメソッドを必ず用意しなければならない、ということはない。好きな名前のメソッド名をつくって使える。

上記は、nameで指定されているアクション(エイリアス)名とメソッド名が同じなので、次のようにも記述できる。

#code("HTML"){{
 <action name="*" method="login" class="category1.LoginAction">
     <result name="input">/category1/login.vm</result>
     <result name="success">/category1/top.vm</result>
 </action>
}}
これでlogin.doなどで呼び出されたら、LoginActionクラスのloginメソッドが呼び出されることになる。

#code("HTML"){{
 <action name="*Menu" method="{1}" class="category1.MenuAction">
     <result name="input">/category1/init.vm</result>
     <result name="success">/category1/menu.vm</result>
 </action>
}}
この場合、呼び出されるメソッド名は自動的にMenuActionクラスが実装しているメソッドから検索される。例えば、initMenu.doなどと呼び出せば、methodには"init"が挿入され、MenuActionのinitメソッドが呼び出されることになる。

さらに上記はresultで指定されているinit.vmというファイル名がメソッド名と同じなので、

#code("HTML"){{
 <action name="*Menu" method="{1}" class="category1.MenuAction">
     <result name="input">/category1/{1}.vm</result>
     <result name="success">/category1/menu.vm</result>
 </action>
}}
と書けば、戻り値がinputのときにinit.vmというページが表示されることになる。

** アクションの拡張子 [#d519abc9]
Struts2では、標準で"action"という拡張子が設定されている(Strutsの"do"に相当する拡張子)。これが気に入らないという場合、これも自由に変更可能。

struts.properties ファイルの中に

#code{{
### Used by the DefaultActionMapper
### You may provide a comma separated list, e.g. struts.action.extension=action,jnlp,do
struts.action.extension=action
}}

という行があるので、ここを好きな拡張子に書き換えるか追記する。

例えば、従来のStrutsのようにdoじゃないとダメだという人は、

 struts.action.extension=do

と書いてしまえば良い。

ここは何でも良い。極端な話、"html"とか"aspx"などを指定することもできるので、静的ページや.NETで動いているページに偽装することもできるヨ?(そんな意味ないけど)。

- [[struts.properties:http://struts.apache.org/2.x/docs/strutsproperties.html]]

** フィールドドリブン [#a17c5afd]
Strutsのexecute()メソッドではActionMapping、ActionForm、HttpServletRequest、HttpServletResponseの4つの引数が渡されてきていた。しかし、Struts2のアクションクラスで呼び出されるメソッドには引数がない。Strutsから移行してきた人の多くは、まずこれに戸惑うはず?

どこからパラメータを取れば良いのか?の回答は、どこでも取れる、ということになる。
例えば、ログイン画面から入力されたユーザ名とパスワードをアクションが取得して認証する、という処理を書きたい場合、

#code("java"){{
import com.opensymphony.xwork2.ActionSupport;

public class LoginAction extends ActionSupport {

    private static final long serialVersionUID = 1L;

    /**
     * ログイン処理を実行する。
     */    
    public String login() throws Exception {
        
        // NOTICE authentication() はどこかに作成しておく。
        if (authentication(userName,password)) {
            return "success";
        }
        return "input";
    }

    /**
     * ユーザ名
     */
    private String userName;

    /**
     * ユーザ名を設定する。
     */
    public void setUserName(String userName){
        this.userName = userName;
    }

    /**
     * パスワード
     */
    private String password;

    /**
     * パスワードを設定する。
     */
    public void setPassword(String password){
        this.password = password;
    }
}
}}

と書けば良い。

ActionSupportを継承したクラスをアクションクラスとし、パラメータ名を設定するsetterメソッド(publicスコープで)を用意しておくだけ。

これで、アクションが起動されたときに、その名前のパラメータが渡されてきたら、自動的にそのsetterが呼び出され、メンバ変数に設定されることになる。なんと便利。

** モデルドリブン [#s3fe2005]
アクションの中にすべての変数を用意してそこにsetterを書いても良いが、パラメータは別オブジェクトに分離して持たせることができる。そのオブジェクトのことを「モデル」と呼ぶ。

モデルとなるクラスにはパラメータとなる変数を持たせておいて、そのsetter、getterを用意しておくだけ。これで、アクションクラスに持つパラメータはモデルのみとなり、処理に専念するクラスにすることができる。

モデルドリブンを使用するには、struts.xmlにModelDrivenInterceptorを設定し(多分デフォルトで設定されている?)、アクションクラスではModelDrivenをimplements、getModel()メソッドを実装しておく。

アクション側
#code("java"){{
import com.opensymphony.xwork2.ActionSupport;
import com.opensymphony.xwork2.ModelDriven;

import category1.LoginModel;

public class LoginAction extends ActionSupport implements ModelDriven {

    private static final long serialVersionUID = 1L;

    /**
     * ログイン処理を実行する。
     */    
    public String login() throws Exception {
        
        // NOTICE authentication() はどこかに作成しておく。
        if (authentication(loginModel.getUserName(),loginModel.getPassword())) {
            return "success";
        }
        return "input";
    }

    /**
     * パラメータを保持するモデル
     */
    private LoginModel loginModel = new LoginModel();

    /**
     * モデルを取得する。
     */
    public Object getModel() {
        return loginModel;
    }
}
}}

モデル側
#code("java"){{
public class LoginModel {

    private String userName;

    public void setUserName(String userName){
        this.userName = userName;
    }
    public String getUserName(){
        return userName;
    }

    private String password;

    public void setPassword(String password){
        this.password = password;
    }
    public String getPassword(){
        return password;
    }
}
}}

** Strutsのアクションの引数を取得したい [#p610e7b1]
それでもやっぱりStruts時代に取得できていたHttpServletRequestなどの情報もほしいよ?という場合もある。それらの情報は消えているわけじゃなく、ちゃんとどこかに保持されているので、それを取り出せるようなsetter、getterを実装すれば良い。

例えばHttpServletRequestを取得する場合、ServletRequestAwareインタフェースをimplementsしてsetServletRequest()を実装する。

#code("java"){{
import com.opensymphony.xwork2.ActionSupport;
import com.opensymphony.xwork2.ModelDriven;
import org.apache.struts2.interceptor.ServletRequestAware;

public class ExampleAction extends ActionSupport
    implements ModelDriven, ServletRequestAware {

    private HttpServletRequest request;

    public void setServletRequest(HttpServletRequest request) {
        this.request = request;
    }

}

}}

- [[org.apache.struts2.interceptor:http://struts.apache.org/2.0.6/struts2-core/apidocs/org/apache/struts2/interceptor/package-summary.html]]
- [[Struts2への移行:http://www.infoq.com/jp/articles/migrating-struts-2-part2]]

** リダイレクト [#v136bbe8]

result type は、デフォルトで dispatcher (jspやvm を表示)になっているが、
これを他のURLに飛ばすリダイレクト(redirect)にすることができる。

''通常はviewを指定''
#code("HTML"){{
 <action name="hogeAction" method="hoge" class="foo">
 	<!-- view を表示 -->
 	<result name="input">/WEB-INF/hoge.vm</result>
 </action>
}}

''他のURLにリダイレクトする場合''
#code("HTML"){{
 <action name="hogeAction" method="hoge" class="foo">
 	<!-- ページにリダイレクト -->
 	<result name="success" type="redirect-action">/foo/hogehoge.html</result>
 	<!-- アクションにリダイレクト-->
 	<result name="success" type="redirect">/foo/hogehogeAction.do</result>
 </action>
}}

アクションにリダイレクトする場合は、redirect-action と書ける。

#code("HTML"){{
 <action name="hogeAction" method="hoge" class="foo">
 	<!-- アクションにリダイレクト -->
 	<result name="success" type="redirect-action">hogehogeAction</result>
 </action>
}}

redirect-action の場合は拡張子は不要(アクションの name を指定)。

注意すべき点は、SSLを使用している場合、スキーマが変わる(https → http)ので、SSLが切れる。

これを保持する場合は chain を用いる。

#code("HTML"){{
 <action name="hogeAction" method="hoge" class="foo">
 	<!-- アクションにチェイン -->
 	<result name="success" type="chain">hogehogeAction</result>
 </action>
}}

※ ただ、chain すると ClassCastException が発生することがある。何か制約があるらしい。(調査中)

** バリデーション [#h80ffc12]

次の命名規則でxmlファイルを作って、そこにチェックを記述する。

 [アクションクラス名]-[アクション名]-validation.xml


TestAction というクラスの loginTest というアクションのバリデーションをする場合、

TestAction-loginTest-validation.xml

というファイルをつくって、TestAction.java と同じ階層に配置する。

例えば、ユーザ名(uid)とパスワード(pass)の必須入力をチェックしたい場合、

#code("HTML"){{
 <!DOCTYPE validators PUBLIC
 	"-//OpenSymphony Group//XWork Validator 1.0.2//EN"
 	"http://www.opensymphony.com/xwork/xwork-validator-1.0.2.dtd">
 
 <validators>
    <field name="uid">
        <field-validator type="required">
            <message>エラーメッセージとか</message>
        </field-validator>
    </field>
    <field name="pass">
        <field-validator type="required">
            <message>エラーメッセージとか</message>
        </field-validator>
    </field>
 </validators>
}}
などと書く。

標準で用意されているバリデーションの種類(type)は validation.xml に記述されている。

#code("HTML"){{
 <?xml version="1.0" encoding="UTF-8"?>
 <!DOCTYPE validators PUBLIC
        "-//OpenSymphony Group//XWork Validator Config 1.0//EN"
        "http://www.opensymphony.com/xwork/xwork-validator-config-1.0.dtd">
 <!-- START SNIPPET: validators-default -->
 <validators>
 
    <!-- 必須入力チェック(nullかどうか) -->
    <validator name="required" class="com.opensymphony.xwork2.validator.validators.RequiredFieldValidator"/>
    <!-- 文字列の必須入力チェック(空文字もチェック) -->
    <validator name="requiredstring" class="com.opensymphony.xwork2.validator.validators.RequiredStringValidator"/>
    <!-- 数値チェック(int) -->
    <validator name="int" class="com.opensymphony.xwork2.validator.validators.IntRangeFieldValidator"/>
    <!-- 数値チェック(double) -->
    <validator name="double" class="com.opensymphony.xwork2.validator.validators.DoubleRangeFieldValidator"/>
    <!-- 日付形式チェック -->
    <validator name="date" class="com.opensymphony.xwork2.validator.validators.DateRangeFieldValidator"/>
    <!-- 論理チェック(任意の評価式) -->
    <validator name="expression" class="com.opensymphony.xwork2.validator.validators.ExpressionValidator"/>
    <!-- 論理チェック(任意の評価式 -->
    <validator name="fieldexpression" class="com.opensymphony.xwork2.validator.validators.FieldExpressionValidator"/>
    <!-- E-Mail形式チェック -->
    <validator name="email" class="com.opensymphony.xwork2.validator.validators.EmailValidator"/>
    <!-- URL形式チェック -->
    <validator name="url" class="com.opensymphony.xwork2.validator.validators.URLValidator"/>
    <!--  -->
    <validator name="visitor" class="com.opensymphony.xwork2.validator.validators.VisitorFieldValidator"/>
    <!--  -->
    <validator name="conversion" class="com.opensymphony.xwork2.validator.validators.ConversionErrorFieldValidator"/>
    <!-- 文字列長チェック -->
    <validator name="stringlength" class="com.opensymphony.xwork2.validator.validators.StringLengthFieldValidator"/>
    <!-- 正規表現チェック -->
    <validator name="regex" class="com.opensymphony.xwork2.validator.validators.RegexFieldValidator"/>
 
 </validators>
}}
これらを拡張して独自バリデータを作るも良し。


ちなみに、バリデーション設定ファイルのアクション名は必須ではない。
アクション名を省略した場合は、指定したアクションクラスにかかる全てのアクションでバリデーションが効く。融通は利かないが、そのクラスにかかるアクションがそれほど多くない場合は、クラス名だけの指定にする方がスッキリするかも。

アクション名まで指定してバリデーションすると、
アクションごとに細かくバリデーションできるという利点があるが、
xmlファイルをアクションごとに作成することになり、
ファイル数が多くなるという欠点もある。

そのクラスの特定のメソッドのみバリデーションが不要であることがわかっていれば、
アノテーションで SkipValidation と書けば無効にできる。

#code("Java"){{
 @SkipValidation
 public String nocheckMethod() {
 
 	// バリデーション不要な処理。
 
 	return SUCCESS;
 
 }
}}

** Interceptor(インターセプタ) [#o9d3426a]
詳細は [[Interceptor]] で。

** Quartz(クォーツ) [#j536b16f]
詳細は [[Quartz]] で。

** Job から ServletContext を参照する [#ld84c11b]

Action 以外から ServletContext を参照するとか。

jobContext.xml
#code("HTML"){{
<bean id="servletContext" class="org.springframework.web.context.support.ServletContextFactoryBean"/>

<bean name="myDataUpdater" class="org.springframework.scheduling.quartz.JobDetailBean">
	<property name="jobClass" value="MyDataUpdater"/>
	<property name="jobDataAsMap">
		<map>
			<entry key="servletContext"><ref bean="servletContext"/></entry>
		</map>
	</property>
</bean>

}}

こうすればマップからとれる。

jobSample.java
#code("java"){{

public void executeInternal(JobExecutionContext cntxt) throws JobExecutionException {
		 ServletContext servletContext=(ServletContext)cntxt.getJobDetail().getJobDataMap().get("servletContext");

}

}}

または、setter で。
#code("java"){{

	private ServletContext servletContext;

	public void setServletContext(ServletContext servletContext) {
		this.servletContext = servletContext;
	}

}}


** リソースパス [#bb66bbd0]

#code("java"){{
	String path = context.getRealPath("");

	if (path == null) {
		// resources are in a .war (JBoss, WebLogic)
		java.net.URL url = context.getResource("");

		System.out.println("found url; " + url);
		path = url.getPath();
	}

	p.setProperty(Velocity.FILE_RESOURCE_LOADER_PATH, path);
}}

** 参考サイト [#kaee5308]
- [[「夜を告げるモノ。」:http://www.gigafield.org/projects/trac]]
-- [[Struts2入門:http://www.gigafield.org/projects/trac/wiki/Struts2]]
- [[Struts2サンプル集:http://nullpo.2log.net/home/struts2/]]
- [[WebWork2 + Velocity:http://ww36.tiki.ne.jp/~supergibby/webwork2/]]
- [[Struts2を使ってみる:http://www15.plala.or.jp/k_maeba/struts2/]]
- [[struts2-samples:http://code.google.com/p/struts2-samples/]]
-- [[struts2-samples:http://code.google.com/p/struts2-samples/w/list]]
** 公式 [#f323e2d0]
- [[Apache:http://www.apache.org/]]
-- [[Jakarta Project:http://jakarta.apache.org/]]
-- [[Struts:http://struts.apache.org/]]
- [[OpenSymphony:http://www.opensymphony.com/]]
- [[Java Developer Connection:http://sdc.sun.co.jp/java/]]
-- [[Java2 SDK ドキュメント:http://sdc.sun.co.jp/java/docs/j2se/1.4/ja/docs/ja/index.html]]

-----
[[MLEXP. Wiki]]

#googleads(1,1)


トップ   新規 一覧 単語検索 最終更新   ヘルプ   最終更新のRSS