目次
読んだ書籍のメモ
メモ
DI
(Dependency Injection
, 依存性注入)
「テストのしやすさ」「モックオブジェクト」 参考:Zend_Application(2) /Zend Framework
におけるDI
コンテナ活用のメリットについて/ -noop
な日々AOP(Aspect Oriented Programming)
超簡単に説明するとソースを修正せずに、メソッドコール前後に好きな処理を埋め込める。
インターセプタ: 織り込む所
ポイントカット: 埋め込む場所を決定する
パッケージ構成
*.action
: MVCのコントローラー*.form
: アクションフォーム*.entity
: エンティティ*.service
: サービス - エンティティに対する操作を実行するクラス*.util
: 命名規則なしの基本的にstaticメソッドで構成されるクラス
*
はルートパッケージ。ルートパッケージは src/main/resources/convention.dicon
で指定する。form
や service
などは自動ID対象だが、この設定は creator.dicon
に存在する。
チュートリアルのメモ
Super Agile Struts - Setup
からはじまるチュートリアルを本を読みつつひと通りやった。
最初のサンプルプロジェクトで「[ESSR0007]S2Container
はnull
あるいは空であってはいけません」と出て動かなかったんだけど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:if
のtest
属性の結果がture
なら中身が表示される。elseif
に相当するものは無いらしい…
c:choose, c:when, c:otherwise
で switch
な条件分岐が可能
ボタンの二度押し対策
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
)
Struts
の Tiles
の機能でレイアウトを作成できる。
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
), ログアウト
@Execute
の roles
でロールに指定されたユーザ以外がアクセスした時に 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"%>
PREFIX | HOST | PATH |
---|---|---|
c | JAVA.SUN.COM | /jsp/jstl/core |
fmt | JAVA.SUN.COM | /jsp/jstl/fmt |
fn | JAVA.SUN.COM | /jsp/jstl/functions |
html | STRUTS.APATCH.ORG | /tags-html |
bean | STRUTS.APATCH.ORG | /tags-bean |
tiles | JAKARTA.APATCH.ORG | /struts/tags-tiles |
s | SASTRUTS.SEASAR.ORG | / |
f | SASTRUTS.SEASAR.ORG | /functions |
これが使用される箇所の定義は web.xml
の jsp-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>