北海道苫小牧市出身の初老PGが書くブログ

永遠のプログラマを夢見る、苫小牧市出身のおじさんのちらしの裏

続・クラスローダー

クラスローダーと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`)

*1:もちろんなんらかのLoaderの守備範囲であることが前提

*2:getMyLocalClass()内では、MyGlobalClassをロードしたローダーが利用される