Spring Framework and ScheduledExecutorService - Load property files during startup


Hi, I am Malathi Boggavarapu working at Volvo Group and i live in Gothenburg, Sweden. I have been working on Java since several years and had vast experience and knowledge across various technologies.

In this post we see how to load property files during the application startup in an efficient way using Spring framework and ExecutorService which allows you to reload properties without server restart if any properties were changed.

So let's get started.

How to load Property files using Spring


Every application is loaded using the deployment descriptor file web.xml. So in web.xml we need to declare the DispatcherServlet class with load-on-startup as 1. servlet-name can be anything. In our example it is called as bl (business layer). So during application startup, server tries to find the
bl-servlet.xml file.

web.xml

<servlet>
    <servlet-name>bl</servlet-name>
    <servlet-class>org.springframework,web.servlet.DispatcherServlet</servlet-class>
   <load-on-startup>1</load-on-startup>
</servlet>


bl-servlet.xml

This xml file is Spring beans xml file which will be loaded during application or server startup. So all the classes that were defined in the xml file will be loaded.

<bean class="com.wirelesscar.dynafleet.common.impl.PropertyLoader">
        <property name="resources">
            <list>
                <value>abc.properties</value>
                <value>api.properties</value>             
            </list>
        </property>
    </bean>


PropertyLoader.java.


  1. If a method is annotated with @PostContruct, that method is called to initialize the class. Only one method can be annotated with this in a given class. 
  2. We are scheduling the class using ScheduledExecutorService which schedules to load the properties file every minute so that changes to the property file could be reloaded with out server restart.
  3. All the properties will be loaded to System properties using one line of code System.getProperties().load(is) (See below code).


import java.io.InputStream;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

import javax.annotation.PostConstruct;

import org.apache.log4j.Logger;

public class PropertyLoader {
    private final static Logger log = Logger.getLogger(PropertyLoader.class);

    private final ScheduledExecutorService executor = Executors.newScheduledThreadPool(1);
    private List<String> resources = new ArrayList<>();

    public void setResources(List<String> resources) {
        this.resources = resources;
    }

    @PostConstruct
    public void initialize() {
        for (String resource : resources) {
            URL url = Thread.currentThread().getContextClassLoader().getResource(resource);
            if (url == null) {
                log.warn("could not find resource=" + resource);
            }
            else {
                Runnable command = new MonitorTask(url);
                executor.scheduleAtFixedRate(command, 60, 60, TimeUnit.SECONDS);
            }
        }
    }

    private final static class MonitorTask implements Runnable {
        private final URL resource;
        private long timestamp = -1L;

        public MonitorTask(URL resource) {
            this.resource = resource;

            log.info("File monitoring task created for resource=" + resource);
            run(); // ensure that we load the resource right away...
        }

        @Override
        public void run() {
            if (isOutOfDate()) {
                load();
            }
        }

        private boolean isOutOfDate() {
            try {
                long ts = resource.openConnection().getLastModified();
                log.debug("timestamp=" + ts + " of resource=" + resource);

                boolean outOfDate = ts > timestamp;
                timestamp = ts;
                return outOfDate;
            }
            catch (Exception e) {
                log.warn("failed out of date check for resource=" + resource, e);
            }
            return true;
        }

        private void load() {
            log.info("reloading resource=" + resource);
            try (InputStream is = resource.openConnection().getInputStream()) {
                System.getProperties().load(is);
            }
            catch (Exception e) {
                log.warn("failed to load resource=" + resource, e);
            }
        }
    }
}


Accessing the System properties


1) Create an interface class called Configuration.

import java.util.EnumSet;
import java.util.List;
import java.util.Map;

public interface Configuration
{
    /**
     * Returns a configuration string matching the given key.
     * @param key the name of the value
     * @return the configured value
     */
    String get(String key);

    /**
     * Returns a configuration string matching the given key. If no value matched the
     * given key, the supplied defaultvalue is returned.
     * @param key the name of the value
     * @param def the value to return if no value matched the key
     * @return the configured value of the supplied defaultvalue.
     */
    String get(String key, String def);

