EQUIP2 WebApp Dynamic Reloading Working Notes

Chris Greenhalgh, 2007-02-07

Introduction

Making some of the things in EQUIP2_WebApp_Dynamic_Reloading.html work took quite a lot of munching through Spring and Java stuff. Here are my working notes.

Hopefully, unless things don't work and you want to fix them you can skip this bit :-)

Working notes

Working notes on Spring Context loading

Unless things don't work and you want to fix them you can skip this bit :-)

When the web application is deployed, the web.xml causes an instance of class org.springframework.web.context.ContextLoaderListener. This creates the top-level org.springframework.web.context.ContextLoader instance. This looks for a web.xml context-param 'contextClass' to identify the context class, defaulting to org.springframework.web.context.support.XmlWebApplicationContext. The context-param 'contextConfigLocation' is passed to the new context to identify the (multiple, comma-separated) file paths from which configuration should be loaded.

Each Spring servlet is an instance of class org.springframework.web.servlet.DispatcherServlet. Presumably the container calls void init(ServletConfig config), which gives the dispatcher change to initialise and access to configuration and context information to connect to the main application context. At some point, initialisation calls

	protected WebApplicationContext createWebApplicationContext(WebApplicationContext parent) throws BeansException

From the javadocs, this should: "Instantiate the WebApplicationContext for this servlet, either a default XmlWebApplicationContext or a custom context class if set. This implementation expects custom contexts to implement ConfigurableWebApplicationContext. Can be overridden in subclasses."

The default implementation creates an instanceof the class returned by getContextClass() (which should be configurable as a Servlet init-param), and on that calls:

refresh() is specified in the interface org.springframework.context.ConfigurableApplicationContext as: "Load or refresh the persistent representation of the configuration, which might an XML file, properties file, or relational database schema." This interface also has a close() method which should "Close this application context, releasing all resources and locks that the implementation might hold."

It quickly gets rather tightly interlinked. The BeanFactory houses the definitions of beans, and creates them in response to requests to getBean(String name) The bean factory seems to try and sort out property setting, in turn requiring instantiation of linked beans, etc. Internally, a bean is defined by an object implementing BeanDefinition, most likely org.springframework.beans.factory.support.RootBeanDefinition..

By default, I assume that refresh() should reload the XML to recreate the bean definitions, and then off we go with new instances, etc. Presumably on an Application Context refresh should also update references to configuration classes (e.g. Message resolvers, etc.) held by the context itself.

So none of this will do anything about loading new versions of classes, but will recreate bean networks if...

Note that updating the DispatcherServlet mappings, etc. requires a call to initFrameworkServlet, so the Servlet also needs to know about changes - we have a simple listener callback mechanism for this.

Other incremental options

It might also be nice to have more incremental support for changing only part of a context's bean, e.g. if a property value is set. However this will only work for cases where (e.g.) setting the property to a new value is all that is required. In general, objects may embed lifecycle/configuration assumptions, implicitly or explicitly, and incremental re-configuration may not work.

It might be worth exploring options here in the future, perhaps with (e.g.) Java 1.5 class/property attributes to indicate which change(s) might be supported without a complete redeploy/refresh.

Working Notes on Using Class Loaders

Unless things don't work and you want to fix them you can skip this bit :-)

Java does have the ability to load different versions of the 'same' class (i.e. with the same name) - provided each is loaded by a different Class Loader. Internally, each is a different Class object and a different (incompatible) type, e.g. a reference to one cannot be assigned to the other. Depending on the JVM, unloading of Class Loaders (and their classes) which are no longer used my or may not actually be implemented (i.e. loading new versions of classes may have the old classes and loaders as a memory leak). Class Loaders do not generally unload individual classes that they have loaded, even if they are not currently referenced.

Classes can only pass object between them in terms of class (versions) that they have in common, i.e. from a common 'parent' class-loader.
So, any facility to dynamically load and use new version(s) of a compiled Java class will rely on:

It is (almost??) impossible to do this in a completely general and automatic way. For example, references to old class versions may have 'diffused' to many different parts of the application via shared components such as the dataspace or shared 'game engine' or utility objects.

But it is still (hopefully) possible to do some useful things...

The Spring context refresh facility (used above) suggests that classes used within a single context might be reloaded as part of the refresh process. The single hardest question to resolve is: which classes should be loaded in a class loader that will be unloaded/replaced when the context is refreshed, and which should be loaded in a more persistent class loader? Generally, only classes loaded more persistently will be safe to pass outside the object(s) being recreated by refreshing that portion of the application, e.g. to the dataspace. Also, classes being passed into other libraries will normally (at least up to some interface or superclass) need to be defined in the class loader at or above that used by the corresponding library.

Two main options suggest themselves:

For initial develop let's try explicit specification...

Beans are actually created by the Spring context's bean factory. For the context(s) we are using this is created/reinitialised in the methods getBeanFactory and refreshBeanFactory defined in org.springframework.context.support.AbstractRefreshableApplicationContext, which instantiates the factory by calling:
protected DefaultListableBeanFactory createBeanFactory() {
        return new DefaultListableBeanFactory(getInternalParentBeanFactory());
}

This default  factory class, org.springframework.beans.factory.support.DefaultListableBeanFactory, calls getBean() defined in AbstractBeanFactory to get actual bean references. Note that BeanDefinition subclasses include actual Class references; not sure where they come from yet! The instances is created by createBean in AbstractAutowireCapableBeanFactory. This uses an InstatiationStrategy, by default CglibSubclassingInstantiationStrategy.

XML context files are read by an org.springframework.beans.factory.xml.XmlBeanDefinitionReader in concert with an XmlBeanDefinitionParser, by default DefaultXmlBeanDefinitionParser. At least once this calls:

BeanDefinitionReaderUtils.createBeanDefinition(	className, parent, cargs, pvs, getBeanDefinitionReader().getBeanClassLoader());
and
ClassUtils.forName(typeClassName, this.beanDefinitionReader.getBeanClassLoader());

getBeanClassLoader is implemented in AbstractBeanDefinitionReader, and defaults to ClassUtils.getDefaultClassLoader().

org.springframework.util.ClassUtils, at the end of the day (hanlding primitives & arrays) calls:

Class.forName(name, true, classLoader);

So...we need to provide our own custom class loader to be used by the BeanDefinitionReader used by our custom Spring context, and we need to kick it or replace it on a refresh. So we override initBeanDefinitionReader() and provide our own class loader, equip2.webapptutorial.reloading.ReloadableClassLoader. The classes to be handled specially are defined by the context property reloadableClasses, which is set in turn by the ReloadableDispatcherServlet from an init-param of the same name.

Another option to explore at some point is replacement of classes by dynamically generated proxies, so that the proxied object might be replaced while running, and avoiding the problem (to some extent) of references leaking into other code (but you would still have to tidy up any 'this' references handed out by the object itself, e.g. to listeners - and don't forget that non-static member classes have reference to the containing class).

Changes

2007-02-07