SAStrutsを読む:サーブレットコンテナからStrutsまで

目的

リクエストがどこを通ってActionまで到達するのか把握

予備知識

サーブレット
JavaによるWebアプリケーション実装の仕様。インタフェース定義とかファイル構成とか
サーブレットコンテナ
サーブレットを動かすためのソフトウェアの仕様。
Tomcat
サーブレットコンテナ実装のひとつ。単体でwebサーバとして機能するしApacheとの連携も可。Tomcat6はServlet2.5対応。
J2EE
Javaのライブラリ規格、J2SEの拡張。サーブレットとかその他エンタープライジーなものが定義されてる。
  • Servlet 2.5仕様(If you intend to build an implementation of... のあたりからダウンロード可能)

準備

DoltengでWebアプリケーションプロジェクト作成。プレゼンテーションはSAStrutsで。必要な設定ファイルとコードの骨組みが自動生成される。

大まかな処理の流れ

リクエストをコンテナが処理→フィルタ(ルーティング等)→StrutsのActionServlet→SAStrutsのRequestProcessor→SAStrutsのActionWrapper→アクションクラス

コンテナからServletまで

web.xml

WEB-INF/web.xmlはデプロイメント記述子(Deployment descriptor)と呼ばれる設定ファイルで、Servlet仕様で決まっている(SRV.13あたり)。
サーブレットとURLをマッピングしたりフィルタをつけたりできる。

フィルタ

処理がサーブレットへ到達する経路にフィルタをかませてなんかすることができる。URLのかきかえとか。
処理の概念はこのへんがわかりやすい。

フィルタの実装はこんなぐあい:

class MyFilter extends Filter
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
			throws IOException, ServletException {
		Servletにリクエストが渡る前にやりたい処理(); // リクエストの書き換えとか
		chain.doFilter(request,response); // 次のフィルタ、あるいはサーブレット本体を起動する
		Servletがリクエストを処理したあとにやりたい処理(); // レスポンスの書き換えとか
	}
}

このパターンS2AOPでも見たな。なんか名前ついてそうだけど。

web.xmlのfilter要素でフィルタを定義、filter-mappingでフィルタを適用する場所を決める。
デフォルトでは以下のフィルタが定義されている。

encodingfilter
リクエストのエンコーディングを設定する
s2filter
コメントに「S2Container用のフィルタです」としか書いてない……
hotdeployfilter
まあホットデプロイ用のなんかでしょうな
routingfilter
渡されたURLを内部URLにかきかえる
requestDumpFilter
リクエストの内容をサーブレットが処理する前後にダンプする

重要なのはroutingfilter、s2filterあたりか。

マッピングは以下。

