Home » Java » Enterprise Java » Spring 3.1 – Loading Properties For XML Configuration From Database

About Allen Chee

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.

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.

Do you want to know how to develop your skillset to become a Java Rockstar?

Subscribe to our newsletter to start Rocking right now!

To get you started we give you our best selling eBooks for FREE!

1. JPA Mini Book

2. JVM Troubleshooting Guide

3. JUnit Tutorial for Unit Testing

4. Java Annotations Tutorial

5. Java Interview Questions

6. Spring Interview Questions

7. Android UI Design

and many more ....

 

Leave a Reply

Your email address will not be published. Required fields are marked *

*


Want to take your Java Skills to the next level?
Grab our programming books for FREE!
  • Save time by leveraging our field-tested solutions to common problems.
  • The books cover a wide range of topics, from JPA and JUnit, to JMeter and Android.
  • Each book comes as a standalone guide (with source code provided), so that you use it as reference.
Last Step ...

Where should we send the free eBooks?

Good Work!
To download the books, please verify your email address by following the instructions found on the email we just sent you.