Search This Blog

Thursday, 5 July 2012

Reloadable MessageSources

As we saw in the previous post, the ResourceBundleMessageSource uses underlying JDK's ResourceBundle implementation. The java.util.ResourceBundle loads locale specific files or bundles only once. It caches loaded bundles forever: Reloading a bundle during program execution is not possible. As this MessageSource relies on ResourceBundle, it faces the same limitation.So we are stuck with the same set of values. Spring provides us with an alternative implementation - ReloadableResourceBundleMessageSource.
Consider the bean implementation for the same.
<bean id="messageSource"
        class="org.springframework.context.support.ReloadableResourceBundleMessageSource">
         <property name="basenames">
      <list>
        <value>classpath:standard</value>
        <value>com/resources/failure</value>        
      </list>
    </property>
     <property name="cacheSeconds" value="1"/> 
</bean>
The above bean implementation includes a "cacheSeconds" property that tells the MessageSource when to reload the bundles. A value of -1 implies that it will be cached forever.
The basenames property is also slightly more advanced than the   ResourceBundleMessageSource. Using it we can refer to any Spring resource location and not just classpath resources. In case we need to use classpath resources, a "classpath:" prefix can be used. So standard.properties is loaded from the classpath.
I wrote the below code to test the working of the MessageSource:
static String FILE_NAME = "<eclipse workspace path>"
        + "Spring - I18N\\bin\\com\\resources" + "\\failure.properties";

public static void main(String[] args) throws InterruptedException {
    ApplicationContext applicationContext = new ClassPathXmlApplicationContext(
        "reloadable-messages.xml");
    System.out.println(applicationContext.getMessage("argument.required",
        new Object[] { "Application Context" }, "Required", null));
    System.out.println("Updating the properties file");
    FileOutputStream fos = null;
    try {
        fos = new FileOutputStream(FILE_NAME);
        fos.write("argument.required=The \''{0}\'' argument is A NEW VALUE."
            .getBytes("UTF-8"));
        fos.close();
    } catch (IOException e) {
        e.printStackTrace();
    }
    System.out.println("going to sleep for 10 secs while the bundles are reloaded");
    Thread.sleep(10 * 1000);
    System.out.println(applicationContext.getMessage("argument.required",
        new Object[] { "Application Context" }, "Required", null));
}
I read the property and then rewrote the message value. I then let the main thread sleep for ten seconds. After this on executing the code:
The 'Application Context' argument is required.
Updating the properties file
going to sleep for 10 secs while the bundles are reloaded
The 'Application Context' argument is A NEW VALUE.
As can be seen, the message loaded was different at both times and this was achieved without restarting the program. I was able to reproduce the same with the static message.
static String FILE_NAME = "D:\\work\\Spring\\Workspace\\Spring ws\\"
    + "Spring - I18N\\bin" + "\\standard.properties";

public static void main(String[] args) throws InterruptedException {
    ApplicationContext applicationContext = new ClassPathXmlApplicationContext(
            "reloadable-messages.xml");
    System.out.println(applicationContext.getMessage("rockMessage",
            null, "Required", null));
    System.out.println("Updating the properties file");
    FileOutputStream fos = null;
    try {
        fos = new FileOutputStream(FILE_NAME);
        fos.write("rockMessage=Alligators never rock ever"
            .getBytes("UTF-8"));
        fos.close();
    } catch (IOException e) {
        e.printStackTrace();
    }
    System.out.println("going to sleep for 10 secs while the bundles are reloaded");
        Thread.sleep(10 * 1000);
    System.out.println(applicationContext.getMessage("rockMessage",
        null, "Required", null));
}
The output indicates that the change was loaded:
Alligators rock!
Updating the properties file
going to sleep for 10 secs while the bundles are reloaded
Alligators never rock ever
The other way to reload the bundles is to programmatically clear the cached bundles.
public static void clearByProg(final ApplicationContext applicationContext) {
     ReloadableResourceBundleMessageSource res = (ReloadableResourceBundleMessageSource) 
                  applicationContext.getBean("messageSource");
     res.clearCache();    
}

5 comments:

  1. Thank you, I was wondering how to programatically create a working ReloadableResourceBundleMessageSource bean (Spring seems to hate good constructors) and this helps very well.

    I was trying to (prematurely...) use this to improve performance instead of reloading a Properties file (using Spring PropertiesLoaderUtils.loadProperties) on each request.

    In my case when the cache is cleared and I request a property it takes about 3.5ms as opposed to only 0.5ms to reload the properties file each time a property is requested. (The second time you request a property it does take about 0.01ms).

    So whether or not the ReloadableResourceBundleMessageSource actually improves performance depends on the use case.

    In my use case it did not because I use it for reading only 3 properties on a user interaction.

    Of course in this case if performance is really important you could also make a refresh option for the user and programmatically refresh. Then the ReloadableResourceBundleMessageSource will always outperform reloading the file on each request.

    ReplyDelete
  2. Hi Henro,
    Your use case is interesting. While ReloadableResourceBundleMessageSource is useful, it has a performance overhead that you will have to keep in mind. In your case as you have only three properties (assuming this number remains small) a reload will cause all your message bundles to be loaded again.

    ReplyDelete
  3. Hi Robin,

    I have tried the same example as yours but it is not working as expected. I think ReloadableResourceBundleMessageSource is not able to reload the bundles stored in the classpath.

    ReplyDelete
    Replies
    1. Hi Jinesh,
      Class path resources work. we have been using it in our Spring projects for quite some time. Can you check within your war/ compiled directory if the resource is present and at the correct location ?

      Delete
  4. Very Useful. For more examples visit http://answersz.com

    ReplyDelete