Wicketのホットデプロイの範囲指定の罠(前編)
はじめに
ホットデプロイを利用する際は、ReloadingClassLoader.includePatternメソッドを使用して範囲を指定します。
私は最初「単にホットデプロイしたい対象のクラスが含まれるように、includePatternを指定すればいいのかな」と思っていました。
しかし、「ホットデプロイしたいクラス以外についても、ReloadingClassLoaderとWebappClassLoader(Tomcatの場合)のどちらに読ませるかを意識しないと、トラブルが発生するのでは?」ということに思い至ったのでメモです。
書いていたら長くなりそうでしたので、今回のメモの範囲は「WebappClassLoaderがRealoding対象クラスを読み込もうとすることがある件」です(後述)。
トラブルの原因
トラブルの原因となるのは、私が気づいた範囲では以下の二つです。
原因 | トラブル顕在化のタイミング | トラブル内容 | ||
---|---|---|---|---|
WebappClassLoaderがRealoding対象クラスを読み込もうとすることがある | Reloging発生前から顕在化 | ホットデプロイ有効時のみ例外が送出される | ||
リローディング後に、古いクラスオブジェクトと関連づいたオブジェクトが残る | Reloging発生後に顕在化 | 同上 |
上記のいずれも根本原因は下記の一つです。
- クラスローダが異なると、同一名のクラスでも別クラス扱いされる
WebappClassLoaderがRealoding対象クラスを読み込もうとすることがある件
どうして発生するのか
どうして、includePatternを無視してWebappClassLoaderが読み込んでしまうことがあるのかについてですが、それはクラスがクラス名を解決する際に「自分自身を読み込んだクラスローダに解決を依頼する」という仕様があるためです。
自分自身をどのクラスローダが読み込んだかによって、クラスの読み込み方は下図の2パターンが存在することになります。
すなわちWebappClassLoaderによって読み込まれたクラスは、読み込み対象がincludePatternに含まれていようが含まれていまいが、WebappClassLoaderに読み込みを依頼します。
RelodingClassLoaderが読み込んだクラスと、WebappClassLoaderが読み込んだクラスは名前が同じでも別物扱いされるので、型変換や、クラスロードのリンクフェーズで例外が発生することになります。
実験方法
下図のクラス図が概要となります。ポイントは下記の通りです。
- Reloading対象のクラス(HomePageクラス)と、Reloding対象外のクラス(ExcludeClassクラス)が、あるクラス名(ABeanクラス)からクラスオブジェクトを得る必要がある。
- 解決されるクラスがReloading対象(ABeanクラス)であること。
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はレンダリングされるようになります。