Spring Properties Reloaded

Some time ago, I promised I would describe how to make spring configuration properties reloadable.

When using a standard spring PropertyPlaceholderConfigurer, properties will be read from a file, and their values can be referenced using a ${…} macro syntax. The expanded property values are usually assigned to bean properties in the xml application context.

Now what I would like to have is this: When the file changes, the properties should be read again, and the updated values should be assigned to the original beans’ properties. The standard Spring answer would be “shut down the application context and launch a new one”, but often we can be much more flexible without disrupting operation (e.g. changing the size of a cache etc.)

I came up with the following design goals:

  • Syntax and usage should be as close as possible to the non-reloading standard spring variant
  • Not all placeholders should be dynamic – some bean properties cannot meaningfully be changed at runtime, and the configuration should make this appearant.
  • The same conversions should take place during initial configuration, and when a property is reloaded
  • No extra demon threads – the XML configuration should control when files are checked for updates
  • Spring best practices: programming to interfaces, testability.
  • Singleton beans are enough – there’s no point in reconfiguring instances of templates.

As an example, let’s take a bean whose “cachesize” property is configured using a placeholder “my.cache.size” taken from a property file “config.properties”. The standard spring way to describe this looks as follows:

<bean id="configproperties"
      class="org.springframework.beans.factory.config.PropertiesFactoryBean">
  <property name="location" value="file:config.properties"/>
</bean>

<bean id="propertyConfigurer"
     class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
  <property name="properties" ref="configproperties"/>
</bean>

<bean id="mybean" class="net.wuenschenswert.spring.example.MyBean">
  <property name="cachesize" value="${my.cache.size}"/>
</bean>

The dynamic variant looks very similar, with only some class names changed, and a slightly different placeholder syntax:

<bean id="configproperties"
      class="net.wuenschenswert.spring.ReloadablePropertiesFactoryBean">
  <property name="location" value="file:config.properties"/>
</bean>

<bean id="propertyConfigurer"
      class="net.wuenschenswert.spring.ReloadingPropertyPlaceholderConfigurer">
  <property name="properties" ref="configproperties"/>
</bean>

<bean id="mybean" class="net.wuenschenswert.spring.example.MyBean">
  <property name="cachesize" value="#{my.cache.size}"/>
</bean>

<!-- regularly reload property files. -->
<bean id="timer" class="org.springframework.scheduling.timer.TimerFactoryBean">
   ...(see complete file for details)...
<bean>

If you’re interested – just dive into the code, or download the jar. Thanks to my colleagues at coremedia for discussion and insights – the hacking is all mine, so I take the blame.

I used Spring 1.2.8 plus commons-logging for development (don’t know about licensing, so I’ll rather not bundle the binaries). Try running the example, change the config file and observe the log output. Whoa.

java -cp spring-reloaded.jar:spring.jar:commons-logging.jar   \\
          net.wuenschenswert.spring.example.Main

Implementation

How does it work? In order to get dynamically reloaded properties, we need the following ingredients:

  • a factory bean that detects file system changes
  • an observer pattern for Properties, so that file system changes can be propagated
  • a property placeholder configurer that remembers where which placeholders were used, and updates singleton beans’ properties
  • a timer that triggers the regular check for changed files

The observer pattern is implemented by the interfaces and classes ReloadableProperties, ReloadablePropertiesListener, PropertiesReloadedEvent, and ReloadablePropertiesBase. None of them are especially exciting, just normal listener handling. The class DelegatingProperties serves to transparently exchange the current properties when properties are updated. We only update the whole property map at once, so that the application can avoid inconsistent intermediate states (more on this later).

Now the ReloadablePropertiesFactoryBean can be written to create a ReloadableProperties instance (instead of a Properties instance, as the PropertiesFactoryBean does). When prompted to do so, the RPFB checks file modification times, and if necessary, updates its ReloadableProperties. This triggers the observer pattern machinery.

In our case, the only listener is the ReloadingPropertyPlaceholderConfigurer. It behaves just like a standard spring PropertyPlaceholderConfigurer, except that it tracks all usages of placeholders. Now when properties are reloaded, all usages of each modified property are found, and the properties of those singleton beans are assigned again.

If it doesn’t work for you, I’d love to read your comments. If it does, even more so!

