Статьи

Spring 3.1 — загрузка свойств для конфигурации XML из базы данных

Spring позволяет легко вводить значения, полученные из файлов свойств через PropertyPlaceholderConfigurer и (до Spring 3.1) PropertySourcesPlaceholderConfigurer (Spring 3.1). Эти классы реализуют интерфейс BeanFactoryPostProcessor, который позволяет им манипулировать значениями в файле конфигурации Spring XML до инициализации bean-компонентов. Поэтому, если вы укажете $ {jdbc.driverClassName}, который будет установлен в свойстве ‘driverClassName’, эта переменная будет заменена / заменена на значение с ключом ‘jdbc.driverClassName’ в файле свойств.

Помимо файлов свойств, таблица базы данных также может быть местом для получения пар ключ-значение. Отлично, так что просто расширьте PropertySourcesPlaceholderConfigurer, и он прочитает таблицу, содержащую пары ключ-значение, заполнит их, и все готово!

Однако есть небольшая проблема. Если bean-компонент DataSource также полагается на значения, полученные из файла свойств (например, URL-адрес JDBC, имя пользователя, пароль) и является хорошим Springers, вставьте этот bean-компонент в класс bean-компонента, расширяющий PropertySourcesPlaceholderConfigurer, контейнер bean-компонента не запустится должным образом, поскольку Переменная jdbc.driverClassName ‘не может быть разрешена. Удивительно, но факт.

Причина этого в том, что любой компонент, внедренный в класс BeanFactoryPostProcessor, будет инициировать инициализацию компонента перед тем, как будут запущены классы BeanFactoryPostProcessor. Вы знаете, инъекция зависимостей … все зависимые бины должны быть готовы, прежде чем их вводят потребителю. Так что это создает нечто вроде циклической зависимости. Все зависимости в конфигурации XML сначала разрешаются до запуска классов BeanFactoryPostProcessor.

Итак, как это сделать? Ну, есть трюк, который вы можете использовать. Класс BeanFactoryPostProcessor имеет доступ к объекту ConfigurableListableBeanFactory с помощью метода postProcessBeanFactory. Из этого объекта вы можете сделать getBean и получить ссылку на любой компонент с идентификатором. И угадайте, что, вы можете получить хваленый bean-компонент DataSource, не вызывая преждевременную инициализацию bean-компонента.

Допустим, есть таблица ‘sys_param’ со следующими данными:

1
2
3
4
5
PARAM_CD PARAM_VALUE
-------------- --------------
service.charge 1.5
rebate.amount 15.99
smtp.ip 173.194.79.16

DbPropertySourcesPlaceholderConfigurer показан здесь:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
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);
    }
}

Класс DbProperties будет использовать ссылку на DataSource и запрашивать базу данных, чтобы получить пары ключ-значение:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
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());
        }
    }
}

Чтобы продемонстрировать, что значения из таблицы правильно введены, вот класс, который действует как потребитель:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
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;
    }
 
}

Наконец, что не менее важно, конфигурация Spring (bean-компонент DataSource не показан, упрощенный для ясности)

1
classpath:system.properties

Первые 2 определения бина — это классы BeanFactoryPostProcessor, и для гарантии того, что первый запускается первым, устанавливается свойство ‘order’ (ниже означает более высокий приоритет).

Для DbPropertySourcesPlaceholderConfigurer для ясности используется другой префикс и суффикс заполнителя (обратите внимание на заполнители для DbPropConsumer).

Таким образом, при запуске контейнера Spring вы сможете увидеть аналогичный вывод, как показано ниже:

1
2
3
4
5
6
7
8
9
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
]

Ссылка: Spring 3.1 — Загрузка свойств для конфигурации XML из базы данных от нашего партнера JCG Аллена Джулии в блоге YK’s Workshop .