JAX-RS のリファレンス実装 Jersey のフィルタを使う方法 その2

前回のフィルタの例はあまりにも実用性がなかったので、もう少し実用性のある例を書いてみました。ContainerRequestFilter と ContainerResponseFilter を組み合わせてリソースの処理時間を測るフィルタです。

package test.jersey.filter;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import javax.ws.rs.core.Context;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.sun.jersey.spi.container.ContainerRequest;
import com.sun.jersey.spi.container.ContainerRequestFilter;
import com.sun.jersey.spi.container.ContainerResponse;
import com.sun.jersey.spi.container.ContainerResponseFilter;

public class ResourceBenchmarkFilter implements ContainerRequestFilter, ContainerResponseFilter {

	private Logger log = LoggerFactory.getLogger(ResourceBenchmarkFilter.class);
	
	@Context
	private HttpServletRequest httpServletRequest;
	
	@Override
	public ContainerRequest filter(ContainerRequest request) {
		// リクエストの時間を記録する
		long requestTime = System.currentTimeMillis();
		// セッションを取得する
		HttpSession session = this.httpServletRequest.getSession();
		// リクエストの時間をセッションに保存する
		session.setAttribute("requestTime", requestTime);
		return request;
	}
	
	@Override
	public ContainerResponse filter(ContainerRequest request, ContainerResponse response) {
		// レスポンスの時間を記録する
		long responseTime = System.currentTimeMillis();
		// セッションを取得する
		HttpSession session = this.httpServletRequest.getSession();
		// セッションからリクエストの時間を取り出す
		long requestTime = (Long)session.getAttribute("requestTime");
		// レスポンスとリクエストの時間差を出力する
		log.info("time: " + (responseTime - requestTime) + "ms");
		return response;
	}

}

やっていること自体は単純で、リクエストの時間とレスポンスの時間をそれぞれ記録して時間差をログに出力しているだけです。リクエストとレスポンスの時間はセッションを使って共有しています。

注目すべきはセッションを取得するのに使われているフィールドの HttpServletRequest です。このインスタンスは @Context を目印に Jersey がインジェクトしてくれます。

 フィルタの登録

おさらいですがフィルタは web.xml で以下のように登録します。

		<init-param>
			<param-name>com.sun.jersey.spi.container.ContainerRequestFilters</param-name>
			<param-value>test.jersey.filter.ResourceBenchmarkFilter</param-value>
		</init-param>
		<init-param>
			<param-name>com.sun.jersey.spi.container.ContainerResponseFilters</param-name>
			<param-value>test.jersey.filter.ResourceBenchmarkFilter</param-value>
		</init-param>

実行結果

リソースにアクセスすると以下のようなログが出ます。ロガーの実装には logback を使っています。

23:35:25 [http-bio-8080-exec-3] INFO  t.j.filter.ResourceBenchmarkFilter - time: 7ms

スレッドセーフ

ところで、フィールドにインジェクトされるということは、このフィルタのインスタンスが複数のスレッドから共有されていると競合が起きそうに思えます。そして (仕様としての記述は見当たりませんが) フィルタのインスタンスは複数のスレッドで共有されます。しかし実際には競合は起きません。理由は @Context でインジェクトされたインスタンスは ThreadLocal なためです。ソースは Jersey 開発者の ML でのリプライです。(残念ながらアーカイブの URL は失念してしまいました。)ただ、以前検証したことはあり確かにスレッド間で競合は起きていませんでした。

まとめ

前回のエントリよりはフィルタの用例がイメージしやすいかと思います。インジェクトできるクラスは他にも色々とあるので、組み合わせるとフィルタの用途がぐっと広がりそうです。

ちなみに Jersey には標準で幾つかフィルタが用意されています。詳細は以下の JavaDoc をご覧ください。
http://jersey.java.net/nonav/apidocs/latest/jersey/com/sun/jersey/api/container/filter/package-summary.html