Chris Greenhalgh, 2007-02-07
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:
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).
<bean id="dataspace" class="equip2.persist.hibernate.j2se.PersistentDataspace" destroy-method="shutdown">
<!-- no logging <constructor-arg><ref bean="dataspaceLogger"/></constructor-arg> -->
</bean>
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.
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).
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
The Java Virtual Machine does not support the modification of loaded classes while running, therefore this is the hardest area to support.
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).
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.