Wednesday, May 15, 2019

Loading configuration/properties files from tomcat shared loader classpath and application classpath via Spring's PropertyPlaceholderConfigurer


The Shared class loader is visible to all web applications and may be used to shared code/config/properties etc. across all web applications. However, any updates to this shared code will require a Tomcat restart.

Tomcat shared loader config can be found in: <TomcatHome>\conf\catalina.properties.

Refer to this post for configuring tomcat shared loader: How to configure 'shared' loader directory in TOMCAT 6.x/7.x/8.x

If you have already setup the shared loader then you can ignore the steps outlined in above post.

Before we start with the steps, we need to know about Spring's PropertyPlaceholderConfigurer class.

PropertyPlaceholderConfigurer class to externalize the deployment details into a properties file, and access from bean configuration file via a special format – ${variable}.

For example, you have a property file called "config.properties" containing a property named server.url=http://127.0.0.1:8080/myApp and you want to use this property in sprint bean definition for passing the configured value then we can load the property file using PropertyPlaceholderConfigurer class and in the bean definition we can refer to property key as ${server.url}.

e.g.

<bean id="requestProcessor" class="com.github.abhinavmishra14.job.RequestProcessor">
  <constructor-arg value="${server.url}" />
</bean>


Now that we know the purpose of using the PropertyPlaceholderConfigurer class, let's try to load two different properties via PropertyPlaceholderConfigurer class. Here we will load a "config.properties" file from application classpath (<app>/WEB-INF/classes/config.properties) and an another properties file called "config-global.properties"

Follow the steps given below to configure and load the properties:

1- Define a bean in applicationContext.xml file which will load the properties file

<bean id="configPropertyLoader"
class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<property name="locations">
<list>
<value>classpath:config.properties</value>
<value>classpath:config-global.properties</value>
</list>
</property>
<property name="ignoreUnresolvablePlaceholders" value="true"/>
<property name="ignoreResourceNotFound" value="true"/>
</bean>

Now here you will notice two additional properties named as "ignoreUnresolvablePlaceholders" and "ignoreResourceNotFound".

ignoreUnresolvablePlaceholders - The ignoreUnresolvablePlaceholders set to true will ignore placeholders that are not set and not throw an exception. For example if you have the following property in your class @Value("${request.timeout}") and no corresponding value set in your properties file. If bean definition for PropertyPlaceholderConfigurer does not provide the aforesaid tag, it will fail there immediately as, by default, Spring is fail-fast.

ignoreResourceNotFoundThe ignoreResourceNotFound property set to true will ignore a resource that isn't found. For example, you added the classpath:config.properties and classpath:config-global.properties in the bean definition given above but you did you not placed/copied the config-global.properties file at "<TomcatHome>\shared\classes"  (Shared loader classpath) folder then bootstrapping will fail for the application. By default, Spring is fail-fast. To avoid the error you need to set the value to true.
If file is not found in the classpath anywhere, the application will bootstrap without any problems but it will print following log:

2019-05-15 16:42:39 INFO  [org.springframework.beans.factory.config.PropertyPlaceholderConfigurer:172] - Loading properties file from class path resource [config.properties]
2019-05-15 16:42:39 INFO  [org.springframework.beans.factory.config.PropertyPlaceholderConfigurer:172] - Loading properties file from class path resource [config-global.properties]

2019-05-15 16:42:39 WARN  [org.springframework.beans.factory.config.PropertyPlaceholderConfigurer:181] - Could not load properties from class path resource [config-global.properties]: class path resource [config-global.properties] cannot be opened because it does not exist

If  "ignoreResourceNotFound" property is not provided or set to "false" (default) then you will get following error:

org.springframework.beans.factory.BeanInitializationException: Could not load properties; nested exception is java.io.FileNotFoundException: class path resource [config-global.properties] cannot be opened because it does not exist
at org.springframework.beans.factory.config.PropertyResourceConfigurer.postProcessBeanFactory(PropertyResourceConfigurer.java:89)

Considering the given above behaviors, it is recommended to always use <property name="ignoreUnresolvablePlaceholders" value="true"/> and <property name="ignoreResourceNotFound" 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.


2- Now copy the config-global.properties file into "<TomcatHome>\shared\classes" folder. If you are testing your application within eclipse, then make sure you have updated the catalina.properties file under Servers project. Use the above given post to get more details on how to set catalina.properties in eclipse servers project.

3- Restart the application. You will see both property files will be loaded and following log will be printed:

2019-05-15 16:13:54 INFO  [org.springframework.beans.factory.config.PropertyPlaceholderConfigurer:172] - Loading properties file from class path resource [config.properties]
2019-05-15 16:13:54 INFO  [org.springframework.beans.factory.config.PropertyPlaceholderConfigurer:172] - Loading properties file from class path resource [config-global.properties]