    /**
     * Returns a List of Strings by splitting the property value on comma.
     *
     * @param key the property file key to find the comma-separated list
     * @param configDefault the default value to use if the key isn't found in the properties file
     * @return a <code>List</code> of <code>String</code> values derived from the property value
     */
    List<String> getList(String key, String configDefault);

    /**
     * Returns an EnumSet by splitting the property value on comma and converting to the specified Enum.
     *
     * @param key the property file key to find the comma-separated list
     * @param clazz the enum class to convert to
     * @return an <code>EnumSet</code> of enums of the specified type
     */
    <E extends Enum<E>> EnumSet<E> getEnumSet(String key, Class<E> clazz);

    /**
     * Returns a configuration string matching the given key.
     * @param key the name of the value
     * @return the configured value
     */
    Byte getByte(String key);

    /**
     * Returns a configuration string matching the given key. If no value matched the
     * given key, the supplied defaultvalue is returned.
     * @param key the name of the value
     * @param def the value to return if no value matched the key
     * @return the configured value of the supplied defaultvalue.
     */
    Byte getByte(String key, Byte def);

    /**
     * Returns a configuration string matching the given key.
     * @param key the name of the value
     * @return the configured value
     */
    Short getShort(String key);

    /**
     * Returns a configuration string matching the given key. If no value matched the
     * given key, the supplied defaultvalue is returned.
     * @param key the name of the value
     * @param def the value to return if no value matched the key
     * @return the configured value of the supplied defaultvalue.
     */
    Short getShort(String key, Short def);

    /**
     * Returns a configuration string matching the given key.
     * @param key the name of the value
     * @return the configured value
     */
    Integer getInteger(String key);

    /**
     * Returns a configuration string matching the given key. If no value matched the
     * given key, the supplied defaultvalue is returned.
     * @param key the name of the value
     * @param def the value to return if no value matched the key
     * @return the configured value of the supplied defaultvalue.
     */
    Integer getInteger(String key, Integer def);

    /**
     * Returns a configuration string matching the given key.
     * @param key the name of the value
     * @return the configured value
     */
    Long getLong(String key);

    /**
     * Returns a configuration string matching the given key. If no value matched the
     * given key, the supplied defaultvalue is returned.
     * @param key the name of the value
     * @param def the value to return if no value matched the key
     * @return the configured value of the supplied defaultvalue.
     */
    Long getLong(String key, Long def);

    Long getLong(String key, String fallbackKey, Long def);

    Map<String, String> getMap(String key, String configDefault);

    /**
     * Returns a configuration string matching the given key.
     * @param key the name of the value
     * @return the configured value
     */
    Boolean getBoolean(String key);

    /**
     * Returns a configuration string matching the given key. If no value matched the
     * given key, the supplied defaultvalue is returned.
     * @param key the name of the value
     * @param def the value to return if no value matched the key
     * @return the configured value of the supplied defaultvalue.
     */
    Boolean getBoolean(String key, Boolean def);

    /**
     * Returns a configuration string matching the given key.
     * @param key the name of the value
     * @return the configured value
     */
    Float getFloat(String key);

    /**
     * Returns a configuration string matching the given key. If no value matched the
     * given key, the supplied defaultvalue is returned.
     * @param key the name of the value
     * @param def the value to return if no value matched the key
     * @return the configured value of the supplied defaultvalue.
     */
    Float getFloat(String key, Float def);

    /**
     * Returns a configuration string matching the given key.
     * @param key the name of the value
     * @return the configured value
     */
    Double getDouble(String key);

    /**
     * Returns a configuration string matching the given key. If no value matched the
     * given key, the supplied defaultvalue is returned.
     * @param key the name of the value
     * @param def the value to return if no value matched the key
     * @return the configured value of the supplied defaultvalue.
     */
    Double getDouble(String key, Double def);

}

2) Create an implementation class for the interface.

import java.util.*;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

import org.apache.commons.lang3.StringUtils;

public class SystemPropertiesConfiguration implements Configuration {

    SystemPropertiesConfiguration() {
        // package private
    }

    /**
     * Returns a configuration string matching the given key.
     * @param key the name of the value
     * @return the configured value
     */
    @Override
    public String get(String key) {
        return System.getProperty(key);
    }

