クラスローダーとJ2EEパッケージング戦略を理解する の解説がわかりやすいです。servletに初めて触ったときからモヤモヤしていたClassNotFoundExceptionの正体がちょっとだけ見えました。
public class MyGlobalClass{ public MyLocalClass getMyLocalClass(){ return new MyLocalClass(); } } class MyLocalClass{ } class MyClassLoaderTest{ static public void main(String[] args){ System.out.println("main loader: " + MyClassLoaderTest.class.getClassLoader()); MyGlobalClass global = new MyGlobalClass(); System.out.println("global loader: " + global.getClass().getClassLoader()); MyLocalClass local = new MyLocalClass(); System.out.println("local loader: " + local.getClass().getClassLoader()); MyLocalClass localFromGlobal = global.getMyLocalClass(); System.out.println("localFromGlobal loader: " + localFromGlobal.getClass().getClassLoader()); } } 【実行結果】 $ java MyClassLoaderTest main loader: sun.misc.Launcher$AppClassLoader@67ac19 global loader: sun.misc.Launcher$AppClassLoader@67ac19 local loader: sun.misc.Launcher$AppClassLoader@67ac19 localFromGlobal loader: sun.misc.Launcher$AppClassLoader@67ac19
このMyGlobalClassをjarにしてやって、jre/lib/extの下に持っていくと、次のようになります。
$ java MyClassLoaderTest main loader: sun.misc.Launcher$AppClassLoader@53ba3d global loader: sun.misc.Launcher$ExtClassLoader@e80a59 local loader: sun.misc.Launcher$AppClassLoader@53ba3d Exception in thread "main" java.lang.NoClassDefFoundError: MyLocalClass at MyGlobalClass.getMyLocalClass(MyGlobalClass.java:3) at MyClassLoaderTest.main(test.java:8)
同じクラス郡なのに、設置位置*1を変えただけでクラスが見つからなくなります。しかも、MyLocalClassのロードはmain()メソッドでは一度成功しているのにも関わらず、getMyLocalClass()メソッドでロードできなくなっています。これは、
- classの設置位置によって紐付くClassLoaderが変化すること
- Loadしたいクラスの利用元クラスに紐付くClassLoaderが利用される*2
の二つが原因で、先の結果を見てもMyGlobalClassのLoaderが変化しています。Loaderは親にしか検索を委譲しないので、この場合だとExtClassLoaderの子であるAppClassLoaderが担当していた、CLASSPATHに基づく検索が行われなくなってしまった、と。
これらの事実から考えると、例えばmain.jarがsub.jarに依存する場合、sub.jarはmain.jarより上位のローダーの守備範囲に置かねばならないと言うことになります。main.jarをjre/lib/extに置いてしまった場合、sub.jarにCLASSPATHを通してもダメで、sub.jarもjre/lib/extに置く必要があるってことになると思います、はい。
追記。SeasarはHttpServletに依存しているので、s2-framework-2.3.10.jarをjre/lib/extに置くと java.lang.NoClassDefFoundError: javax.servlet.http.HttpServletRequest となるわけです。
えーと、どんなに最悪な置き方でも、せめて tomcat の shared/lib にしとけよっ、って言う常識的な突っ込みは横に置いておくとします(´m`)