In the XML config the reference to the property named "x.y.z" this simple:
<bean name="theBean" class="myBean"> <property name="parameter" value="${x.y.z}" /> </bean>
This is usually accompanied by the element from context namespace setting the name of the configuration file:
<context:property-placeholder location="classpath:app.properties"/>
This is good approach for simple scenarios. In a more complex setup things can get a bit unwieldy.
First of all each occurrence of <context:property-placeholder> element causes creation of a new instance of the class responsible for property names resolving - PropertyPlaceholderConfigurer instance. You can enjoy lot of fun with unresolved placeholders in your application, once the library you're using brings its application context configuration containing <context:property-placeholder>. Even if you set ignoreUnresolvablePlaceholders =false for the element, it does not change the fact you have multiple PropertyPlaceholderconfigurers where you need only one.
You may want to the properties loaded by Spring to a Spring unaware class or further customize the property resolving. In such case you'll create a custom PropertyPlaceholderConfigurer ... and get more collisions with default ones created by the <context:property-placeholder>.
Custom PropertyPlaceholderConfigurer
The solution is simple, remove all the occurrences of <context:property-placeholder> and create a custom PropertyPlaceholderConfigurer, that will fulfill your desires. Example of one is below. The static method getProperty() can serve properties to Spring unaware code.
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; import org.springframework.beans.factory.config.PropertyPlaceholderConfigurer; import java.util.*; class MyPropertyResolver extends PropertyPlaceholderConfigurer { private static Map<String, String> propertiesMap = new HashMap<String, String>(); @Override protected void processProperties (ConfigurableListableBeanFactory beanFactory, Properties properties) { super.processProperties(beanFactory, properties); for ( Object propertyKey : properties.keySet()) { propertiesMap.put ( propertyKey.toString(), // Spring 3.x resolvePlaceholder(propertyKey.toString(), properties) // for Spring 2.5 : // parseStringValue(propertyKey.toString(), properties, Collections.EMPTY_SET) ); } } public static String getProperty(String name) { return propertiesMap.get(name); } }
Following configuration of bean named appProperties provides setup for multiple property files - the last one is the application property file overriding default values provided by libraries. Option ignoreResorceNotFound<?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.0.xsd"> <!-- load it early, provides properties to other classes --> <bean id="appProperties" class="MyPropertyResolver"> <property name="ignoreResourceNotFound" value="true"/> <property name="locations"> <list> <!-- Ordering matters, properties in the later loaded files override values from the previous files --> <value>classpath:lib.properties</value> <value>classpath:app.properties</value> </list> </property> </bean> </beans>
Adding to an Application Context
The last thing you need to do to make it working is to put the bean into your application context and use it. Following setup for a web application ensures that the bean is instantiated ASAP during application context initialization.
<web-app xmlns="http://java.sun.com/xml/ns/j2ee" version="2.4" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd"> <context-param> <param-name>contextConfigLocation</param-name> <param-value> classpath:spring-resources.xml ... </param-value> </context-param> </web-app>
For command-line application it's very simple too:
ClassPathXmlApplicationContext appContext = new ClassPathXmlApplicationContext( new String[] {"spring-resources.xml"}); MyPropertyResolver resolver = (MyPropertyResolver) appContext.getBean("appProperties");
Thanks a lot for this article. I was trying to get this working for a while and was able to get it working with the help of this article.
ReplyDeleteI have a further question. Would it be cleaner design if this bean is injected into wherever it is to be used, rather than call the static method? Let me know your thoughts.
Good question - of course you can do it. The advantage of calling the static method is that it can be accessed even by the code the is not Spring-aware.
ReplyDeleteCan you please show me the directory structure where the property file should be placed and how it is accessed using java in Spring 3?
DeleteIf you prefix the file name with "classpath:" you need only to put the file in class path :) - what directories are on class path depends on your setup.
DeleteIn application you do not access the file but the properties, you can alwasy call MyPropertyResolver.getProperty(propertyName) , regardless if the classs is augmented by Spring or not.