首页 > 代码库 > Spring BeanPostProcessor与动态加载数据源配置

Spring BeanPostProcessor与动态加载数据源配置

前言:

      本文旨在介绍Spring动态配置数据源的方式,即对一个DataSource的配置诸如jdbcUrl,user,password,driverClass都通过运行时指定,而非由xml静态配置。

      Spring构造Context的参数一般只包含配置文件路径和类加载器,如果需要达到动态传入配置参数的目的,需要Spring在初始化数据源相关bean的时候能够对原有配置执行修改或替换,为方便处理,本文将定义一个名为DynamicDataSourceConfigHolder的公共类提供配置数据存储。

       本文替换数据源为c3p0配置。

BeanPostProcessor简介:

      Spring BeanPostProcesssor通常被称为Spring Bean回调处理器,它一般用于在实例化一个bean的前后增加一些附加操作,它会对全局的Spring bean配置生效。

Spring Bean的生命周期处理:

      Spring Bean生命周期通常对应两种处理方式,一种是init-method &destroy-method, 另一种是InitializingBean的afterPropertiesSet()方法和DisposeBean的destroy()方法,BeanPostProcessor的出现使得批处理Spring bean定义有了可能。

BeanPostProcessor定义:

 1 /** 2  * Factory hook that allows for custom modification of new bean instances, 3  * e.g. checking for marker interfaces or wrapping them with proxies. 4  * 5  * <p>ApplicationContexts can autodetect BeanPostProcessor beans in their 6  * bean definitions and apply them to any beans subsequently created. 7  * Plain bean factories allow for programmatic registration of post-processors, 8  * applying to all beans created through this factory. 9  *10  * <p>Typically, post-processors that populate beans via marker interfaces11  * or the like will implement {@link #postProcessBeforeInitialization},12  * while post-processors that wrap beans with proxies will normally13  * implement {@link #postProcessAfterInitialization}.14  *15  * @author Juergen Hoeller16  * @since 10.10.200317  * @see InstantiationAwareBeanPostProcessor18  * @see DestructionAwareBeanPostProcessor19  * @see ConfigurableBeanFactory#addBeanPostProcessor20  * @see BeanFactoryPostProcessor21  */22 public interface BeanPostProcessor {23 24     /**25      * Apply this BeanPostProcessor to the given new bean instance <i>before</i> any bean26      * initialization callbacks (like InitializingBean‘s <code>afterPropertiesSet</code>27      * or a custom init-method). The bean will already be populated with property values.28      * The returned bean instance may be a wrapper around the original.29      * @param bean the new bean instance30      * @param beanName the name of the bean31      * @return the bean instance to use, either the original or a wrapped one; if32      * <code>null</code>, no subsequent BeanPostProcessors will be invoked33      * @throws org.springframework.beans.BeansException in case of errors34      * @see org.springframework.beans.factory.InitializingBean#afterPropertiesSet35      */36     Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException;37 38     /**39      * Apply this BeanPostProcessor to the given new bean instance <i>after</i> any bean40      * initialization callbacks (like InitializingBean‘s <code>afterPropertiesSet</code>41      * or a custom init-method). The bean will already be populated with property values.42      * The returned bean instance may be a wrapper around the original.43      * <p>In case of a FactoryBean, this callback will be invoked for both the FactoryBean44      * instance and the objects created by the FactoryBean (as of Spring 2.0). The45      * post-processor can decide whether to apply to either the FactoryBean or created46      * objects or both through corresponding <code>bean instanceof FactoryBean</code> checks.47      * <p>This callback will also be invoked after a short-circuiting triggered by a48      * {@link InstantiationAwareBeanPostProcessor#postProcessBeforeInstantiation} method,49      * in contrast to all other BeanPostProcessor callbacks.50      * @param bean the new bean instance51      * @param beanName the name of the bean52      * @return the bean instance to use, either the original or a wrapped one; if53      * <code>null</code>, no subsequent BeanPostProcessors will be invoked54      * @throws org.springframework.beans.BeansException in case of errors55      * @see org.springframework.beans.factory.InitializingBean#afterPropertiesSet56      * @see org.springframework.beans.factory.FactoryBean57      */58     Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException;59 60 }
View Code

   以上为Spring源代码,我们重点关注它和Spring bean初始化的关系,即postProcessBeforeInitialization将会在Spring 执行bean初始化钩子(init-method或者afterPropertiesSet)之前被调用。

