ユーザ用ツール

サイト用ツール


java:sastruts

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;
   ...
}

アクションのユニットテスト

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() {
    // 何か処理
  }
}

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;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するだけ
java/sastruts.txt · 最終更新: 2010/11/15 06:26 by 127.0.0.1