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 interfaces 11 * or the like will implement {@link #postProcessBeforeInitialization}, 12 * while post-processors that wrap beans with proxies will normally 13 * implement {@link #postProcessAfterInitialization}. 14 * 15 * @author Juergen Hoeller 16 * @since 10.10.2003 17 * @see InstantiationAwareBeanPostProcessor 18 * @see DestructionAwareBeanPostProcessor 19 * @see ConfigurableBeanFactory#addBeanPostProcessor 20 * @see BeanFactoryPostProcessor 21 */ 22 public interface BeanPostProcessor { 23 24 /** 25 * Apply this BeanPostProcessor to the given new bean instance <i>before</i> any bean 26 * 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 instance 30 * @param beanName the name of the bean 31 * @return the bean instance to use, either the original or a wrapped one; if 32 * <code>null</code>, no subsequent BeanPostProcessors will be invoked 33 * @throws org.springframework.beans.BeansException in case of errors 34 * @see org.springframework.beans.factory.InitializingBean#afterPropertiesSet 35 */ 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 bean 40 * 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 FactoryBean 44 * instance and the objects created by the FactoryBean (as of Spring 2.0). The 45 * post-processor can decide whether to apply to either the FactoryBean or created 46 * 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 a 48 * {@link InstantiationAwareBeanPostProcessor#postProcessBeforeInstantiation} method, 49 * in contrast to all other BeanPostProcessor callbacks. 50 * @param bean the new bean instance 51 * @param beanName the name of the bean 52 * @return the bean instance to use, either the original or a wrapped one; if 53 * <code>null</code>, no subsequent BeanPostProcessors will be invoked 54 * @throws org.springframework.beans.BeansException in case of errors 55 * @see org.springframework.beans.factory.InitializingBean#afterPropertiesSet 56 * @see org.springframework.beans.factory.FactoryBean 57 */ 58 Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException; 59 60 }
以上为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 }
数据源配置文件:
1 db.driverClass=**** 2 db.jdbcUrl=**** 3 db.user=**** 4 db.password=****
自定义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 ff 16 * 17 */ 18 public class ComboPooledDataSourceBeanPostProcessor implements BeanPostProcessor { 19 20 private String dataSourceName; 21 22 @Override 23 public Object postProcessAfterInitialization(Object bean, String paramString) throws BeansException { 24 return bean; 25 } 26 27 @Override 28 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 @Override 40 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 }
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
测试示例:
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&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);