DynamicDataSourceConfigHolder:

 1 package org.wit.ff; 2  3 import java.util.Map; 4  5 /** 6  * 动态数据源配置存储. 7  * @author ff 8  * 9  */10 public class DynamicDataSourceConfigHolder {11     12     /**13      * 定义本地变量,加入存在多个Spring Context连接多个不同的数据源时,可以共用此类。14      */15     private static final ThreadLocal<Map<String,String>> dynamicDataSourceConfigHolder = new ThreadLocal<Map<String,String>>();16     17     public static void setDynamicConfig(Map<String,String> dynamicDataSourceConfig) {18         dynamicDataSourceConfigHolder.set(dynamicDataSourceConfig);19     }20 21     public static Map<String,String> getDynamicDataSourceConfig() {22         return (dynamicDataSourceConfigHolder.get());23     }24 25     public static void clear() {26         dynamicDataSourceConfigHolder.remove();27     }28 29 }
View Code

数据源配置文件:

1 db.driverClass=****2 db.jdbcUrl=****3 db.user=****4 db.password=****
View Code

自定义bean回调处理器:

 1 package org.wit.ff; 2  3 import java.lang.reflect.Method; 4 import java.util.HashMap; 5 import java.util.Map; 6  7 import org.springframework.beans.BeansException; 8 import org.springframework.beans.factory.config.BeanPostProcessor; 9 import org.springframework.util.ReflectionUtils;10 11 import com.mchange.v2.c3p0.ComboPooledDataSource;12 13 /**14  * Bean回调处理器.15  * @author ff16  *17  */18 public class ComboPooledDataSourceBeanPostProcessor implements BeanPostProcessor {19 20     private String dataSourceName;21 22     @Override23     public Object postProcessAfterInitialization(Object bean, String paramString) throws BeansException {24         return bean;25     }26 27     @Override28     public Object postProcessBeforeInitialization(final Object bean, String beanName) throws BeansException {29         // 限制数据源名称和类型.30         if (bean instanceof ComboPooledDataSource && dataSourceName.equals(beanName)) {31             final Map<String,String> methodMatchField = new HashMap<String,String>();32             methodMatchField.put("setDriverClass", "db.driverClass");33             methodMatchField.put("setJdbcUrl", "db.jdbcUrl");34             methodMatchField.put("setUser", "db.user");35             methodMatchField.put("setPassword", "db.password");36             // 从公共存储区中加载.37             final Map<String, String> config = DynamicDataSourceConfigHolder.getDynamicDataSourceConfig();38             ReflectionUtils.doWithMethods(bean.getClass(), new ReflectionUtils.MethodCallback() {39                 @Override40                 public void doWith(Method paramMethod) throws IllegalArgumentException, IllegalAccessException {41                     if(methodMatchField.containsKey(paramMethod.getName())){42                         ReflectionUtils.invokeMethod(paramMethod, bean, config.get(methodMatchField.get(paramMethod.getName())));43                     }44                 }45             });46         }47         return bean;48     }49 50     public void setDataSourceName(String dataSourceName) {51         this.dataSourceName = dataSourceName;52     }53 54 }
View Code

Spring 配置文件dynamicDatasource/applicationContext.xml:

 1  <!-- 加载properties配置文件 --> 2     <bean id="propertyConfigurer" 3         class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer"> 4         <property name="locations"> 5             <list> 6                 <!-- 这里支持多种寻址方式:classpath和file --> 7                 <value>classpath:dynamicDatasource/dbconfig.properties</value> 8             </list> 9         </property>10     </bean>11     <!-- 回调处理器.-->12     <bean id="dynamicDataSourceBp" class="org.wit.ff.ComboPooledDataSourceBeanPostProcessor" >13         <property name="dataSourceName" value="dataSource" />14     </bean>15 16     <!-- 数据库连接池 -->17     <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"18         destroy-method="close">19         <property name="driverClass" value="${db.driverClass}" />20         <property name="jdbcUrl" value="${db.jdbcUrl}" />21         <property name="user" value="${db.user}" />22         <property name="password" value="${db.password}" />23     </bean>24     
View Code

测试示例:

 1 Map<String,String> dynamicDataSourceConfig = new HashMap<String,String>(); 2 dynamicDataSourceConfig.put("db.driverClass", "com.mysql.jdbc.Driver"); 3 dynamicDataSourceConfig.put("db.jdbcUrl", "jdbc:mysql://127.0.0.1:3306/menlo3?autoReconnect=true&amp;characterEncoding=utf-8"); 4 dynamicDataSourceConfig.put("db.user", "root"); 5 dynamicDataSourceConfig.put("db.password", "root"); 6 DynamicDataSourceConfigHolder.setDynamicConfig(dynamicDataSourceConfig); 7 ApplicationContext applicationContext = new ClassPathXmlApplicationContext(new String[]{"classpath:dynamicDatasource/applicationContext.xml"}); 8  9 //执行一段操作数据库的逻辑验证即可.10 11 assertNotNull(applicationContext);
View Code

 

Spring BeanPostProcessor与动态加载数据源配置