プロジェクト(TLP)に昇格することを発表した。Apache Chemistryは企業向けCMSの相互運用仕様
「CMIS(Content Management Interoperability Services)」をオープンソースで実装するプロジェクト。
本日(2011/02/23)の日経BPのニュースでタイトル『"生え抜き外国人"社長の勝算』の記事がありました。
オリンバスの次期社長が英国人が就任されます。まぁ、オリンバスに限らず、ソニーや日産自動車や日本板硝子などの大企業も外国人のトップは珍しくなくなった。その背景は海外の収益がますます増加になるに違いないでしょう。社員レベルも役員クラスも就職に当たっては今後グローバルの視点で考えなければいけなくなりつつです。
Zero Configurationとは
じつは、この「Zero Configuration」機能こそが、Struts 2の目玉とも言える機能です。Struts 1では、アプリケーションの規模に比例して、XMLファイルに記述するActionクラスや画面遷移の定義が膨れ上がり、俗に「XML地獄」と呼ばれるような開発効率の低下を招いていました。Struts 2では、このような状況を打破するべく、XMLファイルへの定義を不要にする機能が盛り込まれました。当初は、前述したようにCodebehindプラグインによってサポートされていました。
XMLファイルの定義をなくす仕組みは、決められたルールに従ってActionクラスやJSPファイルを命名することと、アノテーション機能との合わせ技です。
Zero ConfigurationでHello World
まずは、例によって「Hello Worldアプリケーション」を作成していきましょう。次の図のように、ごくシンプルに、リンクをクリックすれば、Actionクラスが処理されて、文字列を表示するというものです。
「Hello World」アプリケーションの画面遷移
ファイル構成
§ /WEB-INF/lib/commons-fileupload-1.2.1.jar
§ /WEB-INF/lib/commons-io-1.3.2.jar
§ /WEB-INF/lib/commons-logging-1.1.jar
§ /WEB-INF/lib/freemarker-2.3.13.jar
§ /WEB-INF/lib/junit-3.8.1.jar
§ /WEB-INF/lib/ognl-2.6.11.jar
§ /WEB-INF/lib/spring-test-2.5.6.jar
§ /WEB-INF/lib/struts2-core-2.1.6.jar
§ /WEB-INF/lib/xwork-2.1.2.jar
§ /WEB-INF/lib/struts2-convention-plugin-2.1.6.jar
全体のファイル構成は、次のようになります。
<ContextRoot>
├ /WEB-INF
│ ├ /classes
│ │ ├ /zero.action
│ │ │ └ ZeroAction.class
│ │ └ struts.xml
│ ├ /content
│ │ └ zero.jsp
│ ├ /lib
│ │ └ 参照ライブラリー
│ └ web.xml
└ index.jsp
web.xml
web.xmlの記述も新しいバージョンでは、若干変更があります。
[リスト1]web.xml
<?xml version="1.0" encoding="UTF-8"?>
<web-app id="WebApp_9" version="2.4" xmlns="http://java.sun.com/xml/ns/j2ee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">
<display-name>ZeroProject</display-name>
<filter>
<filter-name>struts2</filter-name>
<filter-class>org.apache.struts2.dispatcher.ng.filter.StrutsPrepareAndExecuteFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>struts2</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<welcome-file-list>
<welcome-file>index.jsp</welcome-file>
</welcome-file-list>
</web-app>
<filter>タグで指定するクラスが、org.apache.struts2.dispatcher.ng.filter.StrutsPrepareAndExecuteFilterクラスとなりました。以前のFilterDispatcherクラスは、非推奨に変更されています。ただし、このクラスには、前述した文字コードの変換漏れがあるため、リクエスト文字などで文字化けが発生してしまいます。応急的な解決策としては、文字コードの変換処理をActionクラスに追加するか、以前のFilterDispatcherクラスを使うかのどちらかです。
それ以外は、2.0系列との違いはありません。
struts.xml
次にstruts.xmlですが、これは以下の記述だけです。
[リスト2]struts.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE struts PUBLIC
"-//Apache Software Foundation//DTD Struts Configuration 2.0//EN"
"http://struts.apache.org/dtds/struts-2.0.dtd">
<struts>
<constant name="struts.enable.DynamicMethodInvocation" value="false" />
<constant name="struts.devMode" value="false" />
</struts>
「Zero Configuration」でなければ、Actionクラスの定義など、いろいろ記述する必要があるところですが、これだけで大丈夫です。画面遷移もまったく記述不要です。
Conventionプラグインの設定も、デフォルトでかまわないのであれば、特に指定する必要はありません(既定値などは後述します)。
Actonクラス
Actonクラスは、次のように、連載第一回目のものとほぼ同じです。異なるのは、このクラスが、あるルールに従っているところだけです。
[リスト3]ZeroAction.java
package zero.action;
public class ZeroAction {
private static final long serialVersionUID = 1L;
private String replyMsg;
public String getReplyMsg() {
return replyMsg;
}
public void setReplyMsg(String message) {
this.replyMsg = message;
}
public String execute() throws Exception {
this.setReplyMsg( "これがZero Configuration" );
return "success";
}
}
あるルールとは、このクラスをActionクラスだと認識させるためのもので、struts.xmlでの定義の代わりになるものです。Conventionプラグインによって、デフォルトでは、以下のいずれかのルールに従っているクラスをActionクラスと認識します。
§ com.opensymphony.xwork2.Actionインターフェイスを実装してるクラス
§ クラス名が「Action」で終わっているクラス
またConventionプラグインは、この条件に当てはまるActionクラスを、struts、struts2、action、actionsという名前のパッケージからサーチします。
サンプルのZeroAction.javaでは、パッケージ名がzero.actionで、クラス名がZeroActionとなっていますので、このクラスはActionクラスとして認識されることになります。
Actionクラスの名前が、デフォルトのAction名の生成に使用され、execute()メソッドが呼び出されます。ただし、クラス名の最後の「Action」は、Action名から除かれ、さらに大文字と小文字を組みあわせたCamel形式の名前は、小文字をハイフンで区切った名前に変換されます。ZeroActionクラスなら、「zero.action」になるということです。
また、ここで重要なポイントは、パッケージの階層とクラス名が、ActionのURIと対応(マッピング)していることです。パッケージの階層を上位からサーチして、最初にActionクラスのパッケージだと認識したところがURIのルートになります。つまり、sample.actionパッケージにあるZeroActionクラスなら、sample.actionがルートとなり、「/zero.action」ということです。その他、クラス名とActionを示すURIのマッピング例は、次のとおりです。
クラス名とActionを示すURIの対応例
クラス名 | URI |
zero.action.ZeroAction | /zero.action |
sample.action.test.ZeroAction | /test/zero.action |
sample.struts.company.details.HelloWorldAction | /company/details/hello-world |
なお、URIの.actionは省略可能ですので、「/zero」とするだけで、「/zero.action」と見なされます。
Result処理
Resultに指定するViewテンプレートファイルにもルールがあります。サンプルコードでは、/WEB-INF/content/zero.jspとしていますが、デフォルトでは、/WEB-INF/content/以下からファイルがサーチされます。サーチされるファイル名は、Resultコードと対応しており、「Action名-Resultコード.jsp」となります。つまり、zero.actionから呼ばれるメソッドが"success"を返すなら、「zero-success.jsp」となります。
なお、Resultコードが"success"の場合は省略可能ですので、サンプルコードのように「Action名.jsp」が対応します。また、Actionクラスがサブパッケージにあれば、対応するResultファイルも同じ階層にあるものが対応することになります。
実際のマッピング例は、次のとおりです。
Actionを示すURIとサーチされるResultファイルの対応例
ActionのURI | サーチされるデフォルトのViewファイル |
/zero.action | /zero.jsp |
/test/zero.action | /test/zero-success.jsp |
/company/details/hello-world | /company/details/hello-world.jsp |
なお、デフォルトのResultタイプが「Dispatcher」ですので、まずjspファイルがサーチされ、それがなければ、HTMLファイル(.html)、velocity(.vm)、freemarker(.ftl)の順に探していきます。
従ってサンプルコードでは、「/zero」を指定すると、「/WEB-INF/content/zero.jsp」がViewテンプレートファイルとして用いられることになります。
注意点
Conventionプラグインの動作で、注意が必要なものがあります。それは、Actionクラスが見つからない場合、Actionクラスの処理がスキップされて、Viewテンプレートのみを処理してしまうことです。例えば、/WEB-INF/content/zero2.jspというファイルだけがあり、対応するActionクラスがない状態でも、「/zero2.action」とすると、そのjspファイルが表示されてしまいます。
使い方によっては便利な機能ですが、危険な機能とも言えます。あやまって、Actionクラスと認識されないActionクラスを作ってしまうと、そのクラスの実行がスルーされることになってしまうのです。
カスタマイズ方法
ここまで説明してきたルールは、すべてデフォルトの仕様です。Zero Configurationとはいえ、ある程度は既定値を変更するカスタマイズが可能です。カスタマイズには、アノテーションを用いた指定と、XMLの設定ファイルで既定値を上書きする方法があります。
Actionアノテーション
Actionアノテーションは、Actionメソッドと、それを呼び出すURIのマッピングを指定するものです。Actionアノテーションを用いると、一つのメソッドに複数のURIを設定できたり、executeメソッド以外のメソッドを、Actionメソッドとして設定することができます。
例えば次のようにすると、ZeroAnnクラスのActionメソッドは、「/ann/zero-ann.action」というURIにマッピングされます。デフォルトの「/zero-ann.action」ではアクセスすることができません。
[リスト4]ZeroAnn1.javaの一部
package zero.action;
import com.opensymphony.xwork2.ActionSupport;
import org.apache.struts2.convention.annotation.Action;
public class ZeroAnn1 extends ActionSupport {
...中略...
@Action("/ann/zero1")
public String execute() throws Exception {
setReplyMsg("ann/zero1");
return "success";
}
}
この場合URIが「/ann/zero1.action」ですので、対応するResult処理のViewテンプレートファイルは、「/WEB-INF/content/ann/zero1.jsp」ということになります。
一つのメソッドに、複数のURIをマッピングすることもできます。例えば、次のようにすると、「/ann/zero2.action」でも、「/ann/zero3.action」でも、同じメソッド(execute2)が呼び出されます。
[リスト5]ZeroAnn1.javaの一部
import com.opensymphony.xwork2.ActionSupport;
import org.apache.struts2.convention.annotation.Action;
import org.apache.struts2.convention.annotation.Actions;
public class ZeroAnn1 extends ActionSupport {
...中略...
@Actions({
@Action("/ann/zero2"),
@Action("/ann/zero3")
})
public String execute2() throws Exception {
setReplyMsg("ann/zero2,ann/zero3");
return "success";
}
}
Actionアノテーションのもう一つの用途は、一つのActionクラスに、複数のActionメソッドを定義することです。次の例では、ふたつのActionメソッドを定義しています。
[リスト6]ZeroAnn1.javaの一部
public class ZeroAnn1 extends ActionSupport {
...中略...
@Action("/ann/zero1")
public String execute() throws Exception {
setReplyMsg("ann/zero1");
return "success";
}
@Actions( { @Action("/ann/zero2"), @Action("/ann/zero3") })
public String execute2() throws Exception {
setReplyMsg("ann/zero2,ann/zero3");
return "success";
}
}
InterceptorRefアノテーション
InterceptorRefアノテーションを指定することで、インターセプターの設定をすることができます。このアノテーションは、クラス定義の前に記述し、クラスレベルで指定します。クラスレベルで指定すると、クラスで定義されたすべてのActionメソッドに、このアノテーションが適用されます(ドキュメントでは、メソッド単位で指定可能なようですが、確認できませんでした)。
例えば次のように、「timer」インターセプターと、「defaultStack」を指定すると、ログにメソッドの実行時間が出力されます(Eclipseのコンソールでも確認できます)。
[リスト7]ZeroAnn.javaの一部
import com.opensymphony.xwork2.ActionSupport;
import org.apache.struts2.convention.annotation.Action;
import org.apache.struts2.convention.annotation.InterceptorRef;
import org.apache.struts2.convention.annotation.InterceptorRefs;
...中略...
@InterceptorRefs( { @InterceptorRef("timer"),
@InterceptorRef("defaultStack") })
public class ZeroAnn extends ActionSupport {
...中略...
@Action("/ann/zero")
public String execute() throws Exception {
return "success";
}
}
ログの出力は、次のようになります。
情報: Executed action [/ann/zero!execute] took 197 ms.
Resultアノテーション
Resultアノテーションは、Result処理をカスタマイズする際に使用します。このアノテーションでは、クラスレベルでの指定と、Actionメソッド単位での指定が可能です。
例えば次のように指定すれば、このActionクラスのすべてのActionメソッドで、"failure"というResultコードのときには、fail.jspを用いるようになります。現在時刻の秒数が偶数なら、"failure"を返すようにしていますので、そのときだけ「fail.jsp」が表示に用いられます。
[リスト8]ZeroAnn2.javaの一部
import java.text.SimpleDateFormat;
import java.util.Calendar;
import com.opensymphony.xwork2.ActionSupport;
import org.apache.struts2.convention.annotation.Action;
import org.apache.struts2.convention.annotation.Result;
import org.apache.struts2.convention.annotation.Results;
@Results({
@Result(name="failure", location="fail.jsp")
})
public class ZeroAnn2 extends ActionSupport {
...中略...
public String execute() throws Exception {
Calendar cal = Calendar.getInstance();
SimpleDateFormat d = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss");
this.replyMsg = d.format(cal.getTime());
// 現在時刻の秒が偶数なら、failureを返す。
if ((cal.get(Calendar.SECOND) % 2) == 0) {
return "failure";
}
return "success";
}
}
Actionメソッド単位に指定するには、次のようになります。type属性を使って、Result処理の種別を指定することもできます。
[リスト9]ZeroAnn2.javaの一部
public String execute() throws Exception {
...中略...
@Action(value = "/ann/zero5",
results = { @Result(name = "success",
location = "http://codezine.jp/", type = "redirect") })
public String execute2() {
return "success";
}
ResultPathアノテーション
ResultPathアノテーションは、Result処理で指定するViewテンプレートファイルの場所を指定するアノテーションです。この場所は、デフォルトでは「WEB-INF/content」になっています。
また、ResultPathアノテーションは、メソッドレベルでは指定できません。クラスレベルで指定するか、または、パッケージに対するコメントやアノテーションを記述するpackage-info.javaに記述する必要があります。
例えば次のようにすると、このActionクラスでは、「WEB-INF/jsp」からViewテンプレートファイルがサーチされることになります。
[リスト10]ZeroAnn3.javaの一部
import com.opensymphony.xwork2.ActionSupport;
import org.apache.struts2.convention.annotation.Action;
import org.apache.struts2.convention.annotation.ResultPath;
@ResultPath("/WEB-INF/jsp")
public class ZeroAnn3 extends ActionSupport {
...中略...
@Action("/ann/zero6")
public String execute() {
return "success";
}
}
パッケージ内に、次のような「package-info.java」があると、今度は、パッケージに含まれるActionクラスすべてに指定したことになります。「package-info.java」は、JDK1.5以降の機能で、パッケージの宣言と、アノテーション、コメントだけのファイルです。
[リスト11]package-info.java
@org.apache.struts2.convention.annotation.ResultPath("/WEB-INF/jsp")
package zero.action.ResultPath;
その他のアノテーション
上記以外には、パッケージ単位で任意のURIの起点を指定するNamespaceアノテーションや、例外がスローされたときの遷移先を指定する、ExceptionMappingアノテーションなどがあります。
詳しくは、「Convention Plugin」のドキュメントを参照してください。
XMLファイルによる既定値のカスタマイズ
struts.xmlファイルに<constant>タグで設定を記述することによって、既定値を変更することが可能です。
例えば、struts.xmlに次のような記述を追加すれば、ResultPathの既定値を変更することができます。
<constant name="struts.convention.result.path" value="/WEB-INF/jsp/"/>
主な既定値
次の表に、主な既定値をまとめました。
主な変更可能設定項目
設定項目 | 既定値 | 概要 |
struts.convention.result.path | /WEB-INF/content/ | Viewテンプレートのデフォルトディレクトリ |
struts.convention.action.suffix | Action | Actionクラスと認識するためのサフィックス文字列 |
struts.convention.action. | FALSE | Actionクラスを探すために、パッケージをサーチするか否か |
struts.convention.action. | FALSE | 「@Actionアノテーション」なしで、メソッドをActionメソッドとして扱うか否か |
struts.convention.action. | TRUE | アクション名を小文字に変換するかどうか |
struts.convention.action. | - | アクション名や、Viewテンプレートの区切り文字 |
struts.convention.package. | action,actions,struts,struts2 | Actionクラスを探すパッケージ名 |
struts.convention.package. | FALSE | 上記で指定したパッケージからのサーチを行うか否か |
struts.convention.exclude. | org.apache.struts.*, | Actionクラスのサーチから除外するパッケージ名 |
struts.convention.package. | | ここで指定したパッケージを起点としてActionクラスをサーチする |
文字化け防止措置
前述した文字化けの対処として、文字コードを変換する方法を紹介します。文字化けは、パラメータの受け渡しで発生しますので、パラメータのセッターのところで、変換処理を行います。
引数の文字列を、getCharacterEncodingメソッドで取得したリクエストデータの文字コードで変換しています。
[リスト12]ZeroAnn.javaの一部
public class ZeroAnn extends ActionSupport {
...中略...
public void setReplyMsg(String message) {
try {
HttpServletRequest request = ServletActionContext.getRequest();
this.replyMsg = new String(message.getBytes("iso-8859-1"),
request.getCharacterEncoding());
} catch (UnsupportedEncodingException e) {
}
}
このようにすると、次のように、パラメータに日本語を指定しても、正しく文字がセットされます。
[リスト13]index.jspの一部
<s:url id ="link" action="ann/zero">
<s:param name="replyMsg">ゼロコンフィギュレーション</s:param>
</s:url>
<s:a href="%{link}">文字化け応急措置</s:a><br />