Seasar2入門メモ@SAStruts(Super Agile Struts)

読んだ書籍のメモ

メモ

パッケージ構成

  • *.action : MVCのコントローラー
  • *.form : アクションフォーム
  • *.entity : エンティティ
  • *.service : サービス - エンティティに対する操作を実行するクラス
  • *.util : 命名規則なしの基本的にstaticメソッドで構成されるクラス

* はルートパッケージ。ルートパッケージは src/main/resources/convention.dicon で指定する。formservice などは自動ID対象だが、この設定は creator.dicon に存在する。

チュートリアルのメモ

Super Agile Struts - Setupからはじまるチュートリアルを本を読みつつひと通りやった。

最初のサンプルプロジェクトで「[ESSR0007]S2Containernullあるいは空であってはいけません」と出て動かなかったんだけどTomcatを再起動したら直った。なぜだろう?

Action (コントローラー)

詳細は Super Agile Struts - Feature Reference

// このコードはサンプルの足し算のもの…にコメントを追加したもの
package tutorial.action;

import javax.annotation.Resource;

import org.seasar.struts.annotation.ActionForm;
import org.seasar.struts.annotation.Execute;

import tutorial.form.AddForm;

public class AddAction {
  /**
   - @ActionForm
   - アクションフォームとして
   - 使用したいフィールドに
   - つけるアノテーション
   */
  @ActionForm

  /**
   * @Resouce
   * Seasar2フレームワークに
   * 自動的にフィールドのインスタンスを
   * 設定するように
   * 指示するためのアノテーション。
   */
  @Resource
  protected AddForm addForm;

  /**
   * JSPに出力する目的のみのフィールド。
   * ここにセットした値はJSPに送られる。
   * JSPに出力する目的だけなので
   * アノテーションは必要ない
   */
  public Integer result;

  /**
   * @Excecute
   * http://sastruts.seasar.org/annotationReference.html#Execute
   * リクエストを処理する実行メソッドにつけるアノテーション。
   * これを指定するメソッドは引数は無い。また、
   * 戻り値は String であり遷移先のパスを返す。
   * デフォルトは .jsp にフォワードされる。
   * リダイレクトしたい場合は "xxx.jsp?redirect=true" のようにする。
   * @Excecute(recirect = true) でもリダイレクトになる。
   * 外部サイトへリダイレクトする場合は "http://fernweh/jp/?redirect=true" のよう。
   * @Excecute(urlPattern="xxx/{param}") でURLパターンとその場合のパラメータを指定
   * @Excecute(validator = false) で値のバリデーションを行わなくなる(デフォルトは validator = true)
   * @Excecute(input = "yyy.jsp") は validator=true の時に必須で、検証失敗時のフォワード先を指定する。拡張子がない場合は指定した文字列と同様の実行メソッドが呼ばれる。
   *
   * @Excecute(validate="hoge", input="err.jsp")
   * 上の例はvalidateで検証に hogeメソッドを指定している。
   * アット***Typeで検証できないような値を
   * 検証するためのもの。
   * 詳細は http://sastruts.seasar.org/featureReference.html#ValidateMethod
   *
   * @Excecute(removeActionForm = true)
   * セッションスコープのアクションフォームを削除する。
   */
  @Execute(validator = false)
  public String index() {
    return "index.jsp";
  }

  @Execute(input = "index.jsp")
  public String submit() {
    result = Integer.valueOf(addForm.arg1) + Integer.valueOf(addForm.arg2);
    return "index.jsp";
  }
}

jsp (ビュー) と EL関数(f)

<!-- ページの文字エンコーディング設定 -->
<%@page pageEncoding="UTF-8"%>
<html>
<head>
<title>Tutorial: Add</title>
<!-- f:url関数は引数にルートディレクトリをくっつける -->
<link rel="stylesheet" type="text/css" href="${f:url('/css/sa.css')}" />
</head>
<body>

<h1>Tutorial: Add</h1>

<!-- エラーメッセージを表示する -->
<html:errors/>
<!-- SAStruts アクションフォーム用のタグ s:form -->
<s:form>
<!-- SAStruts アクションフォーム用の
  テキストフィールド部品 html:text
  アクションフォームのプロパティ名をproperty属性に指定する -->
