Correctly using Spring environment profiles in order to manage PropertySourcesPlaceholderConfigurer and sets of properties files
Asked Answered
C

5

15

I am working on a Spring application and I am realizing that I have an issue with the way I manage my properties. I use Spring environment profiles in order to load my properties and I've recently added more profiles which has made my properties files unmanagable.

The properties files are located in different folders within src/main/resources/META-INF/props/, with eah folder matching a different Spring environment profile.

I have at least 5 profiles now which means I have 5 sub-folders each containing the properties files with the same names but with different values for only some keys.

Here is how it looks:

properties file screen capture

Here is how I've configured my PropertyPlaceholders:

@Configuration
public class PropertyPlaceholderConfiguration {

    @Profile(Profiles.CLOUD)
    static class cloudConfiguration {
        @Bean
        public static PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer() throws IOException {
            PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer = new PropertySourcesPlaceholderConfigurer();
            propertySourcesPlaceholderConfigurer.setIgnoreUnresolvablePlaceholders(Boolean.TRUE);
            propertySourcesPlaceholderConfigurer.setLocations(new PathMatchingResourcePatternResolver().getResources("classpath*:META-INF/props/cloud/*.properties"));
            return propertySourcesPlaceholderConfigurer;
        }
    }

    @Profile(Profiles.DEFAULT)
    static class defaultConfiguration {
        @Bean
        public static PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer() throws IOException {
            PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer = new PropertySourcesPlaceholderConfigurer();
            propertySourcesPlaceholderConfigurer.setIgnoreUnresolvablePlaceholders(Boolean.TRUE);
            propertySourcesPlaceholderConfigurer.setLocations(new PathMatchingResourcePatternResolver().getResources("classpath*:META-INF/props/default/*.properties"));
            return propertySourcesPlaceholderConfigurer;
        }
    }

    @Profile(Profiles.TEST)
    static class testConfiguration {
        @Bean
        public static PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer() throws IOException {
            PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer = new PropertySourcesPlaceholderConfigurer();
            propertySourcesPlaceholderConfigurer.setIgnoreUnresolvablePlaceholders(Boolean.TRUE);
            propertySourcesPlaceholderConfigurer.setLocations(new PathMatchingResourcePatternResolver().getResources("classpath*:META-INF/props/test/*.properties"));
            return propertySourcesPlaceholderConfigurer;
        }
    }

    @Profile(Profiles.DEV)
    static class devConfiguration {
        @Bean
        public static PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer() throws IOException {
            PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer = new PropertySourcesPlaceholderConfigurer();
            propertySourcesPlaceholderConfigurer.setIgnoreUnresolvablePlaceholders(Boolean.TRUE);
            propertySourcesPlaceholderConfigurer.setLocations(new PathMatchingResourcePatternResolver().getResources("classpath*:META-INF/props/dev/*.properties"));
            return propertySourcesPlaceholderConfigurer;
        }
     ...
    }

To sum up, my problem is as follows:

  • key/value pairs are duplicated all over the 5 different folders because only a few values are different.

I am therefore looking for a new strategy to manage the differences between the different environments.

Can anyone please help?

Culex answered 12/5, 2014 at 20:44 Comment(0)
C
5

There are many ways to do this but I think you are in the right path. Overriding of properties files gets done through BeanFactoryPostProcessors, and there's two implementations that can help you in this case so you don't have to do it from scratch:

PropertySourcesPlaceholderConfigurer & PropertyOverrideConfigurer.

This is an example using PropertySourcesPlaceholderConfigurer:

<bean id="someProperties" class="org.springframework.context.support.PropertySourcesPlaceholderConfigurer" abstract="true" >
    <property name="locations">
        <list>
            <value>classpath:database.properties</value>
            <value>classpath:email.properties</value>
        </list>
    </property>
    <property name="ignoreUnresolvablePlaceholders" value="false"/>
</bean>

<bean id="devProperties" parent="someProperties"  >
    <property name="properties" >
        <props >
            <prop key="database.username">Database Username used for Development Environment </prop> 
        </props>
    </property>
    <property name="localOverride" value="true" />
</bean>

<bean id="testProperties" parent="someProperties"  >
    <property name="properties" >
        <props >
            <prop key="database.username">Database Username used for Testing Environment </prop> 
        </props>
    </property>
    <property name="localOverride" value="true" />
</bean>

In that example you load the default properties into a bean that will be used as a template for other beans, and in the specific bean, say TestEnvironmentProperties Bean or DevEnvironmentProperties Bean you override only the specific properties you want to override from the default properties files. The example only shows how to override specific properties without the need to create another properties file, from there you can decide how to choose which bean to create with a factory, a simple facade class or a profiles system, anything that you like and matches your architecture.

Also if you think this option is too verbose you can use the property-placeholder element.

I recommend you this book:

Getting started with Spring Framework, Second Edition

it has just the examples you need in its 5th chapter. I didn't write it or anything, I just bought it some time ago and I loved it after going through so many bad spring books.

Cormac answered 15/8, 2014 at 17:46 Comment(0)
S
2

Pull the common properties into a separate file and specify that plus the profile specific properties as inputs for each profile. Haven't used the Java based Spring config but here's how I do it in XML. Assume you can do the same in code:

<?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">