    /**
     * Returns a configuration string matching the given key. If no value matched the
     * given key, the supplied defaultvalue is returned.
     * @param key the name of the value
     * @param def the value to return if no value matched the key
     * @return the configured value of the supplied defaultvalue.
     */
    @Override
    public String get(String key, String def) {
        String value = get(key);

        if (value == null) {
            value = def;
        }

        return value;
    }

    /**
     * Returns a List of Strings by splitting the value on comma.
     *
     * @param key the property file key to find the comma-separated list
     * @param configDefault the default value to use if the key isn't found in the properties file
     * @return a <code>List</code> of <code>String</code> values derived from the property value
     */
    @Override
    public List<String> getList(String key, String configDefault) {
        String configString = get(key, configDefault);
        if (StringUtils.isBlank(configString)) {
            return new ArrayList<String>();
        }

        return Arrays.asList(configString.trim().split("\\s*,\\s*"));
    }
    /**
     * Returns a Map of Strings by splitting key and values on comma and then on semi colon.
     *
     * @param key the property file key to find the comma-separated list of keys and values
     *            Example of a key: 1:foo,2:boo
     * @param configDefault the default value to use if the key isn't found in the properties file
     * @return a <code>Map</code> of <code>String</code> keys-values derived from the property value
     */
    @Override
    public Map<String, String> getMap(String key, String configDefault) {
        String configString = get(key, configDefault);
        if (StringUtils.isBlank(configString)) {
            return new HashMap<>();
        }

        if (!(Pattern.compile("[^0-9a-zA-z,:/-]").matcher(configString).find())) {
            return Arrays.stream(configString.split(","))
                    .map(s -> s.split(":"))
                    .collect(Collectors.toMap(e -> e[0], e -> e[1]));
        }
        else {
            return new HashMap<>();
        }

    }

    /**
     * Returns a configured value as a Boolean.
     *
     * @param key the name of the value
     * @return the configured value
     */
    @Override
    public Boolean getBoolean(String key) {
        Boolean result = null;

        String value = get(key);

        if (value != null) {
            String lowerCaseValue = value.trim().toLowerCase();

            if (lowerCaseValue.equals("true") || lowerCaseValue.equals("yes") || lowerCaseValue.equals("on")) {
                result = Boolean.TRUE;
            }
            else if (lowerCaseValue.equals("false") || lowerCaseValue.equals("no") || lowerCaseValue.equals("off")) {
                result = Boolean.FALSE;
            }
        }

        return result;
    }

    /**
     * Returns a configured value or a default value as a Boolean.
     *
     * @param key the name of the value
     * @param def the value to return if no value matched the key
     * @return the configured value of the supplied defaultvalue.
     */
    @Override
    public Boolean getBoolean(String key, Boolean def) {
        Boolean value = getBoolean(key);

        if (value == null) {
            value = def;
        }

        return value;
    }

    /**
     * Returns a configured value as an Integer.
     *
     * @param key the name of the value
     * @return the configured value
     */
    @Override
    public Integer getInteger(String key) {
        Integer result = null;

        String value = get(key);

        if (value != null) {
            try {
                result = new Integer(value);
            }
            catch (NumberFormatException e) {
                result = null;
            }
        }

        return result;
    }

    /**
     * Returns a configured value or a default value as an Integer.
     *
     * @param key the name of the value
     * @param def the value to return if no value matched the key
     * @return the configured value of the supplied defaultvalue.
     */
    @Override
    public Integer getInteger(String key, Integer def) {
        Integer value = getInteger(key);

        if (value == null) {
            value = def;
        }

        return value;
    }

    /**
     * Returns a configured value as a Byte.
     *
     * @param key the name of the value
     * @return the configured value
     */
    @Override
    public Byte getByte(String key) {
        Byte result = null;

        String value = get(key);

        if (value != null) {
            try {
                result = new Byte(value);
            }
            catch (NumberFormatException e) {
                result = null;
            }
        }

        return result;
    }