<filter-mapping>
	<filter-name>s2filter</filter-name>
	<url-pattern>/*</url-pattern>
	<dispatcher>REQUEST</dispatcher>
	<dispatcher>FORWARD</dispatcher>
	<dispatcher>INCLUDE</dispatcher>
</filter-mapping>

<filter-mapping>
	<filter-name>routingfilter</filter-name>
	<url-pattern>/*</url-pattern>
	<dispatcher>REQUEST</dispatcher>
</filter-mapping>

全URLに対して適用される。dispatcherの設定については、REQUESTが通常時、FORWARD/INCLUDEがそれぞれフォワード/インクルード時に呼ばれる。

フォワード
レスポンスを破棄して他のサーブレットに任せる
インクルード
他のサーブレットを呼び出す

ということだとおもう。
この場合ルーティングはリクエスト時のみ、S2コンテナのフィルタは全部のケースで適用される。

s2filter
public void doFilter(ServletRequest request, ServletResponse response,
		FilterChain chain) throws IOException, ServletException {

	S2Container container = SingletonS2ContainerFactory.getContainer();
	ExternalContext externalContext = container.getExternalContext();
	if (externalContext == null) {
		throw new EmptyRuntimeException("externalContext");
	}
	
	final Object originalRequest = externalContext.getRequest();
	final Object originalResponse = externalContext.getResponse();
	try {
		externalContext.setRequest(request);
		externalContext.setResponse(response);
		chain.doFilter(request, response);
	} finally {
		externalContext.setRequest(originalRequest);
		externalContext.setResponse(originalResponse);
		invalidateSession(request);
	}
}

外部コンテキストに設定されてるrequest,responseを渡されたので挿げ替えたり元に戻したり。外部コンテキストがなんなのかわからないので意味がわからないですね。

routingfilter

何がひどいってdoFilterが100行あるうえに特にコメントはないというのが。for文の内部が60行あるとか……
リクエストされたパスを適切に処理してアクションがあったらフォワードして云々(読む気がしない……)。
とにかく対応するアクションがあれば「アクションのパス.do」へフォワード、なかったときだけ次のchainを起動するようだ。

サーブレット本体:ここからStruts

servlet要素でサーブレットを定義、servlet-mapping要素でURLパターンとサーブレットマッピングする。
デフォルトで生成される設定では以下のようになっている。

<servlet>
	<servlet-name>action</servlet-name>
	<servlet-class>org.apache.struts.action.ActionServlet</servlet-class>
	<!-- init-param 略 -->
	<load-on-startup>1</load-on-startup>
</servlet>
<servlet>
	<servlet-name>s2container</servlet-name>
	<servlet-class>org.seasar.framework.container.servlet.S2ContainerServlet</servlet-class>
	<load-on-startup>2</load-on-startup>
</servlet>
<servlet-mapping>
	<servlet-name>action</servlet-name>
	<url-pattern>*.do</url-pattern>
</servlet-mapping>
<servlet-mapping>
	<servlet-name>s2container</servlet-name>
	<url-pattern>/s2container</url-pattern>
</servlet-mapping>

*.doというURLに対してlorg.apache.struts.action.ActionServletというサーブレットが呼ばれる。
/s2containerに対してS2ContainerServletが呼ばれる。

routingfilterでフォワードされる先はアクションパス.doなのでStrutsのActionServletが呼ばれる。

サーブレットに来たGETリクエストはdoGetが処理する。ActionServletにおいてはprocess()に委譲してるだけ。

public void doGet(HttpServletRequest request,
		  HttpServletResponse response)
	throws IOException, ServletException {
	process(request, response);
}
protected void process(HttpServletRequest request, HttpServletResponse response)
	throws IOException, ServletException {
	ModuleUtils.getInstance().selectModule(request, getServletContext());
	ModuleConfig config = getModuleConfig(request);

	RequestProcessor processor = getProcessorForModule(config);
	if (processor == null) {
	   processor = getRequestProcessor(config);
	}
	processor.process(request, response);
}

lRequestProcessorを取得して丸投げ。
さてこのRequestProcessorがどこで決まるかというと、struts-config.xmlのcontroller要素。

<controller
	maxFileSize="1024K"
	bufferSize="1024"
	processorClass="org.seasar.struts.action.S2RequestProcessor"
	multipartClass="org.seasar.struts.upload.S2MultipartRequestHandler"/>

processorClass,multipartClassがSAStrutsのものになっている。
multipartClassはマルチパートなリクエストを処理するためのもので、通常はS2RequestProcessorが呼ばれることがわかる。

S2RequestProcessor: ここからSAStruts

これがメインの処理かな。
このクラスはlRequestProcessorを継承している。

public void process(HttpServletRequest request, HttpServletResponse response)
		throws IOException, ServletException {
	request = processMultipart(request);
	String path = processPath(request, response);
	if (path == null) {
		return;
	}
	processLocale(request, response);
	processContent(request, response);
	processNoCache(request, response);
	if (!processPreprocess(request, response)) {
		return;
	}
	processCachedMessages(request, response);

このへんのprocessなんとかは親クラスのもの。前処理を行ったあとでアクションへのディスパッチを開始する。

	ActionMapping mapping = processMapping(request, response, path);
	if (mapping == null) {
		return;
	}

ActionMappingStrutsのクラス。なんかdeprecatedとか書いてあるけど……。
アクションに処理させるためのクラスだと思う。
processMapping(path)はmoduleConfig.findActionConfig(path)でアクションマッピングを取得している。どこから得てるのかはとりあえず保留。

	ActionForm form = processActionForm(request, response, mapping);

ActionFormstrutsのクラス。まあActionFormですな。

その後も前準備がしばらく続くけど略。

	Action action = processActionCreate(request, response, mapping);
	if (action == null) {
		return;
	}
	ActionForward forward = processActionPerform(request, response, action,
			form, mapping);
	processForwardConfig(request, response, forward);
}

えーと……(未完)

まとめ

Strutsとがっちりかみあってるあたりからわけがわからなくなった

つづき: SAStrutsを読む:Struts以降 - <s>gnarl,</s>技術メモ”’<marquee><textarea>¥
関連:S2AOPを読む - <s>gnarl,</s>技術メモ”’<marquee><textarea>¥