    <beans profile="default">
        <bean id="applicationPropertiesPlaceholder"
            class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
            <property name="locations">
                <list>
                    <value>classpath:profiles/common.profile.properties</value>
                    <value>classpath:profiles/local.profile.properties</value>
                </list>
            </property>
        </bean>
    </beans>

    <beans profile="local">
        <bean id="applicationPropertiesPlaceholder"
            class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
            <property name="locations">
                <list>
                    <value>classpath:profiles/common.profile.properties</value>
                    <value>classpath:profiles/local.profile.properties</value>
                </list>
            </property>
        </bean>
    </beans>

    <beans profile="trial">
        <bean id="applicationPropertiesPlaceholder"
            class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
            <property name="locations">
                <list>
                    <value>classpath:profiles/common.profile.properties</value>
                    <value>classpath:profiles/trial.profile.properties</value>
                </list>
            </property>
        </bean>
    </beans>

    <beans profile="live">
        <bean id="applicationPropertiesPlaceholder"
            class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
            <property name="locations">
                <list>
                    <value>classpath:profiles/common.profile.properties</value>
                    <value>classpath:profiles/live.profile.properties</value>
                </list>
            </property>
        </bean>
    </beans>

</beans>
Springhalt answered 12/5, 2014 at 20:58 Comment(2)
Thanks Alan! Ummm, the only thing is that your soluction forces one to have unrelated properties in the same file (i.e. common.profile.properties)...Culex
Another issue with the suggested solution is that if for some reason at some point I need different values for one of the properties I need to pull it out from the common file and place it in the profile-specific files.Culex
C
0

I think I stumbled upon the beginning of a solution with this interesting blog post.

To quote from the article:

Beware of redundancy of environment-specific properties. For example, if the solution is to have one property file for each environment (e.g. “db-test.properties”, “db-dev.properties”, etc.), then maintaining these properties can be a bit of a nightmare – if a property “foo” is added, then it would have to be added to the property file for each environment (e.g. DEV, TEST, PROD, etc.). The PropertyOverrideConfigurer is appropriate to eliminate this redundancy, setting the default value in the application context itself, but then the overriding value in a separate file. It’s important, however, to document this well, since it can look a bit “magical” to an unsuspecting maintenance developer who sees one value specified in the context file, but another used at runtime.

The idea is to rely on PropertyOverrideConfigurer and factor out common properties.

Culex answered 13/5, 2014 at 15:48 Comment(0)
D
0

The better practice is to put all of the properties files outside of the WAR packaging. You can use a JNDI variable to point Spring to the physical path where the external properties files can be read. Example:

<jee:jndi-lookup id="externalFileArea" jndi-name="java:comp/env/mgo/externalFileArea"
                     default-value="/opt/external/props" lookup-on-startup="true"/>

<util:properties id="myConf" location="file:#{externalFileArea}/my-conf.properties"/>

<!-- And now, to use an entry from this properties import -->
<bean id="foo" class="foo.bar.com">
     <property name="configParam1" value="#{myConf['fooConfig.param1']}"
</bean>

If on Windows, the the JNDI entry might be specified as /C/Users/someone. Finally, add a file named /opt/external/props/my-conf.properties, and in there place an entry like: fooConfig.param1=true

Wash, rinse, repeat. Far less work, much more secure and much more easy to maintain.

Depressomotor answered 4/7, 2015 at 23:19 Comment(0)
L
0

I would suggest that "common" properties do not need to be in a common file, and instead can be default values of property placeholder in-line in your code. This allows them to be overridden via JVM args (or local env) while not needing to be "managed" in a file. Your environment-specific properties, in your environment-specific files, then indicate just those properties that MUST be provided in each environment for the app to start up. As such they would NOT have default values in placeholders.

Lethargy answered 23/3, 2016 at 12:14 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.