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部分を置き換えただけなので、ServletにAOPは使えないし、ServletはHOT Deployもできません・・・が、少なくともJavaEE5の機能は満たせるし、S2をTomcatのEJB3コンテナとしてServletから利用することも可能になります。まぁServletを直接使わない人には何も関係ない話なんですが(^_^;)