Dynamically loading properties file using Spring

2019-01-19 06:57发布

问题:

I have written a PropertyUtils class (from internet), which will load the Properties

<bean id="propertiesUtil" class="com.myApp.PropertiesUtil" >
    <property name="locations">
        <list>
            <value>classpath:myApp/myApp.properties</value>         
        </list>
    </property>
</bean>

and a PropertiesUtil class is like below

public class PropertiesUtil extends PropertyPlaceholderConfigurer {

    private static Map<String, String> properties = new HashMap<String, String>();

    @Override
    protected void loadProperties(final Properties props) throws IOException {
        super.loadProperties(props);
        for (final Object key : props.keySet()) {
            properties.put((String) key, props.getProperty((String) key));
        }
    }

    public String getProperty(final String name) {
        return properties.get(name);
    }

}

Later, I can get the property by calling PropertiesUtil.getProperty() method.

But now I want to modify it slightly such that, If the myApp.properties get modified/changed by user, it should be loaded again

Probably I need FileWatcher class

public abstract class FileWatcher extends TimerTask {
    private long timeStamp;
    private File file;

    public FileWatcher(File file) {
        this.file = file;
        this.timeStamp = file.lastModified();
    }

    @Override
    public final void run() {
        long timeStampNew = this.file.lastModified();

        if (this.timeStamp != timeStampNew) {
            this.timeStamp = timeStampNew;
            onChange(this.file);
        }
    }

    protected abstract void onChange(File newFile);
}

but my doubts are

  1. How do I create File object using classpath:myApp/myApp.properties (because absolute path is not known)
  2. How do I invoke/call spring to load/pass the new myApp.properties to PropetisUtil class [in onChange method].

回答1:

You can use Spring's ReloadableResourceBundleMessageSource class like below:

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

    <bean id="myProperties" class="org.springframework.context.support.ReloadableResourceBundleMessageSource">
        <!-- check property file(s) every 1 second -->
        <property name="cacheSeconds" value="1"/>
        <property name="basenames">
            <list>
                <value>myApp/myApp</value>
            </list>
        </property>
    </bean>

</beans>

And then you can call MessageSource.getMessage() method to get the property value. Here is an example:

package com.example;

import org.springframework.context.ApplicationContext;
import org.springframework.context.MessageSource;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class MyApp {
public static void main(String[] args) throws InterruptedException {
        ApplicationContext ctx = new ClassPathXmlApplicationContext("myApp/MyApp.xml");
        MessageSource myProperties = (MessageSource) ctx.getBean("myProperties");
        while (true) {
            System.out.println(myProperties.getMessage("myApp.propertyOne", null, null));
            Thread.sleep(1000);
        }
    }
} 

You can directly call ApplicationContext.getMessage(String code, Object[] args, Locale locale) if you renamed your 'myProperties' bean as 'messageSource'.

For your web apps, please put your property files outside of your web app's classpath (because the web server might cache them). For instance, WEB-INF/conf/myApp.properties



回答2:

The following supports what you are looking for:

https://commons.apache.org/proper/commons-configuration/userguide/howto_properties.html

PropertiesConfiguration config = new PropertiesConfiguration("usergui.properties");
config.setReloadingStrategy(new FileChangedReloadingStrategy());

This should do the trick. I haven't used it myself but it seems to be pretty straightforward.



回答3:

May be the simpler way would be to do like this:

<util:properties location="classpath:config.properties" id="configProperties" />
<context:property-placeholder properties-ref="configProperties"  />

This way you can access your properties from any bean by injecting/accessing bean with a name "configProperties". Not sure if this is what you need, because it is not possible to modify configProperties at runtime. But this is a clean way and you do not need to write any additional class.