I recently ran into the problem of registering custom property sources in the environment. My problem is that I have a Spring configuration library that I want to import into the Spring application context, and this requires its own property sources. However, I do not necessarily control all places where the application context is created. Because of this, I do not want to use the recommended ApplicationContextInitializer or register-before-refresh mechanisms to register custom property sources.
What I really upset was that using the old PropertyPlaceholderConfigurer, it was easy to subclass and configure the configurators completely in the Spring configuration. In contrast, to configure property sources, we are told that we should not do this in the Spring configuration itself, but before initializing the application context.
After some research and testing and errors, I found that you can register custom property sources from within the Spring configuration, but you have to be careful how you do it. Sources must be registered before any PropertySourcesPlaceholderConfigurers object executes in context. You can do this by doing the initial registration of a BeanFactoryPostProcessor with PriorityOrdered and an order that has a higher priority than PropertySourcesPlaceholderConfigurer, which uses sources.
I wrote this class that does the job:
package example; import java.io.IOException; import java.util.Properties; import org.springframework.beans.BeansException; import org.springframework.beans.factory.BeanInitializationException; import org.springframework.beans.factory.config.BeanFactoryPostProcessor; import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; import org.springframework.core.Ordered; import org.springframework.core.PriorityOrdered; import org.springframework.core.env.ConfigurableEnvironment; import org.springframework.core.env.Environment; import org.springframework.core.env.MutablePropertySources; import org.springframework.core.env.PropertiesPropertySource; import org.springframework.core.env.PropertySource; import org.springframework.core.io.support.PropertiesLoaderSupport; public abstract class AbstractPropertySourceFactory extends PropertiesLoaderSupport implements ApplicationContextAware, PriorityOrdered, BeanFactoryPostProcessor {
It should be noted that the default order of this factory property class takes precedence over the default order for PropertySourcesPlaceholderConfigurer by default.
In addition, the registration of the property source occurs in postProcessBeanFactory, which means that it will be executed in the correct order relative to PropertySourcesPlaceholderConfigurer. I found the hard way that InitializingBean and afterPropertiesSet do not take into account the order parameter, and I abandoned this approach as erroneous and redundant.
Finally, since this is a BeanFactoryPostProcessor, it is a bad idea to try to link a lot depending on dependencies. Therefore, the class accesses the environment directly through the application context, which it receives using ApplicationContextAware.
In my case, I needed a property source to decrypt the password properties, which I implemented using the following subclass:
package example; import java.util.Properties; public class PasswordPropertySourceFactory extends AbstractPropertySourceFactory { private static final PasswordHelper passwordHelper = new PasswordHelper(); private String[] passwordProperties; public String[] getPasswordProperties() { return passwordProperties; } public void setPasswordProperties(String[] passwordProperties) { this.passwordProperties = passwordProperties; } public void setPasswordProperty(String passwordProperty) { this.passwordProperties = new String[] { passwordProperty }; } @Override protected void convertProperties(Properties props) {
Finally, I pointed out the source of the factory properties in my Spring configuration:
<context:property-placeholder order="1000" ignore-unresolvable="true" /> <bean class="example.PasswordPropertySourceFactory"> <property name="propertySourceName" value="DBPropertySource" /> <property name="location" value="classpath:db.properties" /> <property name="passwordProperty" value="db.password" /> <property name="ignoreResourceNotFound" value="true" /> <property name="order" value="100" /> </bean>
To be honest, with the default settings in PropertySourcesPlaceholderConfigurer and AbstractPropertySourceFactory, you might not even need to specify the order in the Spring configuration.
However, this works, and it does not require any attempt to initialize the application context.