You are viewing a plain text version of this content. The canonical link for it is here.
Posted to users@wicket.apache.org by Carlos Pita <ca...@gmail.com> on 2008/02/20 06:44:12 UTC

Reloading on demand (a different approach to reloading)

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: users-unsubscribe@wicket.apache.org
For additional commands, e-mail: users-help@wicket.apache.org