凪瀬 Blog
Programming SHOT BAR

目次

Blog 利用状況
  • 投稿数 - 260
  • 記事 - 0
  • コメント - 46859
  • トラックバック - 192
ニュース
広告
  • Java開発者募集中
  • 経歴不問
  • 腕に自信のある方
  • 富山市内
  • (株)凪瀬アーキテクツ
アクセサリ
  • あわせて読みたい
凪瀬悠輝(なぎせ ゆうき)
  • Java技術者
  • お茶好き。カクテル好き。
  • 所属は(株)凪瀬アーキテクツ
  • Twitter:@nagise

書庫

日記カテゴリ

 

Seaserのホットデプロイは開発向けにはなかなか便利ですが、これを使えるようにフレームワークの構成を考えるのはかなりしんどいものがあります。今回はハマったポイントとしてリフレクションを取り上げます。

Seaserのホットデプロイ

Seaserのホットデプロイは 実行時に特定箇所でクラスローダを毎回新しくするというシンプルなものです。

結構、割り切った作りになっているなぁというのが感想ですが、そのシンプルさと原理からいろいろと制限事項も生まれてしまう。

今回、私がハマったところはフレームワーク内でリフレクションをする際に、クラスローダの違いからClassCastExceptionを出したり、NoSuchMethodExceptionを出したりしたことでした。

クラスローダの基礎知識

まず、基礎知識としてクラスローダが違う場合、同じClassでも代入互換性がありません。Classの同一性は、Class自身とそのクラスローダによって定められます。

次に、クラスローダは階層構造をなしていて、まず親クラスローダにロード処理が移譲されます。

ClassLoader

ClassLoader クラスは、委譲モデルを使ってクラスとリソースを探します。ClassLoader の各インスタンスは、関連する親クラスローダーを持ちます。クラスまたはリソースを見つけるために呼び出されると、ClassLoader インスタンスはそれ自体でクラスまたはリソースの検索を試みる前に、その検索を親クラスに委譲します。「ブートストラップクラスローダー」と呼ばれる仮想マシンの組み込みクラスローダーはそれ自体では親を持たず、ClassLoader インスタンスの親として動作します。

このため、親が同じである場合は、同じクラスローダ(つまるところ親)で読み込まれることで代入互換を持つことがあります。

クラスローダを跨ぐ場合のClassCastException

Seaserのホットデプロイは HotdeployUtilのstart()とstop()の間でクラスローダが変わることで実現しています。

なので、この区間の中と外ではクラスローダが異なります。そのため、戻り値のClassCastExceptionが発生することがあります。

SingletonS2ContainerFactory.init();
S2Container container = SingletonS2ContainerFactory.getContainer();
HotdeployUtil.start();

Object o = container.getComponent("testLogic");
System.out.println(o.getClass().getClassLoader());
Hoge hoge = (Hoge)o;

HotdeployUtil.stop();

log4j:WARN No appenders could be found for logger (org.seasar.framework.container.factory.S2ContainerFactory).
log4j:WARN Please initialize the log4j system properly.
org.seasar.framework.container.hotdeploy.HotdeployClassLoader@6e70c7
Exception in thread "main" java.lang.ClassCastException: test.logic.impl.TestLogicImpl cannot be cast to test.Hoge
    at test.LoaderTest.main(LoaderTest.java:19)

こうした再読込をおこなうタイプのクラスローダは親クラスローダへの委譲を行わないことで実現できます。親へ委譲してしまうと読み込み済みのクラスをリフレッシュすることができません。

しかし、この場合、クラスローダ越境ができなくなります。通常は、親への委譲を先に行うことで、共通して読み込まれるクラスには互換があったわけですが、クラスをロードしなおすために、親への委譲を行わないようにすると、上記例のようにClassCastExceptionとなってしまうのです。

リフレクションの際のメソッドシグネチャにも注意!

こうしたクラスローダを跨いだ箇所でリフレクションを行っていると、わかりにくいバグに遭遇することがあります。

SingletonS2ContainerFactory.init();
S2Container container = SingletonS2ContainerFactory.getContainer();
HotdeployUtil.start();

Object o = container.getComponent("testLogic");
Method[] ms = o.getClass().getMethods();
System.out.println(Arrays.toString(ms));

Method m = o.getClass().getMethod("setHoge", Hoge.class);

HotdeployUtil.stop();

log4j:WARN No appenders could be found for logger (org.seasar.framework.container.factory.S2ContainerFactory).
log4j:WARN Please initialize the log4j system properly.
[public void test.logic.impl.TestLogicImpl.setHoge(test.Hoge), public test.Hoge test.logic.impl.TestLogicImpl.getHoge(), public native int java.lang.Object.hashCode(), public final native java.lang.Class java.lang.Object.getClass(), public final void java.lang.Object.wait(long,int) throws java.lang.InterruptedException, public final void java.lang.Object.wait() throws java.lang.InterruptedException, public final native void java.lang.Object.wait(long) throws java.lang.InterruptedException, public boolean java.lang.Object.equals(java.lang.Object), public java.lang.String java.lang.Object.toString(), public final native void java.lang.Object.notify(), public final native void java.lang.Object.notifyAll()]
Exception in thread "main" java.lang.NoSuchMethodException: test.logic.impl.TestLogicImpl.setHoge(test.Hoge)
    at java.lang.Class.getMethod(Unknown Source)
    at test.LoaderTest.main(LoaderTest.java:24)

ご覧のように、setHoge(Hoge)が存在しているにも関わらず、NoSuchMethodExceptionが投げられsetHoge(Hoge)はない!と言われてしまうのです。これは、getMethod()の引数に渡すHoge.classと、読み込まれたコンポーネントのHoge.classでクラスローダが異なるためです。

開発用と割り切って全体を囲ってしまう

クラスローダの越境は非常に厄介ですので、処理の頭からお尻までまるごとクラスローダを差し替えるように使うのが無難です。

Servletであれば、ホットデプロイ用の HotdeployFilter が用意されていますのでそちらでリクエストの全体で差し替えるほうがよいでしょうね。

投稿日時 : 2008年6月17日 15:39
コメント
No comments posted yet.
タイトル
名前
Url
コメント