目次
Super Agile Struts (SAStruts)
SAStrutsはSeasarプロジェクトのWebフレームワーク。ActionやFormの扱いがStrutsとは大きく違うため、Struts上に作った別のフレームワークと考えると良いかもしれない。
バリデーションや画面遷移をアノテーションで指定出来るため、Struts特有の設定ファイルが要らずコードに集中でき書きやすい。
使ってみる
EclipseとDoltengプラグインを使ってSAStrutsのプロジェクトを作る。パッケージ名だけ最初に決める必要がある。
基本的な使い方
ActionはタダのJava Object (POJO) でStruts Actionクラスを継承する必要は無い。
package jp.paulownia.test.action; public class HogeAction { @Required public String id; // パラメータでnameが送られてくるとココに入っている。 @Execute(validation=false) // このアクションメソッドではバリデーションしない public String list() { return "list.jsp"; } @Execute(input="list") // このアクションメソッドでは検査する。エラー時はlistメソッドを実行する public String edit() { return "edit.jsp"; } }
というアクションがある場合、 http://example.com:8080/context_path/hoge/edit へアクセスすると
jp.paulownia.test.action.HogeAction#edit
メソッドが呼ばれる。そしてメソッドの戻り値で指定した
/WEB-INF/view/hoge/edit.jsp
のJSPを実行する。(/hoge/
はアクション名から自動補完される。)
- URLとマッピングするメソッドには@Executeアノテーションを付ける
- ダウンロード等遷移先が無い場合@Executeなメソッドの戻り値をnullにする
- リダイレクトは /hoge/edit?redirect=trueとか戻すとリダイレクトしてeditメソッドの呼び出し
- 他のアクションにforwardするときは、/ で始める?
Actionの下にパッケージを作ってそこにActionを置くと、URLのパスが深くなる
http://example.com:8080/context_path/fuga/hige/edit
package jp.paulownia.test.action.fuga; public class HigeAction { @Execute(validation=false) public String edit() { return "edit.jsp"; } }
遷移先を指定する戻り値とinputの値はjsp名、またはアクションメソッド名。forward先をしてい
ActionForm
アクションフォームは、@ActionFormアノテーションで指定
package jp.paulownia.test.action; public class HogeAction { @ActionForm public HogeForm hogeForm; }
アクションフォームクラスは ルートパッケージ以下のformパッケージに作成。命名規則はXxxxForm
。
package jp.paulownia.test.form; public class HogeForm { @Required public String name; }
注意:以下は古いやり方
入力パラメータはActionの中のpublicフィールドに入るので特にアクションフォームを作成する必要はない
public class HogeAction { public String name; // パラメータでnameが送られてくるとココに入っている。 }
セッションスコープのActionFormが必要ならDTOを作成する
package jp.paulownia.test.action; public class HogeAction { @ActionForm public HogeDto hogeDto; }
package jp.paulownia.test.action; @Component(instance = InstanceType.SESSION) public class HogeDto implements Serializable { public String name; }
フォームバリデーション
public class HogeAction { @Required public String id; // パラメータでnameが送られてくるとココに入っている。 @Execute(validation=false) // このアクションメソッドではバリデーションしない public String list() { return "list.jsp"; } @Execute(input="list.jsp") // このアクションメソッドでは検査する。エラー時はlist.jspに戻る public String edit() { return "edit.jsp"; } }
セッションスコープにデータ格納
適当なDTOを作ってInstanceTypeをSESSIONにして、Actionクラスにフィールドを作っておくとコンテナが自動的にインスタンスをInjectしてくれます。
@Component(instance = InstanceType.SESSION) public class FugaDto implements Serializable { private String value; ... }
アクションクラスで
@Component(instance = InstanceType.SESSION) public class FugaAction implements Serializable { @Resource private FugaDto fugaDto; ... }
FugaDtoをpublicフィールドにすると、fuga/index?fugaDto=1 みたいなURLでアクセスするとDTOを上書きできてしまう(?)ので止めた方が良いという噂。
アプリケーションスコープにデータ格納
インスタンスのライフサイクルをAPPLICATIONにするだけ、あとはSessionと同じように使う
@Component(instance = InstanceType.APPLICATION) public class PiyoDto implements Serializable { private String value; ... }
アクションのユニットテスト
S2TestCaseではActionをテストクラスにインジェクションできないので、テストコード中でnewする。アクションの依存オブジェクト(サービスやJdbcManager)は、まずテストクラスにインジェクションして、アクションに手動でセットする。
public class HelloActionTest extends S2TestCase { public JdbcManager db; public FugaService fugaService; protected void setUp() throws Exception { include("app.dicon"); super.setUp(); } public void testIndex () { // テスト対象のアクションをnew HelloAction test = new HelloAction(); // 依存クラスは自分でセット test.db = db; test.fugaService = fugaService; assertEquals("index.vm", test.index()); assertEquals("Hello SAStruts Unit Test!", test.message); assertEquals(new SimpleDateFormat("yyyy-MM-dd").format(new Date()), test.today); }
俺インターセプタと俺アノテーションを作る
サンプルとして、アクションメソッドを起動できるHTTPメソッドを制限するインターセプタを作成する。
- アクションメソッドにアノテーションで許可するHTTPメソッドを指定する。
- 許可されないHTTPメソッドの場合、アクションメソッドを実行せずにHTTP Status 405を返す。
という仕様。
インターセプタクラス
package your.rootpackage.interceptor; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.aopalliance.intercept.MethodInvocation; import org.seasar.framework.aop.interceptors.AbstractInterceptor; import org.seasar.struts.util.RequestUtil; import org.seasar.struts.util.ResponseUtil; /** * SAStrutsのアクションメソッドの実行を、 * 特定のHTTPメソッドによるアクセスの場合のみに許可するインターセプタです。 * 許可されないHTTPメソッドでアクセスされた場合、HTTPステータス405を返します。 * */ public class HttpMethodInterceptor extends AbstractInterceptor { /** * 許可するHTTPメソッド名、カンマ区切りで複数指定可。 */ public String value; @Override public Object invoke(MethodInvocation invocation) throws Throwable { if (this.value != null) { HttpServletRequest request = RequestUtil.getRequest(); String requestMethod = request.getMethod(); String[] methods = this.value.split(","); for (String method: methods) { if (method.trim().equalsIgnoreCase(requestMethod)) { return invocation.proceed(); } } } HttpServletResponse response = ResponseUtil.getResponse(); response.setStatus(405); return null; } }
アノテーション
package your.rootpackage.interceptor; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import org.seasar.framework.aop.annotation.Interceptor; /** * {@link HttpMethodInterceptor}をSAStrutsのアクションメソッドに適用するアノテーションです。 * 引数で許可するHTTPメソッドを指定します。カンマ区切りで複数のHTTPメソッドを指定できます。 * */ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) @Interceptor("httpMethodInterceptor") public @interface HttpMethod { String value(); }
使い方
public class AuthAction { @Execute(input = "index") @HttpMethod("post,get") public String login() { // 何か処理 } }
JNDIのデータソース設定を使う
SAStruts 1.0.4 sp8
jdbc.diconの設定を以下のようにする
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE components PUBLIC "-//SEASAR2.1//DTD S2Container//EN" "http://www.seasar.org/dtd/components21.dtd"> <components namespace="jdbc"> <include path="jta.dicon"/> <component name="xaDataSource" class="org.seasar.extension.dbcp.impl.DataSourceXADataSource"> <property name="dataSourceName">"java:comp/env/jdbc/hogehoge"</property> </component> <component name="connectionPool" class="org.seasar.extension.dbcp.impl.ConnectionPoolImpl"> <property name="timeout">600</property> <property name="maxPoolSize">10</property> <property name="allowLocalTx">true</property> <destroyMethod name="close"/> </component> <component name="DataSource" class="org.seasar.extension.dbcp.impl.DataSourceImpl" /> </components>
jdbc/hogehogeという名前でJNDIにデータソースを登録しておく、context.xmlに書くならば
<?xml version="1.0" encoding="UTF-8"?> <Context path="/contextName" reloadable="false" docBase="contextName"> <Resource auth="Container" type="javax.sql.DataSource" name="jdbc/hogehoge" driverClassName="com.mysql.jdbc.Driver" url="jdbc:mysql://localhost:3306/hogehoge?characterEncoding=UTF-8&amp;characterSetResults=UTF-8" username="hoge" password="hoge" maxActive="4" maxIdle="1" defaultAutoCommit="false" validationQuery="SELECT 1" testWhileIdle="true" minEvictableIdleTimeMillis="180000" /> </Context>
複数のデータソースを使う
jdbc.diconを2つコピーする。
- jdbc1.dicon - 一つ目のDBの設定
- jdbc2.dicon - 二つ目のDBの設定
- jdbc.dicon - 不要なので削除する
s2jdbc.diconを2つコピーする。
- s2jdbc1.dicon - 一つ目のDBに接続するJDBCManagerの設定、jdbc.diconの代わりにjdbc1.diconをインクルードする
- s2jdbc2.dicon - 二つ目のDBに接続するJDBCManagerの設定、jdbc.diconの代わりにjdbc2.diconをインクルードする
- s2jdbc.dicon - JDBCManagerの設定は削除し、上記2つのdiconをincludeするだけ