19 thoughts on “Spring Properties Reloaded”

  1. As I’ve already told torsten – for other readers that stumble upon this page:

    Yes, it is definitely possible to use that code, that’s what I put it online for. I’m eventually aiming to get some of it into the Spring distribution, so for the time being, just pretend my code has the least restrictive license that will allow that.

    I have just patched the code to be Spring 2.0.5 compatible (the signature of an overridden PropertyPlaceholderConfigurer method has changed). Release forthcoming.

    UPDATE: released.

  2. Did not know we could use runtime express (eg. ${my.cache.size})
    in spring config file.

    Recently I had to support dynamic runtime properties. I would have saved lot of time if I had found this article.

    Thanks

  3. Why Im getting this error?

    StandardContext[/halo]Initializing Spring root WebApplicationContext
    [:halo] 2007-07-13 14:37:57,721 INFO ReloadablePropertiesFactoryBean.loadProperties – Loading properties file from class path resource [halo/resources/monitoring/jamon.properties]
    [:halo] 2007-07-13 14:37:59,784 ERROR ContextLoader.initWebApplicationContext – Context initialization failed
    java.lang.NoSuchMethodError: net.wuenschenswert.spring.DefaultPropertyPlaceholderConfigurer.parseStringValue(Ljava/lang/String;Ljava/util/Properties;Ljava/lang/String;)Ljava/lang/String;
    at net.wuenschenswert.spring.ReloadingPropertyPlaceholderConfigurer.parseStringValue(ReloadingPropertyPlaceholderConfigurer.java:121)
    at net.wuenschenswert.spring.ReloadingPropertyPlaceholderConfigurer$PlaceholderResolvingBeanDefinitionVisitor.resolveStringValue(ReloadingPropertyPlaceholderConfigurer.java:378)
    at org.springframework.beans.factory.config.BeanDefinitionVisitor.visitBeanClassName(BeanDefinitionVisitor.java:68)
    at org.springframework.beans.factory.config.BeanDefinitionVisitor.visitBeanDefinition(BeanDefinitionVisitor.java:57)
    at net.wuenschenswert.spring.ReloadingPropertyPlaceholderConfigurer.processProperties(ReloadingPropertyPlaceholderConfigurer.java:326)
    at org.springframework.beans.factory.config.PropertyResourceConfigurer.postProcessBeanFactory(PropertyResourceConfigurer.java:75)
    at org.springframework.context.support.AbstractApplicationContext.invokeBeanFactoryPostProcessors(AbstractApplicationContext.java:437)
    at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:342)
    at org.springframework.web.context.ContextLoader.createWebApplicationContext(ContextLoader.java:241)
    at org.springframework.web.context.ContextLoader.initWebApplicationContext(ContextLoader.java:184)
    at org.springframework.web.context.ContextLoaderListener.contextInitialized(ContextLoaderListener.java:49)
    at halo.webapp.listener.StartupListener.contextInitialized(StartupListener.java:55)
    at org.apache.catalina.core.StandardContext.listenerStart(StandardContext.java:3827)
    at org.apache.catalina.core.StandardContext.start(StandardContext.java:4343)
    at org.apache.catalina.core.ContainerBase.start(ContainerBase.java:1083)
    at org.apache.catalina.core.StandardHost.start(StandardHost.java:789)
    at org.apache.catalina.core.ContainerBase.start(ContainerBase.java:1083)
    at org.apache.catalina.core.StandardEngine.start(StandardEngine.java:478)

  4. I am getting this error…

    391 2007-10-25 21:15:36,078 [ExecuteThread: ‘4’ for queue: ‘weblogic.kernel.System’] ERROR org.springframework.web.context.ContextLoader – Context initialization failed
    java.lang.IllegalArgumentException: Scope must not be null
    at org.springframework.util.Assert.notNull(Assert.java:112)
    at org.springframework.beans.factory.support.AbstractBeanDefinition.setScope(AbstractBeanDefinition.java:341)
    at org.springframework.beans.factory.config.BeanDefinitionVisitor.visitScope(BeanDefinitionVisitor.java:80)
    at org.springframework.beans.factory.config.BeanDefinitionVisitor.visitBeanDefinition(BeanDefinitionVisitor.java:58)
    at net.wuenschenswert.spring.ReloadingPropertyPlaceholderConfigurer.processProperties(ReloadingPropertyPlaceholderConfigurer.java:336)
    at org.springframework.beans.factory.config.PropertyResourceConfigurer.postProcessBeanFactory(PropertyResourceConfigurer.java:75)
    at org.springframework.context.support.AbstractApplicationContext.invokeBeanFactoryPostProcessors(AbstractApplicationContext.java:467)
    at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:334)
    at org.springframework.web.context.ContextLoader.createWebApplicationContext(ContextLoader.java:244)
    at org.springframework.web.context.ContextLoader.initWebApplicationContext(ContextLoader.java:187)
    at org.springframework.web.context.ContextLoaderListener.contextInitialized(ContextLoaderListener.java:49)
    at weblogic.servlet.internal.WebAppServletContext$FireContextListenerAction.run(WebAppServletContext.java:7110)
    at weblogic.security.acl.internal.AuthenticatedSubject.doAs(AuthenticatedSubject.java:321)
    at weblogic.security.service.SecurityManager.runAs(SecurityManager.java:121)
    at weblogic.servlet.internal.WebAppServletContext.notifyCreated(WebAppServletContext.java:1757)
    at weblogic.servlet.internal.WebAppServletContext.preloadResources(WebAppServletContext.java:3378)
    at weblogic.servlet.internal.WebAppServletContext.setStarted(WebAppServletContext.java:6262)
    at weblogic.servlet.internal.WebAppModule.start(WebAppModule.java:892)
    at weblogic.j2ee.J2EEApplicationContainer.start(J2EEApplicationContainer.java:2181)
    at weblogic.j2ee.J2EEApplicationContainer.activate(J2EEApplicationContainer.java:2222)
    at weblogic.management.deploy.slave.SlaveDeployer$ComponentActivateTask.activateContainer(SlaveDeployer.java:2521)
    at weblogic.management.deploy.slave.SlaveDeployer$ActivateTask.doCommit(SlaveDeployer.java:2439)
    at weblogic.management.deploy.slave.SlaveDeployer$Task.commit(SlaveDeployer.java:2156)
    at weblogic.management.deploy.slave.SlaveDeployer$Task.checkAutoCommit(SlaveDeployer.java:2255)
    at weblogic.management.deploy.slave.SlaveDeployer$Task.prepare(SlaveDeployer.java:2150)
    at weblogic.management.deploy.slave.SlaveDeployer$ActivateTask.prepare(SlaveDeployer.java:2402)
    at weblogic.management.deploy.slave.SlaveDeployer.processPrepareTask(SlaveDeployer.java:884)
    at weblogic.management.deploy.slave.SlaveDeployer.prepareDelta(SlaveDeployer.java:592)
    at weblogic.management.deploy.slave.SlaveDeployer.prepareUpdate(SlaveDeployer.java:501)
    at weblogic.drs.internal.SlaveCallbackHandler$1.execute(SlaveCallbackHandler.java:25)
    at weblogic.kernel.ExecuteThread.execute(ExecuteThread.java:224)
    at weblogic.kernel.ExecuteThread.run(ExecuteThread.java:183)

  5. Hi, has anyone had difficulties when reloading or properties?

    It seems that the latest version of spring reloaded cant handle this. The two scenarios are quite different.

    1. it looks like the reloader is trying to assign one of the individual property values to the actual map property.

    2. no exceptions are thrown here but the list property is re populated with only the property value that was changed. For example if you started with a list of 12 items, after the reload you only have one item

    Would be greatful if anyone could help me out here

  6. I made a mistake in my last post of including tags and the site has stripped them. Sorry it was meant to read

    Hi, has anyone had difficulties when reloading map or list properties?

    It seems that the latest version of spring reloaded cant handle this. The two scenarios are quite different.

    1. map. it looks like the reloader is trying to assign one of the individual property values to the actual map property.

    2. list. no exceptions are thrown here but the list property is re populated with only the property value that was changed. For example if you started with a list of 12 items, after the reload you only have one item

  7. True, the functionality is currently limited: It works well as long as you just have string properties of named singleton beans containing a placeholder. For more complex properties, more complex update code or an even more core-level solution would be required. Handling map and list should be feasible, I think, but the impact of value conversion isn’t quite clear to me.

    This stuff requires a bit more code reading and thinking.

    I recently noticed that properties of inner beans (a bean element contained in a property element) are not reloadable either, because Spring (at least Spring 2) ignores the name of an inner singleton bean. That’s always fixable (just move the bean outside and use a ref), but unfortunate.

  8. We have property file entries for maxlength and min length.

    In many cases these keys are not used in the application.

    Is there any way of finding out these keys,.

  9. More complex cases like maps, list etc. would be simpler if the reloadable implementation is done with proxies instead of an observer. For example, if a “#{…}” is found anywhere in the application context, you can inject a proxy there that retrieves the current property value from the FactoryBean (or from a ReloadableMessageSource, for all I care).

  10. michael: true, but that only works if the injected beans are used through some kind of interface – it will not work for injecting an integer, which is afterwards used for all kinds of computation. A good example is dimension of cache sizes -the cache implementation wouldn’t want to go through a service interface every time it needs to determine whether the cache it full, and it will probably use the cache size for allocating resources (e.g. internal arrays) up front. So reacting to a changed configuration will involve some code implemented in the setter (push style / callback style).

  11. Thanks, it is well designed and well coded, but it can be rewritten according to spring 2.5 spec.
    If I managed that I will let you know it… 🙂

  12. I have one properties file having the value
    totalNumberOfRows=100
    if the user changed the value from 100 to 200.It should work accordingly without restatring the server. For that i need to write the spring listener.Do i use the above code for my work.

    Suggest me some simplest thing to complete this. 🙂

  13. Very interesting!

    How do you handle threading and synchronization issue? Your update will most likely occur in a thread that is different from those reading the value. Unless the property’s backing field is volatile (for simple value) or Atomic (for complex values), the reading thread can potentially never see the new value, or see a completely different value than the one you are setting!?

    Thanks.

  14. Good observation – the bean’s code will actually have to handle synchronization itself. That is, if you want your bean’s configuration properties to be changeable during operation, the new value has to be communicated from the thread observing the properties file to the bean instance doing the actual work. So you will have to use a volatile or synchronized keyword at some point.

    Using the ReconfigurationAware interface, you can even do this for multiple properties at once, by first storing the new values in a temporary place and then copying the values in one synchronized block in the afterReconfiguration callback.

Comments are closed.