S2WicketがWicket1.5の自動リロードonで動いた
前回*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が既にあるというエラーが出るので破棄しておく。こっちはちょっと自信ない。