前回*1の続き。
原因が分かった。
Wicket1.4のFilter#initは
public void init(FilterConfig filterConfig) throws ServletException
{
initIgnorePaths(filterConfig);
this.filterConfig = filterConfig;
String filterMapping = filterConfig.getInitParameter(WicketFilter.FILTER_MAPPING_PARAM);
if (SERVLET_PATH_HOLDER.equals(filterMapping))
{
servletMode = true;
}
final ClassLoader previousClassLoader = Thread.currentThread().getContextClassLoader();
final ClassLoader newClassLoader = getClassLoader();
try
{
if (previousClassLoader != newClassLoader)
{
Thread.currentThread().setContextClassLoader(newClassLoader);
}
...
webApplicationFactory = getApplicationFactory();
// Construct WebApplication subclass
webApplication = webApplicationFactory.createApplication(this);
となっている。
setContextClassLoaderを呼び出した後にcreateApplicationでWebApplicationを生成しているのがポイント。
ところがWicket1.5 rc2では
public void init(final boolean isServlet, final FilterConfig filterConfig) throws ServletException { this.filterConfig = filterConfig; applicationFactory = getApplicationFactory(); application = applicationFactory.createApplication(this); application.setName(filterConfig.getFilterName()); application.setWicketFilter(this); ... final ClassLoader previousClassLoader = Thread.currentThread().getContextClassLoader(); final ClassLoader newClassLoader = getClassLoader(); ThreadContext.setApplication(application); try { if (previousClassLoader != newClassLoader) { Thread.currentThread().setContextClassLoader(newClassLoader); } application.initApplication();
となっている。
バグか仕様かは分からないが、WebApplicationを生成してからsetContextClassLoaderを呼び出している。
こうなると、WebApplicationはReloadingClassLoaderではなく、デフォルトのクラスローダーが使われる。
で、mountPageやトップページが動かなかったのは
@Override public Class<? extends Page> getHomePage() { return TopPage.class; } @Override protected void init(){ super.init(); mountPage("/guest", GuestPage.class); }
と書いていたからで、WebApplicationがデフォルトのクラスローダーで生成されるなら、このファイルに書いたTopPageやGuestPageもデフォルトのクラスローダーで生成される。
で、ReloadingClassLoaderからもこれらのクラスが生成されるとClassCastExceptionになる。または、構造上は継承関係にあってもクラスローダーが違うと
else if (!clazz.isAssignableFrom(container.getClass())) { throw new WicketRuntimeException("Parameter clazz must be an instance of " + container.getClass().getName() + ", but is a " + clazz.getName()); }
ここで継承関係ではないと判断されて例外が発生する。
まぁ結局のところ、S2WicketFilterでsuper.init()を呼び出す前に自分でsetContextClassLoaderを呼び出してしまえばいい。
String contextKey = filterConfig.getFilterName();
WebApplication webApplication = (WebApplication) Application.get(contextKey);
if (webApplication != null){
// WARM DEPLOY の場合に applicationKeyToApplication.removeを呼び出すため
webApplication.internalDestroy();
}
Thread.currentThread().setContextClassLoader(getClassLoader());
super.init(isServlet, filterConfig);
あと自動リロード時にApplicationが既にあるというエラーが出るので破棄しておく。こっちはちょっと自信ない。