====== Super Agile Struts (SAStruts) ======
[[http://sastruts.seasar.org/|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;
...
}
===== アクションのユニットテスト =====
[[http://ml.seasar.org/archives/seasar-user/2008-February/013045.html|Seasar-user:13045]]
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() {
// 何か処理
}
}