<html:text property="arg1"/> +
<!-- errorStyleClass にエラー時のclass属性値を指定できる -->
<html:text property="arg2" errorStyleClass="hogehoge" />
<!-- SAStruts f:h はHTMLエスケープする関数。
引数はプロパティ名で、プロパティの中身をHTMLエスケープして出力する。
エスケープせずにそのまま出力する場合は ${result} -->
= ${f:h(result)}<br />
<!-- type="submit" を持つ INPUT要素のname属性は
呼び出したいActionの実行メソッドを指定する -->
<input type="submit" name="submit" value="サブミット"/>
</s:form>
</body>
</html>
  • f:u() - urlエンコード
  • f:br() - 改行をに変換
  • f:nbsp() - 空白をそのまま表示
  • f:date() - 文字列をDate型に変換
  • f:number() - 文字列をNumber型に変換

アクションフォーム XxxForm

import org.seasar.struts.annotation.IntegerType;
import org.seasar.struts.annotation.Required;

public class XxxForm {
  /**
   * @Required
   * 必須チェックのアノテーション
   * このアノテーションが存在する場合は
   * nullチェックが実行され nullの場合は
   * エラーメッセージが送信される。
   *
   *
   * msg要素(オプション)
   * @Msg
   * 検証がNGの場合のメッセージを指定する。
   * デフォルトは @Msg(key = "errors.required")
   *
   * key要素(必須) : resource=true の場合に
   * メッセージリソース(application(_ja).properties)に
   * 登録されているキーを指定する。
   *
   * resource要素 : メッセージがメッセージリソースに
   * 登録されているかどうかを指定する。
   *
   * bundle要素 : リソースバンドルの指定
   *
   *
   * arg0要素(オプション)
   * @Arg
   * メッセージの最初の引数を指定。
   * デフォルトは
   * @Arg(key="プロパティ名")
   *
   *
   * target要素(オプション)
   * @Required(target = "goNext")
   * 検証の対象とするメソッドを指定。
   * 複数ある場合はカンマで区切る
   */
  @Required

  /**
   * @IntegerType
   * Integer型チェックのアノテーション
   * このアノテーションを指定すした場合は
   * フォームに入力された値にチェックが入る。
   * 値として不正な場合は
   * エラーメッセージがJSPに送信される。
   *
   * msg要素(オプション)
   * デフォルトは
   * @Msg(key="errors.integer")
   *
   * arg0要素(オプション)
   * デフォルトはプロパティ名
   *
   * target要素(オプション)
   * 検証の対象とするメソッド名
   *
   *
   * 型チェックのアノテーションは他に
   * @ByteType, @ShortType, @IntegerType,
   * @LongType, @FloatType, @DoubleType,
   * @DateType, @CreditCartType,
   * @EmailType, @UrlType がある。
   * @Maskは正規表現によるvalidateを実行。
   *
   * 値の範囲チェックのアノテーション
   * は @***Range。@IntRange, @LongRange,
   * @FloatRange, @DoubleRange の4つ。
   *
   * @Validwhen アノテーションにより
   * 検証を実行する条件を指定できる。
   */
  @IntegerType

  // パブリックフィールド - String 型を指定する
  // public型はプロパティとして認識されるのでアクセサの記述は不要
  public String arg;
}
  • フォームの input type="submit"name属性の値

は、フォームを実行した時に実行される Action クラスのメソッド名を指定する。

// セッションスコープのアクションフォーム
import java.io.Serializable;

import org.seasar.framework.container.annotation.tiger.Component;
import org.seasar.framework.container.annotation.tiger.InstanceType;
import org.seasar.struts.annotation.Required;

// 下のアノテーションをアクションフォームに
// 付加することでセッションスコープで扱える。
// これはセッションスコープのDtoにも使う。
@Component(instance = InstanceType.SESSION)
public class SessionForm implements Serializable {

  // セッションスコープのアクションフォームには
  // Serializable インターフェイスを実装しなければならない。
  private static final long serialVersionUID = 1L;

  @Required
  public String first;

  @Required(target = "goThird")
  public String second;
}

繰り返し

その1

public class XxxAction {

  public List<String> xxxItems = new ArrayList<String>();