    /**
     * Returns a configured value or a default value as a Byte.
     *
     * @param key the name of the value
     * @param def the value to return if no value matched the key
     * @return the configured value of the supplied defaultvalue.
     */
    @Override
    public Byte getByte(String key, Byte def) {
        Byte value = getByte(key);

        if (value == null) {
            value = def;
        }

        return value;
    }

    /**
     * Returns a configured value as a Short.
     *
     * @param key the name of the value
     * @return the configured value
     */
    @Override
    public Short getShort(String key) {
        Short result = null;

        String value = get(key);

        if (value != null) {
            try {
                result = new Short(value);
            }
            catch (NumberFormatException e) {
                result = null;
            }
        }

        return result;
    }

    /**
     * Returns a configured value or a default value as a Short.
     *
     * @param key the name of the value
     * @param def the value to return if no value matched the key
     * @return the configured value of the supplied defaultvalue.
     */
    @Override
    public Short getShort(String key, Short def) {
        Short value = getShort(key);

        if (value == null) {
            value = def;
        }

        return value;
    }

    /**
     * Returns a configured value as a Long.
     *
     * @param key the name of the value
     * @return the configured value
     */
    @Override
    public Long getLong(String key) {
        Long result = null;

        String value = get(key);

        if (value != null) {
            try {
                result = new Long(value);
            }
            catch (NumberFormatException e) {
                result = null;
            }
        }

        return result;
    }

    /**
     * Returns a configured value or a default value as a Long.
     *
     * @param key the name of the value
     * @param def the value to return if no value matched the key
     * @return the configured value of the supplied defaultvalue.
     */
    @Override
    public Long getLong(String key, Long def) {
        Long value = getLong(key);

        if (value == null) {
            value = def;
        }

        return value;
    }

    /**
     * Returns a configured value or a default value as a Long.
     *
     * @param key the name of the value1
     * @param fallbackKey the name of the value2
     * @param def the value to return if no value matched the key
     * @return the configured value of the supplied defaultvalue.
     */
    @Override
    public Long getLong(String key, String fallbackKey, Long def) {
        Long value = getLong(key);

        if (value == null) {
            value = getLong(fallbackKey);
        }

        if (value == null) {
            value = def;
        }

        return value;
    }

    /**
     * Returns a configured value as a Float.
     *
     * @param key the name of the value
     * @return the configured value
     */
    @Override
    public Float getFloat(String key) {
        Float result = null;

        String value = get(key);

        if (value != null) {
            try {
                result = new Float(value);
            }
            catch (NumberFormatException e) {
                result = null;
            }
        }

        return result;
    }

    /**
     * Returns a configured value or a default value as a Float.
     *
     * @param key the name of the value
     * @param def the value to return if no value matched the key
     * @return the configured value of the supplied defaultvalue.
     */
    @Override
    public Float getFloat(String key, Float def) {
        Float value = getFloat(key);

        if (value == null) {
            value = def;
        }

        return value;
    }

    /**
     * Returns a configured value as a Double.
     *
     * @param key the name of the value
     * @return the configured value
     */
    @Override
    public Double getDouble(String key) {
        Double result = null;

        String value = get(key);

        if (value != null) {
            try {
                result = new Double(value);
            }
            catch (NumberFormatException e) {
                result = null;
            }
        }

        return result;
    }

    /**
     * Returns a configured value or a default value as a Double.
     *
     * @param key the name of the value
     * @param def the value to return if no value matched the key
     * @return the configured value of the supplied defaultvalue.
     */
    @Override
    public Double getDouble(String key, Double def) {
        Double value = getDouble(key);

        if (value == null) {
            value = def;
        }

        return value;
    }

    @Override
    public <E extends Enum<E>> EnumSet<E> getEnumSet(String key, Class<E> clazz) {
        EnumSet<E> set = EnumSet.noneOf(clazz);
        List<String> value = getList(key, "");
        for (String string : value) {
            set.add(Enum.valueOf(clazz, string));
        }
        return set;
    }

}

Hope the post is helpful. Please post your comments.

Comments

Popular posts from this blog

Bash - Execute Pl/Sql script from Shell script

Gradle Fundamentals

Load Balancing using Spring Cloud Netflix Ribbon