Wicketのホットデプロイの範囲指定の罠(前編)

はじめに

 ホットデプロイを利用する際は、ReloadingClassLoader.includePatternメソッドを使用して範囲を指定します。
 私は最初「単にホットデプロイしたい対象のクラスが含まれるように、includePatternを指定すればいいのかな」と思っていました。

 しかし、「ホットデプロイしたいクラス以外についても、ReloadingClassLoaderとWebappClassLoader(Tomcatの場合)のどちらに読ませるかを意識しないと、トラブルが発生するのでは?」ということに思い至ったのでメモです。

 書いていたら長くなりそうでしたので、今回のメモの範囲は「WebappClassLoaderがRealoding対象クラスを読み込もうとすることがある件」です(後述)。


環境


トラブルの原因

 トラブルの原因となるのは、私が気づいた範囲では以下の二つです。

原因 トラブル顕在化のタイミング トラブル内容
WebappClassLoaderがRealoding対象クラスを読み込もうとすることがある Reloging発生前から顕在化 ホットデプロイ有効時のみ例外が送出される
リローディング後に、古いクラスオブジェクトと関連づいたオブジェクトが残る Reloging発生後に顕在化 同上

 上記のいずれも根本原因は下記の一つです。

  • クラスローダが異なると、同一名のクラスでも別クラス扱いされる
 今回は、「WebappClassLoaderがRealoding対象クラスを読み込もうとすることがある件」についてのメモです。


WebappClassLoaderがRealoding対象クラスを読み込もうとすることがある件

どうして発生するのか

 どうして、includePatternを無視してWebappClassLoaderが読み込んでしまうことがあるのかについてですが、それはクラスがクラス名を解決する際に「自分自身を読み込んだクラスローダに解決を依頼する」という仕様があるためです。
 自分自身をどのクラスローダが読み込んだかによって、クラスの読み込み方は下図の2パターンが存在することになります。



 すなわちWebappClassLoaderによって読み込まれたクラスは、読み込み対象がincludePatternに含まれていようが含まれていまいが、WebappClassLoaderに読み込みを依頼します。
 RelodingClassLoaderが読み込んだクラスと、WebappClassLoaderが読み込んだクラスは名前が同じでも別物扱いされるので、型変換や、クラスロードのリンクフェーズで例外が発生することになります。

実験方法
下図のクラス図が概要となります。

ポイントは下記の通りです。
  • Reloading対象のクラス(HomePageクラス)と、Reloding対象外のクラス(ExcludeClassクラス)が、あるクラス名(ABeanクラス)からクラスオブジェクトを得る必要がある。
  • 解決されるクラスがReloading対象(ABeanクラス)であること。
適当にBeanを用意します。
package wicketReloading.Bean;

import java.io.Serializable;

public class ABean implements Serializable{
	private static final long serialVersionUID = 7295681075951794491L;

	String message = "ABean";

	public String getMessage() {
		return message;
	}
}

Beanを利用するReloading対象外クラスを用意します。
package wicketReloading.excludePackage;

import wicketReloading.Bean.ABean;

public class ExcludeClass {

	public ABean getABean() {
		ABean aBean = new ABean();
		return aBean;
	}
}

Beanを利用するReloading対象クラスを用意します。
package wicketReloading.page;

import org.apache.wicket.PageParameters;
import org.apache.wicket.markup.html.WebPage;
import org.apache.wicket.markup.html.basic.Label;

import wicketReloading.Bean.ABean;
import wicketReloading.excludePackage.ExcludeClass;

public class HomePage extends WebPage {

	private static final long serialVersionUID = 1L;

    public HomePage(final PageParameters parameters) {
    	ABean aBean = new ABean();
    	ExcludeClass excludeClass = new ExcludeClass();
    	Object obj = excludeClass.getABean();
    	aBean = (ABean)obj;
        add(new Label("message", aBean.getMessage()));
    }
}

最後にフィルタを用意します。 設定は以下の通りになるように書きます。
  • ABeanがReloading対象
  • ExcludeClassがReloading対象外
  • HomePageがReloading対象
package wicketReloading;

import org.apache.wicket.application.ReloadingClassLoader;
import org.apache.wicket.protocol.http.ReloadingWicketFilter;

public class MyWicketFilter extends ReloadingWicketFilter {
    static
    {
        ReloadingClassLoader.includePattern("wicketReloading.*");
        //ReloadingClassLoader.excludePattern("wicketReloading.Bean.*");
        ReloadingClassLoader.excludePattern("wicketReloading.excludePackage.*");
        ReloadingClassLoader.excludePattern("wicketReloading.session.*");
    }
}
 実行すると例外が発生し下図のような画面になります。

 フィルタを変更してABeanをReloading対象外にすると、HomePageはレンダリングされるようになります。

終わりに

 「リローディング後に、古いクラスオブジェクトと関連づいたオブジェクトが残る」については、また後日気力があるときにメモします。