  @Execute(validator = false)
  public String index() {
    for (int i = 0; i < 10; i++) {
      xxxItems.add("" + i);
    }
    return "xxx.jsp";
  }
}
<!-- JSPファイル -->
<c:forEach var="foo" varStatus="bar" items="${xxxItems}">
bar.index : ${f:h(bar.index)}<br />
foo : ${f:h(foo)}<br />
</c:forEach>

その2

public class XxxAction {

  public List<Map<String, Object>> xxxItems;

  @Execute(validator = false)
  public String index() {
    xxxItems = new ArrayList<Map<String, Object>>();
    for (int i = 0; i < 10; i++) {
      Map xxx = new HashMap<String, Object>();
      xxx.put("キー", "キーの値" + i);
      xxxItems.add(xxx);
    }
    return "index.jsp";
  }
}
<!-- JSPファイル -->
<c:forEach var="xxx" items="${xxxItems}">
値 : ${f:h(xxx.キー)}<br />
</c:forEach>

その3

フォーム

public class NestedForeachUpdateForm {

  public List<List<Map<String, Object>>> mapItemsItems = new ArrayList<List<Map<String, Object>>>();

  public void initialize() {
    for (int i = 0; i < 10; i++) {
      List<Map<String, Object>> mapItems = new ArrayList<Map<String, Object>>();
      for (int j = 0; j < 2; j++) {
        Map<String, Object> m = new HashMap<String, Object>();
        m.put("id", i * 10 + j);
        m.put("name", "name" + i + j);
        mapItems.add(m);
      }
      mapItemsItems.add(mapItems);
    }
  }
}
<!-- JSPファイル -->
<s:form>
<table border="1">
<c:forEach var="mapItems" varStatus="s" items="${mapItemsItems}">
  <tr>
  <c:forEach var="m" varStatus="s2" items="${mapItems}">
    <td>
      <input type="text"
        name="mapItemsItems[${s.index}][${s2.index}].id"
        value="${f:h(m.id)}"/>
    </td>
    <td>
      <input type="text"
        name="mapItemsItems[${s.index}][${s2.index}].name"
        value="${f:h(m.name)}"/>
    </td>
  </c:forEach>
  </tr>
</c:forEach>
</table><br />
<input type="submit" name="submit" value="サブミット"/>
</s:form>

選択リスト

<!-- JSPファイル -->
<s:form>
  <html:select property="formFieldName">
  <html:option value="1">One</html:option>
  <html:option value="2">Two</html:option>
  <html:option value="3">Three</html:option>
</html:select>

というような感じでOK。options を配列から生成したい場合は c:forEach を使いましょう。

アクションフォームのプロパティを public Integer formFieldName; みたいにしておけば送信時に格納されますよ。

複数選択リスト

選択リストと似たようなものだけど、

アクションフォームのプロパティは当然配列にしておきます。

またセッションフォームの場合に、複数選択リストで何も選択しないでsubmitすると何も送信されないので、

何も選択されないことをサーバに伝えることができない。これを解決するためにはリセットメソッドを使う必要がある。

@Execute(reset = "メソッド名") でリセットメソッドを指定する。ここで指定するメソッド名は、セッションフォームのメソッドです。

<!-- JSPファイル は 選択リストとほとんど変わりがない -->
<s:form>
  <html:select property="formFieldName" multiple="true" size="3">
  <html:option value="1">One</html:option>
  <html:option value="2">Two</html:option>
  <html:option value="3">Three</html:option>
</html:select>

チェックボックス

チェックボックスもチェックしなかった場合は何も送信されないので、セッションフォームを利用する場合はリセットメソッドを設定しておくこと。。

<!-- JSPファイル -->
<s:form>
<html:checkbox property="formFieldName">
</s:form>

アクションフォームのプロパティに public boolean check1; のようにしておけば送信時にセットされる。

複数選択可能なチェックボックス

まぁチェックボックスと変わりないです。

<!-- JSPファイル -->
<s:form>
<html:multibox property="formFieldName" value="1">
<html:multibox property="formFieldName" valie="2">
</s:form>

ラジオボタン

簡単

