EQUIP2 WebApp Dynamic Reloading

Chris Greenhalgh, 2007-02-07

Introduction

During Java web application development on a local machine it can be frustrating and time-consuming to have to restart the web application after each file update for each code/test iteration. This document explores some options for speeding this up:

Using unit tests

If you develop using unit tests (running outside the web container) then the compile/test cycle is typically shorter anyway - see EQUIP2_WebApp_Unit_Testing.html.

Other web applications

You can speed up the restart time of your development web server (e.g. Tomcat) by undeploying any web applications that you are not currently working on.

You can also use the Tomcat manager pages to restart only the web application of interest (although this may eventually run out of memory).

EQUIP logging

The normal configuration of the EQUIP2 dataspace logs all dataspace actions. The logger also snapshots the dataspace on start-up, which can take quite a long time for a large dataspace (10s of seconds). For development you may wish to disable dataspace logging, saving this time on startup - comment out the logger constructor argument to the persistent dataspace bean in WEB-INF/applicationContext.xml:
  <bean id="dataspace" class="equip2.persist.hibernate.j2se.PersistentDataspace" destroy-method="shutdown">
<!-- no logging <constructor-arg><ref bean="dataspaceLogger"/></constructor-arg> -->
</bean>

JSPs

Most J2EE servlet containers (certainly Tomcat) will detect that JSPs have changed and re-generate, recompile and remap to the changed version. This is one important reason why view development in JSP is preferred to developing views in Servlets or other standard Java classes.

Server scripting

If part(s) of the server logic are written in a scripting language then (assuming the scripting system is not cacheing the scripts for too long) this elements should be updatable by a simple redeploy.

There are some examples of using the Jakarta Bean Scripting Framework (BSF) to script (for example) Spring controllers in EQUIP2_WebApp_Scripting_Support.html. Those examples show script loading from the web application file system; in other applications the script fragments might actually be stored in the database and provided as part of the game authoring and/or run-time evolution.

Message bundles

Spring recommends that messages from controllers (e.g. form validation failure messages) be resolved via message resolvers in the Spring dispatcher context. Spring provides a message resolver, org.springframework.context.support.ReloadableResourceBundleMessageSource, which uses a properties file to resolve messages but will check if that file has been changed at a configurable interval and update itself accordingly, e.g.:

   <bean id="messageSource" class="org.springframework.context.support.ReloadableResourceBundleMessageSource">
<property name="basename"><value>WEB-INF/classes/reloading</value></property>
<property name="cacheSeconds"><value>2</value></property>
</bean>

Note that, unlike the ResourceBundleMessageSource, the basename should be a path within the web application. There is some suggestion in the documentation that a resource in the classes directory might be cached by the container - if you find the above example does not update when the file is changed then move the properties bundle up the WEB-INF directory (works for me at the moment).

Spring Context XML files

This is rather experimental, but...

I have created a subclass of the normal Spring Dispatcher Servlet, ,equip2.webapptutorial.reloading.ReloadableDispatcherServlet (based on Spring 1.2.8), which works together with a subclass of the normal Spring XML Web application context, equip2.webapptutorial.reloading.ReloadableXmlWebApplicationContext, to poll the filesystem for changes to the application context's main XML configuration file, and to refresh the context and reinitialise the dispatcher servlet if it changes. Note that this will create completely new instances of the bean specified in that servlet's configuration.

The new servlet is configured in web.xml as below (for example):

  <servlet>
<servlet-name>reloading</servlet-name>
<servlet-class>equip2.webapptutorial.reloading.ReloadableDispatcherServlet</servlet-class>
<init-param>
<description>config-reloading poll interval</description>
<param-name>cacheSeconds</param-name>
<param-value>1</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>

The cacheSeconds parameter is the time between configuration file polls, in seconds. Values <=0 disable reloading/refreshing.

Note that it currently only checks the last modified time of the servlet's directly specified configuration file(s) - in this case reloading-servlet.xml - it won't check for modifications of other configuration files, e.g. which are included in that(those) file(s).

