Hi all, I have worked out a reloading classloader (loosely inspired by Jean Baptiste's one) that reloads classes upon entering of an http request, instead of monitoring and reloading on saved changes. There are 2 observations that motivated me to implement this variant:
1) the current reloading classloader does a considerable amount of unnecessary work reloading every tiny change. Normally I make more than one change to one or more files before refreshing the browser page to see the results. And I tend to hit ctrl-s multiple times even while staying in the same file to refresh ide assistance information. This is perceived as a "loop" of reloadings in the reloading classloader log output and use to be slow, just as if the reloader is cycling out of control. Sometimes it's faster to restart the app. 2) the last change to code occurs immediately before refreshing the browser page. So there's no significant idle time before the last source code edition and the following page refresh, that is, no idle time that a monitoring background reloader could take advantage of. You will have to wait a bit in both cases to see the changes taking effect. So on-demand reloading will reload all changes since last request, dropping one classloader and reinitializing the app just once between request and request. Of course, if no change occurs in the middle reloading won't take place. On the overall this will be faster and less resource intensive, and there is no perceivable latency added by the on-demand approach (due to (2)). Moreover, the changed resource scanning step that happens on >every< request represents no appreciable overhead. In practice I've been using this for a while with results that confirm the above. I will like to wait a bit more before firmly concluding anything, but if your are interested below is the untested, unreliable code, which is quite simple in fact: public class ExampleServlet extends WicketServlet { @Override protected WicketFilter newWicketFilter() { return new WicketReloadingFilter() {{ watch("com\\.livra\\.(cereza|common)\\.web.*"); // <-- patterns are regexps ignore("com\\.livra\\.cereza\\.web.CerezaClient"); }}; } } --------------------------------- Filter that uses the reloader ------------------------------------------------ public class WicketReloadingFilter extends WicketFilter { private ClassReloader classReloader; private FilterConfig filterConfig; public WicketReloadingFilter() { classReloader = new ClassReloader(getClass().getClassLoader()); } public void ignore(String pattern) { classReloader.ignore(pattern); } public void watch(String pattern) { classReloader.watch(pattern); } @Override public void init(FilterConfig filterConfig) throws ServletException { this.filterConfig = filterConfig; super.init(filterConfig); } @Override public void doGet(HttpServletRequest servletRequest, HttpServletResponse servletResponse) throws ServletException, IOException { if (classReloader.hasChanges()) { classReloader = classReloader.clone(); try { super.init(filterConfig); } catch (ServletException e) { throw new RuntimeException(e); } } super.doGet(servletRequest, servletResponse); } @Override protected ClassLoader getClassLoader() { return classReloader; } } --------------------------------- Reloader itself ------------------------------------------------ public class ClassReloader extends URLClassLoader { @SuppressWarnings("unused") private static Logger logger = Logger.getLogger(ClassReloader.class); private List<Pattern> watchPatterns = new ArrayList<Pattern>(); private List<Pattern> ignorePatterns = new ArrayList<Pattern>(); private long lastReload; private Set<Class<?>> loadedClasses = new HashSet<Class<?>>(); public ClassReloader(ClassLoader parent) { super(new URL[] {}, parent); lastReload = new Date().getTime(); Enumeration<URL> resources; try { resources = parent.getResources(""); } catch (IOException exception) { throw new RuntimeException(exception); } while (resources.hasMoreElements()) { addURL(resources.nextElement()); } } public void watch(String pattern) { watchPatterns.add(Pattern.compile(pattern)); } public void ignore(String pattern) { ignorePatterns.add(Pattern.compile(pattern)); } @Override public Class<?> loadClass(String name) throws ClassNotFoundException { Class<?> clazz = findLoadedClass(name); if (clazz == null && mustWatch(name)) { try { clazz = super.findClass(name); loadedClasses.add(clazz); } catch (ClassNotFoundException exception) {} } if (clazz == null && getParent() != null) { clazz = getParent().loadClass(name); } if (clazz == null) throw new ClassNotFoundException(name); return clazz; } public boolean hasChanges() { for (Class<?> clazz : loadedClasses) { String classRelativeFile = clazz.getName().replaceAll("\\.", "/") +".class"; for (URL url : getURLs()) { File classAbsoluteFile = new File(url.getFile() + classRelativeFile); if (classAbsoluteFile.exists() && classAbsoluteFile.lastModified() > lastReload) { logger.debug(clazz.getCanonicalName() + " changed"); return true; } } } return false; } public ClassReloader clone() { ClassReloader clone = new ClassReloader(getParent()); clone.ignorePatterns = ignorePatterns; clone.watchPatterns = watchPatterns; return clone; } private boolean mustWatch(String name) { for (Pattern pattern : watchPatterns) { if (pattern.matcher(name).matches()) { for (Pattern pattern_ : ignorePatterns) { if (pattern_.matcher(name).matches()) return false; } return true; } } return false; } } --------------------------------------------------------------------- To unsubscribe, e-mail: [EMAIL PROTECTED] For additional commands, e-mail: [EMAIL PROTECTED]