Tomcat6のDI機能をSeasar2に置き換える

Tomcat6(Servlet2.5)のDI機能は、何度か日記に書いたとおり、JNDIを使ってます。InitialContextを使わなくともフィールドに欲しいコンポーネントをインジェクションしてくれるのは便利です。・・・がしかし、DIコンテナ環境に慣れてしまった今、今更server.xmlにちまちまとインジェクション対象コンポーネントを書く気にはなれません。ですが、ServletはDIコンテナの管理外に存在するので、このままではServletだけがDI環境から取り残されてしまいます(というか現にそうなってます)。Servletを何とかもっと簡単に使う為には、やはりDI機能をDIコンテナにお任せしたいものです。
・・・というわけで、id:da-yoshi:20070209#1170971160の続きを考えてみました。
まずは、org.apache.catalina.Contextインターフェイスの実装。StandardContextを継承して、S2用のjavax.naming.Contextを渡せるように修正します。

public class S2Context extends StandardContext {

	@Override
	public boolean isUseNaming() {
		boolean ret = super.isUseNaming();

		if (ret && getNamingContextListener() == null) {
			NamingContextListener listener = new S2NamingContextListener();
			listener.setName(getNamingContextName());
			addLifecycleListener(listener);
			setNamingContextListener(listener);
		}

		return ret;
	}

	/**
	 * Get naming context full name.
	 */
	private String getNamingContextName() {
		String namingContextName = null;
		Container parent = getParent();
		if (parent == null) {
			namingContextName = getName();
		} else {
			Stack<String> stk = new Stack<String>();
			StringBuffer buff = new StringBuffer();
			while (parent != null) {
				stk.push(parent.getName());
				parent = parent.getParent();
			}
			while (!stk.empty()) {
				buff.append("/" + stk.pop());
			}
			buff.append(getName());
			namingContextName = buff.toString();
		}
		return namingContextName;
	}

}

StandardContextクラスのソースがかなり長くて分割されてないので、やむなくトリッキーな部分をオーバーライドしました。ここで独自のNamingContextListenerを渡します。
次は、そのNamingContextListenerの実装

public class S2NamingContextListener extends NamingContextListener {
	
	private Context ctx;

	@SuppressWarnings("unchecked")
	@Override
	public Context getEnvContext() {
		try {
			if (ctx == null) {
				ClassLoader loader = Thread.currentThread().getContextClassLoader();
				Class<? extends Context> clazz =
					(Class<? extends Context>)loader.loadClass("org.seasar.extension.j2ee.JndiContext");
				Constructor<? extends Context> cons = clazz.getConstructor(Hashtable.class);
				Hashtable<Object, Object> env = new Hashtable<Object, Object>();				
				ctx = cons.newInstance(env);
			}
			return ctx;
		} catch (Exception e) {
			e.printStackTrace();
			return super.getEnvContext();
		}
	}

	@Override
	public void lifecycleEvent(LifecycleEvent event) {
		super.lifecycleEvent(event);
		if (event.getType() == Lifecycle.STOP_EVENT) {
			ctx = null;
		}
	}

}

このgetEnvContextメソッドが実行されているときは、contextClassLoaderにはWebAppClassLoaderがセットされているのを利用しています。これも微妙な方法だなぁ・・・WebAppClassLoaderを使って、S2のJndiContextのクラスをロードして、インスタンスを作成してます。
これを使う前提の、server.xml(context.xml)の設定は

<Context path="/testWeb" reloadable="false" docBase="C:\workspace\testWeb\src\main\webapp" workDir="C:\workspace\testWeb\work"
	className="testweb.catalina.S2Context">
	<Logger className="org.apache.catalina.logger.SystemOutLogger" verbosity="4" timestamp="true"/>
</Context>

前回とは違い、TomcatのlibディレクトリにはS2のライブラリは置きません。作成したContextとNamingContextListenerのクラスだけをlibディレクトリにおきます。
最後に、テストするServlet

public class TestServlet extends HttpServlet {

	@Resource(name = "jdbc/dataSource")
	private DataSource dataSource;

	@Override
	protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		response.setContentType("text/plain; charset=UTF-8");
		PrintWriter out = response.getWriter();
		out.println(dataSource);
	}

}

手抜きです(汗)標準でjdbc.diconに定義されているDataSourceをServletにDIさせようとしています。
これで実行・・・してみたのですが、何故かDIしてくれず、フィールドはnullのままでしたorz
調べてみたのですが・・・どうやらS2-Tigerにjavax.annotationパッケージが入っているのが原因みたいです。S2-Tigerに限らず、WEB-INF/libにjavax.annotationやjavax.ejbやjavax.persistenceのAPIを置いていると、TomcatのAnnotationProcessorと、Servletに定義しているアノテーションのクラスオブジェクトが違ってしまう為、ServletアノテーションTomcatが認識できなくなってしまうようです。うーむ・・・TopLinkのjarとかもAPI入ってるし、これって今後問題化しそうな予感・・・
とりあえずは、S2-Tigerを外して実行してみました。

org.seasar.extension.dbcp.impl.DataSourceImpl@4fdf11

OKですね。S2-Tigerを外すか、S2関連ライブラリをTomcatのlibディレクトリに入れるか、現状では二択を迫られるみたいです・・・が、とりあえずTomcatのDI機能としてS2を使えることを確認できました。あくまでDI部分を置き換えただけなので、ServletAOPは使えないし、ServletはHOT Deployもできません・・・が、少なくともJavaEE5の機能は満たせるし、S2をTomcatEJB3コンテナとしてServletから利用することも可能になります。まぁServletを直接使わない人には何も関係ない話なんですが(^_^;)