Chris Greenhalgh, 2007-02-07
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 :-)
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...
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.
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:
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.