Struts2

Struts 2 はTomcat 上で動くフレームワークです。Struts1 に比べて大きく変更されてますが、シンプルで分かりやすく改良されてます。
更新日 2016-02-13

概要

Struts2 は、Struts1 のフレームワークの良い所を残し、煩雑な設計思想をコンパクトに見直しています。現状ドキュメントが少ないので 習得難易度が高いと思われますが、使い勝手は良くなってます。

処理の流れ

リクエスト → ディスパッチャフィルタ → インターセプター → アクション → JSP です。
実際の作業はアクションクラスとJSP の準備、そして/WIB-INF/classes/struts.xml の編集だけです。

アクション

ActionSupport を継承したクラスです。アクション毎に用意します。リクエストパラメータの値は、このクラスに用意したプロパティ (public もしくはアクセサメソッドを持った変数)にセットされます。またプロパティはJSP からも参照可能です。またStruts2 が 提供するセッションスコープのMap オブジェクトにデータを保持したり、JSP から参照できたりします。
package mypackage;

import org.apache.struts2.ServletActionContext;
import com.opensymphony.xwork2.ActionContext;
import java.util.Map;

public class BrowsePageAction extends ActionSupport 
{
	// request parameters
	public String genre = "";

    public String execute() throws Exception
    {
    	Map<String, Object> mapSession = ActionContext.getContext().getSession();
    	if (mapSession.containsKey("count") == false)
    	{
    		mapSession.put("count", 0);
    	}
    	else
    	{
    		Integer nCount = (Integer)mapSession.get("count");
    		nCount++;
    		mapSession.put("count", nCount);
    	}

		return SUCCESS;
	}
}
リクエストパラメータは同名の変数(public) を用意するだけでそこに保存されます。複数ある場合はList<String> で受ける事もできます。 Integer 型の変数であれば、パラメータを数値変換して保持できます。数値変換できないと文字列扱いになります。複数の数値は文字列になりますが、 これもList<Integer> で受ける事ができます。

JSP

HTML ページを作成します。アクションクラスのプロパティを参照する事ができます。struts2 では新しく使いやすくなった /struts-tags ライブラリを利用します。シンプルでスリム化されていますが、割と習得しやすいと思います。
// BrowsePage.jsp

<@page contentType="text/html; charset=UTF-8" pageEncoding="Windows-31J">
<@taglib prefix="s" uri="/struts-tags">
<html>
	<head>
	</head>
	<body>
genre=
		<s:property value="genre"/>
		<br/>
count=
		<s:property value="#session.count"/>
	</body>
</html>

struts.xml

action タグを記述します。name 属性には該当するURL パスを、class にはアクションクラスを記述します。
<package name="default" namespace="/" extends="struts-default">

	<action name="BrowsePage" class="mypackage.BrowsePageAction">
		<result>/BrowsePage.jsp</result>
	</action>

</package>
例えば上記ではajp://localhost:8009/ast/BrowsePage.action のURL に対応します。

インターセプター

アクションクラスの前、もしくは後に特別な共通の処理を行いたい場合に記述します。アクションクラスにコールバックメソッドを設ける事も、 別にクラスを作る事もできます。下記のオリジナルインターセプターは後者で、リクエストの度に呼ばれます。
// struts.xml の <struts> の中に記述
<package ...>
	<interceptors>
    	<interceptor name="simple" class="mypackage.SimpleInterceptor"/>
		<interceptor-stack name="simpleInterceptorStack" >
	    	<interceptor-ref name="defaultStack"/>
			<interceptor-ref name="simple" />
		</interceptor-stack>
	</interceptors>

	<action name="BrowsePage" class="mypackage.BrowsePageAction">
		<interceptor-ref name="simpleInterceptorStack"/>
		<result>/BrowsePage.jsp</result>
	</action>
</package>
mypackage.SimpleInterceptor を作成
public class SimpleInterceptor extends AbstractInterceptor 
{    
	private static final long serialVersionUID = 1L;    

	public String intercept(ActionInvocation invocation) throws Exception 
	{
		// ココに前処理を書く

		// 次のチェインを呼び出す。
		String result_code = invocation.invoke();  

		// ココに後処理を書く
		// 後処理でHttpServletRequest.setAttribute() してもうまく動作しない?

		return result_code;    
	}
}
前処理 → BrowsePageAction.excute() → 後処理の順番で呼ばれます(他のインターセプターも間で呼ばれています)