<!-- JSPファイル -->
<s:form>
<html:radio property="formFieldName" value="1" /> One
<html:radio property="formFieldName" value="2" /> Two
<html:radio property="formFieldName" value="3" /> Three
</s:form>

テキストエリア

<html:textarea property="formFieldName" />

@Mask

正規表現によるバリデーションを行うためのアノテーション

@Mask(
  mask="(\\d)+",
  msg=@Msg(key = "errors.hoge"),
  arg0 = @Arg(key = "この正規表現は", resource = false),
  args = @Arg(key = "数値のみしか受け付けないよ", resource = false, position = 1)
)
public String hoge;

@Validwhen

public String validwhen1Text;

@Validwhen(
  test = "((validwhen1Text == null) or (*this* != null))",
  msg = @Msg(key = "errors.required.other"),
  args = @Arg(key = "validwhen1Text", resource = false, position = 1)
)
public String validwhen2Text;

text で指定した条件を満たさなければダメだよっていうアノテーション検証を可能とする

クライアントサイド検証

jspファイルの記述でJavascriptによるクライアントサイド検証を実行できる。

<!-- jspファイル -->
<head>
  <html:javascript formName="xxxActionForm_submit"/>
</head>

ヘッダにチェック用Javascriptを出力する html:javascript 要素を記述。

formName属性の命名規則は 「アクション名 + From_ + 実行メソッド名」

サブパッケージ名をつけるときは 「サブパッケージ名 + _ + 以降同じ」となる。

<!-- jspファイル -->
<s:submit property="submit" clientValidate="true">aaaが必須</s:submit>

フォームは上のコードのように clientValidate="true" と属性を設定する。

タグによる条件分岐

// jspファイル
<c:if test="${id != null}">
"id" is not null.
</c:if>
<c:if test="${id == null}">
"id" is null.
</c:if>
<br />
<c:choose>
<c:when test="${id == '1'}">
"id" is one.
</c:when>
<c:when test="${id == '2'}">
"id" is two.
</c:when>
<c:otherwise>
"id" is other.
</c:otherwise>
</c:choose>

c:iftest属性の結果がtureなら中身が表示される。elseifに相当するものは無いらしい…

c:choose, c:when, c:otherwiseswitchな条件分岐が可能

ボタンの二度押し対策

Strutsの同期トークンを利用して2重サブミット防止対策を行う。

org.apache.struts.util.TokenProcessorクラスによって同期トークンを生成してhiddenフィールドに埋め込み、サブミット時にそのトークンをチェックする。

// チュートリのコピペにわずかにコメントと手を加えたモノ
public class TokenAction {

  @Resource
  protected HttpServletRequest request;

  @Execute(validator = false)
  public String index() {
    // 同期トークンの生成
    TokenProcessor.getInstance().saveToken(request);
    return "index.jsp";
  }

  // validate = "validateToken" ではバリデーションを実行する任意のメソッド(ここでは validateToken )を指定している。
  // これはアノテーションでは処理できないようなものを記述する。
  // validate で指定したメソッドは validator = false でも実行され、
  // ActionMessagesを返す必要がある。
  // ActionMessagesにメッセージを追加せずに返した場合は valid であると判定される。
  @Execute(validator = false, validate = "validateToken", input = "index.jsp")
  public String result() {
    return "result.jsp";
  }

  public ActionMessages validateToken() {
    ActionMessages errors = new ActionMessages();
    // #isTokenValid で同期トークンが同じものかチェックする。
    // 2つ目の引数はトークンが一致した時にトークンを削除するかどうかのフラグ
    if (!TokenProcessor.getInstance().isTokenValid(request, true)) {
      errors.add(
        ActionMessages.GLOBAL_MESSAGE,
        new ActionMessage("errors.invalid", "Token")
      );
    }
    return errors;
  }
}

ファイルアップロード

アップロードしたファイルを受け取るアクションフォームのフィールドは FormFile クラス。

@Required
public FormFile formFile;
// チュートリのコピペにわずかにコメントと手を加えたモノ
public class UploadAction {

  @ActionForm
  @Resource
  protected UploadForm uploadForm;

  @Resource
  protected HttpServletRequest request;

  @Resource
  protected ServletContext application;

