ReloadingClassLoaderがクラスファイル読んでくれないことがある件
はじめに
org.apache.wicket.protocol.http.ReloadingWicketFilterを使うと、クラスファイルの変更を監視してくれます。
そしてReloadingWicketFilterを使用した際に使われるクラスローダがorg.apache.wicket.application.ReloadingClassLoaderです。
で、ReloadingClassLoaderはクラスファイルのパスに空白があると、ファイルを監視対象にしてくれないのです。
実験方法
- eclipseのworkspaceを空白が含まれるパスに配置します。
- wicketで何か書きます。
- ReloadingWicketFilterを継承したフィルタを作ります。
- web.xmlを編集して作成したフィルタを使用するようにします。
- log4j.propertiesにDEBUGレベルを指定します。
- eclipse上からTomcatを起動してログを観察します。
log4j.propertiesはたとえば以下のような感じ。冗長な気がしますが、まあ、それはそれということで。
log4j.appender.Stdout=org.apache.log4j.ConsoleAppender log4j.appender.Stdout.layout=org.apache.log4j.PatternLayout log4j.appender.Stdout.layout.conversionPattern=%-5p - %-26.26c{1} - %m\n log4j.rootLogger=DEBUG,Stdout log4j.logger.org.apache.wicket=DEBUG log4j.logger.org.apache.wicket.protocol.http.HttpSessionStore=DEBUG log4j.logger.org.apache.wicket.version=DEBUG log4j.logger.org.apache.wicket.RequestCycle=DEBUG
以下はReloadingWicketFilterを継承したフィルタの例
public class MyWicketFilter extends ReloadingWicketFilter { static { ReloadingClassLoader.includePattern("wicketReloading.*"); } }
実験結果
ログを観察すると、どうやら読み込みに失敗していることが分かります。
DEBUG - ReloadingClassLoader - clzLocation=/C:/Documents%20and%20Settings/seko/workspace/wicketReloading/WEB-INF/classes/wicketReloading/WicketApplication.class DEBUG - ReloadingClassLoader - Class file does not exist: C:\Documents%20and%20Settings\seko\workspace\wicketReloading\WEB-INF\classes\wicketReloading\WicketApplication.class DEBUG - ReloadingClassLoader - clzLocation=/C:/eclipse3/servers/tomcat-5.5/shared/classes/wicketReloading/WicketApplication.class DEBUG - ReloadingClassLoader - Class file does not exist: C:\eclipse3\servers\tomcat-5.5\shared\classes\wicketReloading\WicketApplication.class DEBUG - ReloadingClassLoader - clzLocation=/C:/eclipse3/servers/tomcat-5.5/common/classes/wicketReloading/WicketApplication.class DEBUG - ReloadingClassLoader - Class file does not exist: C:\eclipse3\servers\tomcat-5.5\common\classes\wicketReloading\WicketApplication.class DEBUG - ReloadingClassLoader - Could not locate class wicketReloading.WicketApplication
ちゃんとクラスファイルを監視対象に出来た場合のログは、以下のような感じ。
DEBUG - ReloadingClassLoader - clzLocation=/C:/eclipse3/servers/tomcat-5.5/shared/classes/wicketReloading/WicketApplication.class DEBUG - ReloadingClassLoader - Class file does not exist: C:\eclipse3\servers\tomcat-5.5\shared\classes\wicketReloading\WicketApplication.class DEBUG - ReloadingClassLoader - clzLocation=/C:/workspace/wicketReloading/WEB-INF/classes/wicketReloading/WicketApplication.class INFO - ReloadingClassLoader - Watching changes of class C:\workspace\wicketReloading\WEB-INF\classes\wicketReloading\WicketApplication.class
でも、ReloadingClassLoaderがクラスファイルを監視対象にすることに失敗してもアプリは動きます
ReloadingClassLoader.loadClassメソッドは以下のようになっております。
クラスファイルを監視対象に出来なかった場合は、watchForModificationsメソッドが実行されなかった場合、またはwatchForModificationsメソッドの実行中にクラスファイルを発見できなかった場合です。
で、クラスローダがファイルを読み込む方法とwatchForModificationsメソッドがファイルを読み込む方法は異なるようで、クラスファイルを読み込めてもwatchForModificationsメソッドはファイルを発見できなかったりするという。
public final Class loadClass(String name, boolean resolve) throws ClassNotFoundException { // First check if it's already loaded Class clazz = findLoadedClass(name); if (clazz == null) { final ClassLoader parent = getParent(); if (tryClassHere(name)) { try { clazz = findClass(name); watchForModifications(clazz); } catch (ClassNotFoundException cnfe) { if (parent == null) { // Propagate exception throw cnfe; } } } if (clazz == null) { if (parent == null) { throw new ClassNotFoundException(name); } else { // Will throw a CFNE if not found in parent clazz = parent.loadClass(name); } } } if (resolve) { resolveClass(clazz); } return clazz; }
では、workspaseを示すパスに空白が入っている場合、ソースをコンパイルするたびにTomcatの再起動が必要かというと実はそうでもなくて。
Tomcatの場合、クラスローダが更新を認識して読み込んでくれたりすることもあるようです。どういう状況下で読み込まれるのかは調べてません。