サーブレット情報群にアクセスする

struts2 では基本的に扱いを避けられているHttpServlet の類を使うには、下記のようにします。
// アプリケーションスコープに格納
ServletActionContext.getServletContext().setAttribute("DBController", dbCtrl);

HttpServletRequest req = ServletActionContext.getRequest();
HttpServletResponse res = ServletActionContext.getResponse();
HttpSession ses = req.getSession();
やっぱり必要ですよね。

struts2 tag

struts2 では一つのタグライブラリ(/struts-tags) が用意されています。使い方や仕様について詳しく知りたい方はソースコードを見るのが一番 手っ取り早いでしょう。struts-2.1.6\src\core\src\main\java\org\apache\struts2\views\jsp にあります。

s:property

アクションクラスに記述したpublic プロパティやstruts2 の提供するセッションスコープのMap オブジェクト内の値などを出力できます。
// アクションクラスのプロパティ(アクセサメソッド経由、もしくはpublic )
<s:property value="user_name">

// ネストしたプロパティにも簡単にアクセス可!
<s:property value="ojbCategory.objLink.name">

// struts2 の提供するセッションスコープ(Map) の値(user_address はキー)
<s:property value="#session.user_address">

s:if, s:elseif, s:else

比較に使えます。
<s:if test="objLink.name == \"AST\"">
	</s:property value="objLink.name"> is AST.
</s:if>
<s:elseif test="objLink.count == 1">
	</s:property value="objLink.name"> is One.
</s:elseif>
<s:else>
	</s:property value="objLink.name"> is the other.
</s:else>
文字列の比較では、やはりエスケープ付きダブルクォーテーションで無いとダメみたいですね。ハマりました。

s:iterator

List やMap といったコレクションにシーケンシャルアクセスできます。
// ArrayList arrayObjLink = new ArrayList();
// arrayObjLink.add(new ObjLink("Dr.House"));
// arrayObjLink.add(new ObjLink("NYPD Blue"));

<s:iterator value="arrayObjLink" status="st" id="id_Value">
	</s:property value="name">
	</s:property value="id_Value.url">
</s:iterator>

s:set

JSP で簡単に変数を宣言して値を割り当てられます。
<s:set name="nRow" value="0"/> 
<s:property value="#nRow"/>
// 0 と表示される

<s:set name="nRow" value="#nRow+1"/> 
<s:property value="#nRow"/>
// 1 と表示される

Log4J をつかう

WEB-INF/lib にlog4j-1.2.16.jar を置く。WEB-INF/classes にlog4j.properties とcommons-logging.properties を作成する。

log4j.properties

# log4j.properties
### direct log messages to stdout ###
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.Target=System.out
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss} %l %p%n%m%n

### output sample ###
# 2011-01-03 00:53:29 mypackage.BrowsePage.execute(BrowsePage.java:59) ERROR
# messege string


### direct messages to file mylog.log ###
log4j.appender.file=org.apache.log4j.FileAppender
log4j.appender.file.File=${catalina.home}/logs/maruhide.log
log4j.appender.file.Append=true
log4j.appender.file.layout=org.apache.log4j.PatternLayout
log4j.appender.file.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss} %l %p%n%m%n

# info[level], stdout[dest], file[dest]
log4j.rootLogger=info, stdout, file

commons-logging.properties

org.apache.commons.logging.Log=org.apache.commons.logging.impl.Log4JLogger

ソース中での利用方法

log インスタンスを作って、出力メソッドを呼ぶだけです。log4j.properties のlog4j.rootLogger に設定した出力レベル以上のメソッドはログ出力が有効になります。 スレッドセーフなのでstatic でも可能ですし、呼ばれた場所の関数名やソースコード上の行数も取得できます。
TRACE < DEBUG < INFO < WARN < ERROR < FATAL の順です。DBUG 以下にするとログが溢れるほどです。
Log log = LogFactory.getLog("dummy");
log.debug("dbug");
log.info("info");
log.warn("warn");
log.error("error");
log.fatal("fatal");