  // UploadUtil.checkSizeLimit で struts-config.xml の controller > maxFileSize の上限チェックを実行
  // 上限を超えていたら errors.upload.size を投げる。
  @Execute(validator = false)
  public String index() {
    UploadUtil.checkSizeLimit(request);
    return "index.jsp";
  }

  @Execute(input = "index.jsp")
  public String upload() {
    upload(uploadForm.formFile);
    return "index.jsp";
  }

  // UploadUtil.write でファイルの書き込み実行
  protected void upload(FormFile file) {
    String path = application.getRealPath("/WEB-INF/work/"
      + file.getFileName());
    UploadUtil.write(path, file);
  }
}

ダウンロード

public class DownloadAction {

  @Execute(validator = false)
  public String index() {
    return "index.jsp";
  }

  @Execute(validator = false)
  public String download() {
    try {
      ResponseUtil.download(
        // ファイル名を指定 - 日本語を含める場合は ISO-8859-1 に変換?
        // するらしいけど、環境に依存するそうなのでASCIIが無難
        new String("サンプル.txt".getBytes("Shift_JIS"), "ISO-8859-1"),
        // ファイルの中身
        "こんにちは".getBytes("Shift_JIS")
      );
    } catch (IOException e) {
      throw new IORuntimeException(e);
    }
    // ダウンロードの場合はページ遷移をしないので null を返す。
    return null;
  }
}

レイアウト (Struts - Tiles)

StrutsTiles の機能でレイアウトを作成できる。

tiles:insert, tiles:put 要素によりjspに記述する。

Ajax

Ajaxのリクエストを返すアクションは以下のようになる。

public class AjaxAction {

  @Execute(validator = false)
  public String hello() {
    // ResponseUtil.writeで出力内容を詰める
    ResponseUtil.write("こんにちは");
    // Ajaxの場合はページ遷移しないため return null;
    return null;
  }
}

javascriptからの呼び出すときは ${f:url('hello')} のようにしてURLを指定する。あとはjsで自由に処理すればOK。サンプルだと以下のようにしてレスポンスを出力している。

<input type="button" value="hello"
onclick="$('#message').load('${f:url('hello')}');"/>

ログイン(roles), ログアウト

@Executeroles でロールに指定されたユーザ以外がアクセスした時に NoRoleRuntimeException を投げる。SAStruts 1.0.1-rc1のテスト2(roles属性) - コンピュータクワガタ

session.invalidate() でログアウト

HTTPリクエストとかサーブレットの変数をDIするときの変数名

@Resource
protected HttpServletRequest request;

@Resource
protected ServletContext application;

JSP共通taglibの宣言

/WEB-INF/view/common/common.jsp ファイルで宣言されている。

<%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<%@taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt"%>
<%@taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions"%>
<%@taglib prefix="html" uri="http://struts.apache.org/tags-html"%>
<%@taglib prefix="bean" uri="http://struts.apache.org/tags-bean"%>
<%@taglib prefix="tiles" uri="http://jakarta.apache.org/struts/tags-tiles"%>
<%@taglib prefix="s" uri="http://sastruts.seasar.org"%>
<%@taglib prefix="f" uri="http://sastruts.seasar.org/functions"%>
PREFIXHOSTPATH
cJAVA.SUN.COM/jsp/jstl/core
fmtJAVA.SUN.COM/jsp/jstl/fmt
fnJAVA.SUN.COM/jsp/jstl/functions
htmlSTRUTS.APATCH.ORG/tags-html
beanSTRUTS.APATCH.ORG/tags-bean
tilesJAKARTA.APATCH.ORG/struts/tags-tiles
sSASTRUTS.SEASAR.ORG/
fSASTRUTS.SEASAR.ORG/functions

これが使用される箇所の定義は web.xmljsp-config > include - prelude 要素に書かれている。

  <jsp-config>
    <jsp-property-group>
      <url-pattern>*.jsp</url-pattern>
      <el-ignored>false</el-ignored>
      <page-encoding>UTF-8</page-encoding>
      <scripting-invalid>false</scripting-invalid>

      <!-- コレ -->
      <include-prelude>/WEB-INF/view/common/common.jsp</include-prelude>
    </jsp-property-group>
  </jsp-config>

読んだ書籍

Share
関連記事