Enterprise Java

Spring 3.1 – Loading Properties For XML Configuration From Database

Spring makes it easy to inject values obtained from properties files via its PropertyPlaceholderConfigurer and (pre-Spring 3.1) PropertySourcesPlaceholderConfigurer (Spring 3.1). These classes implement the BeanFactoryPostProcessor interface, which enables them to manipulate the values within the Spring XML configuration file before the beans are initialized. So if you specify ${jdbc.driverClassName} to be set to the property ‘driverClassName’, this variable will be replaced/swapped with the value with the key ‘jdbc.driverClassName’ in a properties file.

Apart from properties files, the database table can also be a place to get key-value pairs. Great, so just extend the PropertySourcesPlaceholderConfigurer, and have it read a table containing the key-value pairs, populate them and we’re done!

However, there’s a slight problem. If the DataSource bean also relies on values obtained from a properties file (e.g. JDBC URL, username, password), and being good Springers, inject this bean to the bean class extending PropertySourcesPlaceholderConfigurer, the bean container will fail to startup properly, because the ‘jdbc.driverClassName’ variable cannot be resolved. Strange, but true.

The reason for this is that any bean injected into a BeanFactoryPostProcessor class will trigger bean initialization BEFORE the BeanFactoryPostProcessor classes are run. You know, dependency injection…all depending beans have to be ready before being injected into the consumer. So this creates a cyclic-dependency kind of thing. All dependencies in the XML configuration are resolved first before the BeanFactoryPostProcessor classes are run.

So, how to go about this? Well, there’s a trick you can employ. A BeanFactoryPostProcessor class has access to the ConfigurableListableBeanFactory object via the ‘postProcessBeanFactory’ method. From this object, you can do a ‘getBean’ and get a reference of any bean with an id. And guess what, you can get the vaunted DataSource bean without triggering premature bean initialization.

Let’s say there’s a table ‘sys_param’ with the following data:

PARAM_CD PARAM_VALUE
-------------- --------------
service.charge 1.5
rebate.amount 15.99
smtp.ip 173.194.79.16

The DbPropertySourcesPlaceholderConfigurer is shown here:

package org.gizmo.labs.utils.spring;

import javax.sql.DataSource;

import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.context.support.PropertySourcesPlaceholderConfigurer;

public class DbPropertySourcesPlaceholderConfigurer extends PropertySourcesPlaceholderConfigurer
{
	@Override
	public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException
	{
		DataSource dataSource = beanFactory.getBean(DataSource.class);
		DbProperties dbProps = new DbProperties(dataSource);

		setProperties(dbProps);
		super.postProcessBeanFactory(beanFactory);
	}
}

The DbProperties class will make use of the DataSource reference and queries the database to get the key-value pairs:

package org.gizmo.labs.utils.spring;

import java.util.List;
import java.util.Map;
import java.util.Properties;

import javax.sql.DataSource;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.jdbc.core.JdbcTemplate;

public class DbProperties extends Properties
{
	private final Logger logger = LoggerFactory.getLogger(DbProperties.class);
	private static final long serialVersionUID = 1L;

	public DbProperties(DataSource dataSource)
	{
		super();
		JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource); 
		List
                    <map
                     > l = jdbcTemplate.queryForList('select param_cd, param_value from sys_param');

		for(Map

                       m: l)
		{
			logger.debug('Loading from DB: [{}:{}]', m.get('PARAM_CD'), m.get('PARAM_VALUE'));
			setProperty((m.get('PARAM_CD')).toString(), (m.get('PARAM_VALUE')).toString());
		}
	}
}

To demonstrate that the values from the table are properly injected, here’s the class which acts as the consumer:

package org.gizmo.labs.utils.spring;

import java.math.BigDecimal;

import org.apache.commons.lang.builder.ReflectionToStringBuilder;
import org.apache.commons.lang.builder.ToStringStyle;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.InitializingBean;

public class DbPropConsumer implements InitializingBean
{
	private final Logger logger = LoggerFactory.getLogger(DbPropConsumer.class);

	private BigDecimal serviceCharge;
	private double rebateAmount;
	private String smtpIp;

	@Override
	public void afterPropertiesSet() throws Exception
	{
		logger.debug('I have consumed: {}', this);
	}

	public String toString()
	{
		return ReflectionToStringBuilder.toString(this, ToStringStyle.MULTI_LINE_STYLE);
	}	

	public BigDecimal getServiceCharge() {
		return serviceCharge;
	}

	public void setServiceCharge(BigDecimal serviceCharge) {
		this.serviceCharge = serviceCharge;
	}

	public double getRebateAmount() {
		return rebateAmount;
	}

	public void setRebateAmount(double rebateAmount) {
		this.rebateAmount = rebateAmount;
	}

	public String getSmtpIp() {
		return smtpIp;
	}

	public void setSmtpIp(String smtpIp) {
		this.smtpIp = smtpIp;
	}

}

Last but not least, the Spring configuration (DataSource bean not shown, simplified for clarity):

classpath:system.properties

The first 2 bean definitions are the BeanFactoryPostProcessor classes, and to ensure the first one is run first, the ‘order’ property is set (lower means higher precedence).

For the DbPropertySourcesPlaceholderConfigurer, a different placeholder prefix and suffix is used for clarity (notice the placeholders for DbPropConsumer).

So, upon Spring container startup, you should be able to view a similar output as below:

2012-09-18 00:03:14, DEBUG, org.gizmo.labs.utils.spring.DbProperties, Loading from DB: [service.charge:1.5]
2012-09-18 00:03:14, DEBUG, org.gizmo.labs.utils.spring.DbProperties, Loading from DB: [rebate.amount:15.99]
2012-09-18 00:03:14, DEBUG, org.gizmo.labs.utils.spring.DbProperties, Loading from DB: [smtp.ip:173.194.79.16]
2012-09-18 00:03:14, DEBUG, org.gizmo.labs.utils.spring.DbPropConsumer, I have consumed: org.gizmo.labs.utils.spring.DbPropConsumer@189b939[
logger=Logger[org.gizmo.labs.utils.spring.DbPropConsumer]
serviceCharge=1.5
rebateAmount=15.99
smtpIp=173.194.79.16
]

 

Reference: Spring 3.1 – Loading Properties For XML Configuration From Database from our JCG partner Allen Julia at the YK’s Workshop blog.

Allen Chee

Allen is a software developer working in the banking domain. Apart from hacking code and tinkering with technology, he reads a lot about history, so that mistakes of the past need not be repeated if they are remembered.
Subscribe
Notify of
guest

This site uses Akismet to reduce spam. Learn how your comment data is processed.

1 Comment
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
Dharmendra Parakh
Dharmendra Parakh
5 years ago

Hello Allen,

I tried suggested approach, but it did not work for me. I am using spring 4.2.4. I see that postProcessBeanFactory is invoked only after resolving other beans. So I always get this exception if any of the bean uses a property:

Exception in thread “main” org.springframework.beans.factory.BeanDefinitionStoreException: Invalid bean definition with name ‘customer’ defined in class path resource [test-context.xml]: Could not resolve placeholder ‘configParam’ in value “${configParam}”; nested exception is java.lang.IllegalArgumentException: Could not resolve placeholder ‘configParam’ in value “${configParam}”
at org.springframework.beans.factory.config.PlaceholderConfigurerSupport.doProcessProperties(PlaceholderConfigurerSupport.java:228)

Please advice.

Back to top button