Skip to content

Spring Hot Deployer

Nicholas Whitehead edited this page Mar 12, 2013 · 13 revisions

The core of APMRouter is managed by Spring. At startup, the APMRouter main accepts an array of directory names which will be recursuvely searched for file names matching "*.apmrouter.xml". All located files will be used to initialize the APMRouter's root context. A simple example of this is:

java org.helios.apmrouter.APMRouter ./src/test/resources/server

If the class SpringHotDeployer is configured in the root context, the spring hot deployer will be activated.

Configuration of SpringHotDeployer

The simplest deployment of the hot deployer is the bean itself with no properties.

Initial Deployment

On startup, (that is, once the root context has started) the hot deployer will scan the configured "hot" directories looking for file names matching "*.apmrouter.xml". (It will also look for deployment "apps", a construct described below). For each file found, the following occurs:

  1. A new Spring context is created and initialized using the discovered file.
  2. The context display name and id are set to the discovered file name to give the context provenance.
  3. The root context is set as the parent context.
  4. The context is started.

Since the hot deployed context's parent is the root context, it has visibility of all the root's beans, so the hot deployed "*.apmrouter.xml" file can inject or reference beans defined in the root, and beans can be looked up by name in the hot deployed context's ApplicationContext. However, the root context never has visibility of the child context beans. (This is not entirely true, since all deployed contexts are exposed through JMX, hot deployed ones transiently, so in theory, some thread of activity in the root context could access the child context through the JMX interface).

This is what the root and hot deployed contexts look like in JConsole:

Spring Contexts Viewed In JConsole

Hot Deployed Watcher

Once the startup process is complete, the SpringHotDeployer starts a Java 7 File Watcher on the hot directories looking for changes in the "*.apmrouter.xml" files memebrship. Events on these files are handled as follows:

  • New File Located: The file is hot deployed as described above.
  • Existing file deleted: The associated hot deployed context is stopped and destroyed. The context's JMX interface is unregistered.
  • Existing file updated: The associated hot deployed context is stopped and destroyed. The context's JMX interface is unregistered. The file is then hot deployed as described above. (Basically, it's a DELETE event followed by a NEW event).

The Java 7 File Watcher provides excellent "freshness" for the hot deployer (i.e. we don't have to wait around for a polling process to notice a change) but it does present some challenges. For example, if a new file is copied into the hot directory, the watcher actually detects this as a NEW file event, followed by a MODIFIED file event. (I have observed this behaviour in Linux, Windows and Solaris so until I learn otherwise, I am assuming this is the galactical standard). In order to smooth out the activities executed as a result of file changes, the file events are placed into a delay-queue for 500 ms. and events for the same logical file are conflated while queued.

Hot Deployer Configuration

The configurable aspects of the deployer are as follows:

  • setHotDirNames(Set) : The names of the "hot" directories that the deployer will watch for new, modified or deleted deployments. If not defined, the hot directory defaults to ${user.home}/.apmrouter/hotdir. The default hotdir can be disabled using setDisableDefaultHotDir(true).
  • setPattern(String): Overrides the pattern of files the hot deployer will hot deploy. The default is "*.apmrouter.xml".
  • setDisableHotDirApps(boolean) : Enables or disables hot deployed apps. (See Hot Deployed Apps below). Default is enabled.
  • setDisableHotDirLibs(boolean) : Enables or disables classloading of libraries and wars in hot deployed apps. (See Hot Deployed Apps below). Default is enabled.
  • setHotDeployAppDirectoryExt(String) : Sets the extension of hot deployed app directories. (See Hot Deployed Apps below). Default is .app.

Hot Deployed Apps

A hot deployed app is a group of resources that is hot deployed as a single context. Rather than hooking into a single file, an app is hooked into a directory in a hot dir, by default named "*.app". Useful aspects of an app are as follows:

  • Multiple "*.apmrouter.xml" files in the .app directory will be hot deployed as a single context.
  • Any jars found in a subdirectory named XXX.lib of an XXX.app directory will be classloaded into the hot deployed context. These hot-loaded jars will be isolated from the root context and only visible to the hot-deployed context.
  • Any wars found in a subdirectory named XXX.lib of an XXX.app directory will be initialized and started as web-apps. This functionality is currently limited in that the app's spring "*.apmrouter.xml" files must define the base Jetty constructs in order to support the war deployments. This is on the roadmap to address and the goal is to have the Jetty constructs automatically spun up if a war needs to be deployed.

Another limitation which has not yet been addressed is that hot-deployed context classpaths cannot override the root context classpath. hot-deployed context classpath is comprised of a URLClassLoader containing the app's jars, with the root context classloader as the parent, so if the parent classpath already contains a specific class, the hot-deployed context will always use this version, even if the app contains a different ersion in it's lib directory. For example, if the root classpath contains log4j version A, and the app's lib directory contains log4j version B, the latter will be ignored.

Example App File Structure

The following outlines an app called jolokia.app which hot deploys the Jolokia JMX WAR Agent and a MongoDB data browser web app called mViewer which requires JSP support (hence all the extra jars).

A sample app dir structure

The xml descriptor containing the Jetty constructs is as follows: (note that the reference ref="MBeanServer" is a reference to an MBeanServer bean defined in the root context)

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xmlns:p="http://www.springframework.org/schema/p"
        xmlns:util="http://www.springframework.org/schema/util"
        xsi:schemaLocation="http://www.springframework.org/schema/beans
           http://www.springframework.org/schema/beans/spring-beans-3.1.xsd
           http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-3.1.xsd">

      <bean id="JettyThreadPool" class="org.eclipse.jetty.util.thread.QueuedThreadPool"  >
        <property name="minThreads" value="10"/>
        <property name="maxThreads" value="50"/>
        <property name="daemon" value="true"/>
      </bean>

      <bean id="HttpConnector" class="org.eclipse.jetty.server.nio.SelectChannelConnector" >
        <property name="port" value="8162"/>
        <property name="statsOn" value="true" />
        <property name="host" value="0.0.0.0" ></property>
      </bean>

        <bean name="HttpServer" class="org.eclipse.jetty.server.Server"
                lazy-init="false" destroy-method="stop" init-method="start">
           <property name="connectors">
              <list>
                <ref bean="HttpConnector" />
              </list>
            </property>
      <property name="handler" ref="Handlers"/>
            <property name="threadPool" ref="JettyThreadPool"/>
        </bean>


        <util:list id="JettyConfigs" >
                <bean id="JettyWebXml" class="org.eclipse.jetty.webapp.WebXmlConfiguration" />
        </util:list>


		  <bean id="Handlers" class="org.eclipse.jetty.server.handler.HandlerCollection"  depends-on="HttpServer">
		    <property name="handlers">
		      <list>
		      </list>
		    </property>
		  </bean>

        <bean id="JettyJMXExporter" class="org.springframework.jmx.export.MBeanExporter">
                <property name="beans">
                        <map>
                                <entry key="org.helios.apmrouter.jetty:service=ThreadPool" value-ref="JettyThreadPool" />
                                <entry key="org.helios.apmrouter.jetty:service=HttpConnector" value-ref="HttpConnector" />
                                <entry key="org.helios.apmrouter.jetty:service=HttpServer" value-ref="HttpServer" />
                                <entry key="org.helios.apmrouter.jetty:service=Handlers" value-ref="Handlers" />
                                <!-- <entry key="org.helios.jmx:service=JMXConnectorServer,protocol=iiop" value-ref="IIOPMBeanServerConnector" /> -->
                        </map>
                </property>
                <property name="server" ref="MBeanServer" />
        </bean>

</beans>

Clone this wiki locally