Refreshing the context causes destructor-type methods to be called on hosted beans - if you want to exploit this capability then you must ensure that these are provided where appropriate, e.g. dataspace listeners must be unregistered or those changes will get handled by both the old and the new bean. E.g. this testing bean has its 'shutdown' method called on the old bean when a refresh/reload occurs:

	<bean id="reloadTest" class="equip2.webapptutorial.reloading.ReloadTestBean" 
destroy-method="shutdown"/>

Because Java uses garbage collection there is no way to 'force' beans to cease to exist, but the Spring context will release its own references to them. So it is pretty easy to have big memory leaks if you do this much. I.e. you'll still need to restart Tomcat reasonable often - if stange things start to fail then try that first.

The same reloading application context can also be used as the top-level application context. The (untested) configuration in applicationContext.xml should be:

  <context-param>
<param-name>contextClass</param-name>
<param-value>equip2.webapptutorial.reloading.ReloadableXmlWebApplicationContext</param-value>
</context-param>

Note that there is currently no way to specify a non-default poll interval (property cacheSeconds) to the top-level context; this would require a custom Context Loader and Context Loader Listener (work-around: change the value in the source file and recompile).

For lots of details see Working_notes_on_Spring_Context_loading

Java Classes

The Java Virtual Machine does not support the modification of loaded classes while running, therefore this is the hardest area to support.

New Class Loading

The first time a class is referenced it is loaded into the virtual machine. Consequently it is possible to add completely new classes to a running application, and when they are subsequently referenced they will be loaded. This referencing/loading might be done by a newly updated JSP or by an updated Spring context configuration (if using reloading, as described above).

Custom Servlet Class re-loading

The reloading servlet/context described above has also been extended to have its own (reloadable=replacable) class loader. If there are classes which are are only directly used within that particular servlet, and which can be suitably recreated by refreshing the context (as described above) then you can also configure them to be reloaded when the context is refreshed.

In web.xml the additional servlet init-param 'reloadableClasses' specifies a comma-separated list of class and/or pacakge names, optionally with a trailing '*' indicating a wildcard match. For example, the following configuration will give special handling to all classes in the equip2.webapptutorial.reloading package (if they were not already loaded before this point):

  <servlet>
<servlet-name>reloading</servlet-name>
<servlet-class>equip2.webapptutorial.reloading.ReloadableDispatcherServlet</servlet-class>
<init-param>
<description>config-reloading poll interval</description>
<param-name>cacheSeconds</param-name>
<param-value>1</param-value>
</init-param>
<init-param>
<description>class names/patterns to be loaded reloadably by this servlet only</description>
<param-name>reloadableClasses</param-name>
<param-value>equip2.webapptutorial.reloading.*</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>

The ReloadableDispatcherServlet pushes this value to the ReloadableXmlWebApplicationContext, which in turn creates a configures a custom Java class loader, equip2.webapptutorial.reloading.ReloadableClassLoader. This class loader will try to load itself (not delegate to parent) any class matching the pattern. If the context is reloaded then the old class loader (and classes!) is abandoned and a new instance is created and used to refresh the context; this will cause the class definitions to be loaded afresh from the disk.

The ReloadableXmlWebApplicationContext asks the custom class loader to check the most recent last modified time of the files which it would load each time it checks the servlet context defininition. With a lot of class files, this could introduce a fair load. If one is seen to have changed then the context refreshes.

Note that the custom class loaded will (currently) only load .class files directly from disk (not from a JAR), and uses the root directory of the package hierarchy in which it finds its own class as the only hierarchy from which it will load. This should probably be fixed at some point :-)

For lots of details, see Working_Notes_on_Using_Class_Loaders

As with context refreshing generally, because Java uses garbage collection there is no way to 'force' beans to cease to exist, but the Spring context will release its own references to them. Anecdotal evidence is that JVMs are also historically poor at unloading Class loaders and classes that are no longer used. So it is pretty easy to have big memory leaks if you do this much. I.e. you'll still need to restart Tomcat reasonable often - if stange things start to fail then try that first.

Changes

2007-02-07