Nested Property Placeholders in Spring Configuration
- Using Java Properties in Spring Configuration - the PropertyPlaceholderConfigurer
- Using Nested Java Properties in Spring Configuration - nested Place Holders
- More Nested Java Properties in Spring Configuration
- Caveat for using PropertyPlaceholderConfigurer
- Data Source vs JNDI
- Appendix
Using Java Properties in Spring Configuration - the PropertyPlaceholderConfigurer
In your Spring configuration (Spring 2.5), it is possible to use property place-holders that get replaced with values from a properties file. For example, consider the Spring configuration below in which I define a POJO (plain old Java object) called testBean and a PropertyPlaceholderConfigurer called placeholderConfig.
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd"> <beans> <bean id="placeholderConfig" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer"> <property name="location" value="classpath:application.properties" /> </bean> <bean name="testBean" class="my.Test"> <property name="environmentName"> <value>${environmentSpecificName}</value> </property> </bean> </beans>
Note that the placeholderConfig bean has a location property whose value is the name of a properties file (application.properties
) that is expected to appear on the class path. PropertyPlaceholderConfigurer
's job is to go through your Spring configuration and replace all property place-holders (a string enclosed by a dollar-curly-brace pair: ${}) with a value from a property file. The property place-holder value is the key to retrieving the value from property file. See below for an important caveat with this behaviour.
Below is the properties file that my Spring configuration references.
# Environment: "local environment", "development environment", # "test environment", "stress and volume testing environment" # or "production environment". environmentSpecificName=local environment
This means that when I retrieve testBean, its environmentName property will be "local environment". Whenever I want to change the value of testBean.environmentName in my code, I change the environmentSpecificName property and re-run my application.
Using Nested Java Properties in Spring Configuration - nested Place Holders
As you might guess from the comment I placed above the environmentSpecificName property, I want it to vary depending on which environment my code is running within. I.e. I want a different value for each of the environments my code might run within: local, development, testing, SVT and production.
An alternative way I can do this is to set up different properties for each possibility (environment), and use a nested property place-holder in the property value to determine which value should actually be used. My Spring configuration does not change, but my properties file does. Here is my new properties file.
# Environment: local, dev, test, svt or prod. environmentSpecificName=${local.environmentSpecificName} local.environmentSpecificName=local environment dev.environmentSpecificName=development environment test.environmentSpecificName=test environment svt.environmentSpecificName=stress and volume testing environment prod.environmentSpecificName=production environment
Now what happens is that the PropertyPlaceholderConfigurer
looks up the value for environmentSpecificName
and finds another property place-holder: ${local.environmentSpecificName}. It looks up the value for ${local.environmentSpecificName} and finds local environment.
The advantage here is that I can express each of the possible values as properties and switch between them as needed. It is a bit easier to edit a properties file than a Spring configuration, which tends to get complicated very quickly i.e. arguably, a properties file is more readable than a Spring configuration.
The disadvantage is that I still have to edit a file, which means a re-deploy for a web-app. An Ant build script can help with this, by outputting a different deployable artifact for each environment, using a string replacement (the replace task) to make sure environmentSpecificName is correct.
Why go to all this trouble just to vary one value in the Spring configuration? Forget the place-holders altogether and edit the value directly in the Spring configuration - like you said, an Ant build script can replace the value for us too. This is correct. Everything in this blog entry can be done with an Ant script. The only potential advantage is if you consider it easier to manage options in a properties file than in a build script, or Spring configuration. See the next section for an example of when I prefer to use this approach.
More Nested Java Properties in Spring Configuration
One situation in which I prefer this approach is when I have to manage multiple values that depend on one condition. Perhaps I have a different database for each environment, or a different set of web service URLs for each environment. I can set up each set of values in a properties file and use a single nested property to switch between sets. This is as close to conditional properties you can get with Spring.
Below is an example Spring configuration in which I define a JdbcTemplate that will come loaded with a data source specific for the environment my code is operating within.
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd"> <beans> <bean id="placeholderConfig" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer"> <property name="ignoreUnresolvablePlaceholders" value="false" /> <property name="location" value="classpath:application.properties" /> </bean> <bean name="jdbcTemplate" singleton="true" class="org.springframework.jdbc.core.JdbcTemplate"> <constructor-arg index="1"> <ref bean="dataSource" /> </constructor-arg> </bean> <bean name="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource" destroy-method="close"> <property name="driverClassName"> <value>${dataSource.driverClassName}</value> </property> <property name="url"> <value>${dataSource.url}</value> </property> <property name="username"> <value>${dataSource.username}</value> </property> <property name="password"> <value>${dataSource.password}</value> </property> </bean> </beans>
Here is what my application.properties file might look like.
# Environment: local, dev, test, svt or prod. environment=local ############################################################################### ## DATABASE PROPERTIES ############################################################################### dataSource.driverClassName=${${environment}.dataSource.driverClassName} dataSource.url=${${environment}.dataSource.url} dataSource.username=${${environment}.dataSource.username} dataSource.password=${${environment}.dataSource.password} local.dataSource.url=jdbc:oracle:thin:@oralocal:1521:oralocal local.dataSource.username=localuser local.dataSource.password=localpwd local.dataSource.driverClassName=oracle.jdbc.driver.OracleDriver dev.dataSource.url=jdbc:oracle:thin:@oradev:1521:oradev dev.dataSource.username=devuser dev.dataSource.password=devpwd dev.dataSource.driverClassName=oracle.jdbc.driver.OracleDriver test.dataSource.url=jdbc:oracle:thin:@oratest:1521:oratest test.dataSource.username=testuser test.dataSource.password=testpwd test.dataSource.driverClassName=oracle.jdbc.driver.OracleDriver svt.dataSource.url=jdbc:oracle:thin:@orasvt:1521:orasvt svt.dataSource.username=svtuser svt.dataSource.password=svtpwd svt.dataSource.driverClassName=oracle.jdbc.driver.OracleDriver prod.dataSource.url=jdbc:oracle:thin:@oraprod:1521:oraprod prod.dataSource.username=produser prod.dataSource.password=prodpwd prod.dataSource.driverClassName=oracle.jdbc.driver.OracleDriver
In this version, I only need to change one property (environment) to have four other properties change in response. Here is what the PropertyPlaceholderConfigurer
is doing for ${dataSource.url} and the other place-holders in the data source Spring configured bean.
- The property place holder ${dataSource.url} key resolves to the value ${${environment}.dataSource.url}, which is itself a property place holder with another property place holder nested within it.
- The property place holder ${environment} key resolves to the value local.
- The property place holder ${${environment}.dataSource.url} key is now ${local.dataSource.url}
- The property place holder ${local.dataSource.url} resolves to the value jdbc:oracle:thin:@oralocal:1521:oralocal.
Caveat for using PropertyPlaceholderConfigurer
If you are using an XmlBeanFactory, you have to explicitly reference the PropertyPlaceholderConfigurer
and invoke it upon your bean factory. For example.
XmlBeanFactory factory = new XmlBeanFactory(new FileSystemResource("beans.xml")); PropertyPlaceholderConfigurer configurer = new PropertyPlaceholderConfigurer(); configurer.setLocation(new FileSystemResource("application.properties")); configurer.postProcessBeanFactory(factory);
Or you could retrieve the bean from the context and then invoke it.
XmlBeanFactory factory = new XmlBeanFactory(new FileSystemResource("beans.xml")); PropertyPlaceholderConfigurer configurer = factory.getBean("placeholderConfig"); configurer.postProcessBeanFactory(factory);
Either way, you have explicitly invoke the configurer to do its work upon your Spring configuration. For this reason alone, I prefer to use ClassPathXmlApplicationContext as my factory, because it will automatically invoke any PropertyPlaceholderConfigurer
on the context, just by having it in the Spring configuration. Also, you generally need a ApplicationContext
in web apps.
Data Source vs JNDI
If you need to manage data sources across environments, and you have access to JNDI in each environment, use that instead. No need to hard code passwords in property files that way. But you will probably still a properties file approach for local unit testing - that you should be able to run without a server.
Appendix
Links that helped me with this.
- From the Spring - Java/J2EE Application Framework Reference Documentation is Chapter 3. Beans, BeanFactory and the ApplicationContext.
- I posted a question about this on the Spring forum, where I learned how to use this functionality. Further, I learned that Spring V3 might allow nested property place-holders in the property keys, not just values.
Comments
The idea is that your Spring configuration puts values into a bean object and you retrieve the bean object from Spring.
See "3.6. Interacting with the BeanFactory" in Chapter 3. Beans, BeanFactory and the ApplicationContext for ways of getting your bean!
Rob
:)
I was looking for a solution for the nested property and found your post. It's helping me resolve the problem!!!
I would like to add that instead of hard-coding your env variable inside the properties file itself, you can setup a system property -Denv=local for example. Then you don't even have to modify this property file every time you change fromone env to another. Just remember to change the system property though. Like in Tomcat I put -Denv=local in the startup.sh in my CATALINA_BASE/startup.sh file.
Cheers
Doug