Spring: Be Careful When Using PropertyPlaceholderConfigurer
A few days back, one of my juniors came to me and said that when he tested his modules separately in JUnit, everything worked perfectly. But when the modules were deployed to the server as a unit, the server fails to start.
So I checked his code and, fortunately, identified the problem, which we'll discuss here. Let me describe the problem in detail.
Problem Statement
The application he works on has three layers.
DAO layer
Service layer
Middleware (Spring MVC).
Of course, that's a very standard architecture, and each layer is built on top of Spring, maintaining separate Spring context files. Each layer maintains its own property file. Say for the DAO layer, the database connection-related properties are maintained in db.properties. In the service layer, web service URL, or other service, parameters are maintained in service.properties. To load these properties files in an application, we use PropertyPlaceholderConfigurer in each SpringContext file (DAO, Service, Middleware).
Example
db.properties File
db.url=abc@localhost:1028/BillSchema
db.user=admin
db.password=tiger
And the entry for PropertyPlaceholderConfigurer in Spring_DB_Context.xml:
<bean id="propertyConfigurerDB" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<property name="locations">
<list>
<value>classpath:db.properties</value>
</list>
</property>
</bean>
Same for service:
Service.properties File
bil.service.url =http://localhost:9292/billing/getTaxBasedOnState?state=
bil.user=admin.
bill.passwd=abcd
And the entry for PropertyPlaceholderConfigurer in Spring_Service_Context.xml:
<bean id="propertyConfigurerService" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<property name="locations">
<list>
<value>classpath:service.properties</value>
</list>
</property>
</bean>
In the middleware, Spring context Spring_Middleware_Context.xml imports Spring_DB_Context.xml and Spring_Service_Context.xml.
Now, when my colleague ran each module's JUnit test, they resolved the properties files and worked fine. But the problem kicks in when the middleware combined with the other two modules as a JAR and imported context files into the Spring_middleware context and tried to deploy.
It showed an error message, {db.url} couldn't resolve, and the server failed to start.
Why Was This Happening?
It ran in JUnit, but when we combined the modules as a JAR and tried to deploy it on the server, why do we get such an error message, as we have provided the PropertyPlaceholderConfigurer in each context, and both properties files are present in classpath?
Actual Problem
Upon viewing the error message, one thing is clear: Somehow, those properties are not resolved by PropertyPlaceholderConfigurer.
Let's take a deep dive into this.
There are three separate Spring context files:
Spring_DB_Context.xml
Spring_Service_Context.xml
Spring_Middleware_Context.xml
Spring_DB_Context.xml and Spring_Service_Context.xml joined the big context Spring_Middleware_Context.xml while deploying via import statement in Spring_Middleware_Context.xml.
Each context has its own PropertyPlaceholderConfigurer and a properties file. Now, if the service context is loaded first by the classloader, then its PropertyPlaceholderConfigurer and properties files are loaded, so this PropertyPlaceholderConfigurer can resolve the beans under the service context, but it won’t able to resolve the properties of DB_Context, as the service PropertyPlaceholderConfigurer does not knows about db.properties file. So, the server says {db.url} can’t be resolved.
The same is true for the service layer if the classloader loads the database context first. The {service.url} can’t be resolved.
Solution
The solution is just a tweak in PropertyPlaceholderConfigurer in each context. Just add the following line:
<property name="ignoreUnresolvablePlaceholders" value="true"/>
<!--Updated Service context File-->
<bean id="propertyConfigurerDB" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<property name="locations">
<list>
<value>classpath:service.properties</value>
</list>
</property>
<property name="ignoreUnresolvablePlaceholders" value="true"/>
</bean>
<!--Updated DB context File-->
<bean id="propertyConfigurerService" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<property name="locations">
<list>
<value>classpath:dbproperties</value>
</list>
</property>
<property name="ignoreUnresolvablePlaceholders" value="true"/>
</bean>
By doing this, we tell Spring to ignore the unresolved properties. By default, Spring throws an exception if it can’t resolve a property that is fail-fast in nature.
But here, we will explicitly say to Spring, "Don’t throw the exception. Go ahead with the unresolved properties." The action will be deferred so that when the other PropertyPlaceholderConfigurer (DB context here) is loaded, it will resolve.
Please note that for each context, you have to mention <property name="ignoreUnresolvablePlaceholders" value="true"/>. If one context does not provide the aforesaid tag, it will fail there immediately as, by default, Spring is fail-fast.
My junior's case was very similar to this, but although he provided <property name="ignoreUnresolvablePlaceholders" value="true"/> in every module, it still failed.
Then, I searched for transitive dependencies on the modules and found that one of the modules does not define the <property name="ignoreUnresolvablePlaceholders" value=" true"/> tag in its context, so it fails there. We updated and recompiled it, and the problems vanished like magic.
Tip:Always use <property name="ignoreUnresolvablePlaceholders" value="true"/> with PropertyPlaceholderConfigurer, as you never know when and how other modules use your modules. They may have their own PropertyPlaceholderConfigurer as well, so you can break their code unknowingly, leaving you scratching your head.