Spring代碼中動態切換數據源

在Spring-Mybatis中,有這樣一個類AbstractRoutingDataSource,根據名字能夠猜到,這是一個框架提供的用於動態選擇數據源的類。這個類有兩個重要的參數,分別叫html

defaultTargetDataSource和targetDataSources。通常的工程都是一個數據源,因此不太接觸到這個類。java

 

[html]程序員

  1. <bean id="myoneDataSource" class="org.apache.commons.dbcp2.BasicDataSource" destroy-method="close">    
  2.     <property name="driverClassName" value="${jdbc.myone.driver}"/>    
  3.     <property name="url" value="${jdbc.myone.url}"/>    
  4.     <property name="username" value="${jdbc.myone.username}"/>    
  5.     <property name="password" value="${jdbc.myone.password}"/>    
  6. </bean>    
  7. <bean id="mytwoDataSource" class="org.apache.commons.dbcp2.BasicDataSource" destroy-method="close">    
  8.     <property name="driverClassName" value="${jdbc.mytwo.driver}"/>    
  9.     <property name="url" value="${jdbc.mytwo.url}"/>    
  10.     <property name="username" value="${jdbc.mytwo.username}"/>    
  11.     <property name="password" value="${jdbc.mytwo.password}"/>    
  12. </bean>    
  13.   下載
  14. <bean id="multipleDataSource" class="dal.datasourceswitch.MultipleDataSource">    
  15.     <property name="defaultTargetDataSource" ref="myoneDataSource"/> <!--默認主庫-->    
  16.     <property name="targetDataSources">    
  17.         <map>    
  18.             <entry key="myone" value-ref="myoneDataSource"/>            <!--輔助aop完成自動數據庫切換-->    
  19.             <entry key="mytwo" value-ref="mytwoDataSource"/>    
  20.         </map>    
  21.     </property>    
  22. </bean>   


上面的配置文件對這兩個參數的描述已經很清楚了,但這是多個數據源已經肯定的場景。咱們這篇博客中的場景是多個數據源的信息存在於數據庫中,可能數據庫中的數據源信息會動態的增長或者減小。這樣的話,就不能像上面這樣配置了。那怎麼辦呢?spring

 

咱們僅僅須要設定默認的數據源,即defaultDataSource參數,至於targetDataSources參數咱們須要在代碼中動態的設定。來看下具體的xml配置:數據庫

 

[html] apache

  1. <bean id="defaultDataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close"  
  2.           p:driverClassName="${db_driver}"  
  3.           p:url="${db_url}"  
  4.           p:username="${db_user}"  
  5.           p:password="${db_pass}"  
  6.           p:validationQuery="select 1"  
  7.           p:testOnBorrow="true"/>  
  8.   
  9.     <!--動態數據源相關-->  
  10.     <bean id="dynamicDataSource" class="org.xyz.test.service.datasourceswitch.impl.DynamicDataSource">  
  11.         <property name="targetDataSources">  
  12.             <map key-type="java.lang.String">  
  13.                 <entry key="defaultDataSource" value-ref="defaultDataSource"/>  
  14.             </map>  
  15.         </property>  
  16.         <property name="defaultTargetDataSource" ref="defaultDataSource"/>  
  17.     </bean>  


從上面的配置文件中能夠看到,咱們僅僅配置了默認的數據源defaultDataSource下載。至於其餘的數據源targetDataSources,咱們沒有配置,須要在代碼中動態的建立。關於配置就講清楚啦!但咱們注意到,支持動態數據源的不該該是AbstractRoutingDataSource類嗎?怎麼上面的配置中是DynamicDataSource類。沒錯,這個是咱們自定義的繼承自AbstractRoutingDataSource類的類,也只最重要的類,來看下:(理解這個類,你須要熟練掌握JAVA反射,以及ThreadLocal變量,和Spring的注入機制。別退縮,你們都是這樣一步步學過來的!)(下面僅僅是看下全貌,代碼的下面會有詳細的說明)app

 

[java] 框架

  1. final class DynamicDataSource extends AbstractRoutingDataSource implements ApplicationContextAware{  
  2.   
  3.     private static final String DATA_SOURCES_NAME = "targetDataSources";  
  4.   
  5.     private ApplicationContext applicationContext;  
  6.   
  7.     @Override  
  8.     protected Object determineCurrentLookupKey() {  
  9.         DataSourceBeanBuilder dataSourceBeanBuilder = DataSourceHolder.getDataSource();  
  10.         System.out.println("----determineCurrentLookupKey---"+dataSourceBeanBuilder);  
  11.         if (dataSourceBeanBuilder == null) {  
  12.             return null;  
  13.         }  
  14.         DataSourceBean dataSourceBean = new DataSourceBean(dataSourceBeanBuilder);  
  15.         //查看當前容器中是否存在  
  16.         try {  
  17.             if (!getTargetDataSources().keySet().contains(dataSourceBean.getBeanName())) {  
  18.                 addNewDataSourceToTargerDataSources(dataSourceBean);  
  19.             }  
  20.             return dataSourceBean.getBeanName();  
  21.         } catch (NoSuchFieldException | IllegalAccessException e) {  
  22.             throw new SystemException(ErrorEnum.MULTI_DATASOURCE_SWITCH_EXCEPTION);  
  23.         }  
  24.     }  
  25.   
  26.     private void addNewDataSourceToTargerDataSources(DataSourceBean dataSourceBean) throws NoSuchFieldException, IllegalAccessException {  
  27.         getTargetDataSources().put(dataSourceBean.getBeanName(), createDataSource(dataSourceBean));  
  28.         super.afterPropertiesSet();//通知spring有bean更新  
  29.     }  
  30.   
  31.     private Object createDataSource(DataSourceBean dataSourceBean) throws IllegalAccessException {  
  32.         //在spring容器中建立而且聲明bean  
  33.         ConfigurableApplicationContext context = (ConfigurableApplicationContext) applicationContext;  
  34.         DefaultListableBeanFactory beanFactory = (DefaultListableBeanFactory) context.getBeanFactory();  
  35.         BeanDefinitionBuilder beanDefinitionBuilder = BeanDefinitionBuilder.genericBeanDefinition(BasicDataSource.class);  
  36.         //將dataSourceBean中的屬性值賦給目標bean  
  37.         Map<String, Object> properties = getPropertyKeyValues(DataSourceBean.class, dataSourceBean);  
  38.         for (Map.Entry<String, Object> entry : properties.entrySet()) {  
  39.             beanDefinitionBuilder.addPropertyValue((String) entry.getKey(), entry.getValue());  下載
  40.         }  
  41.         beanFactory.registerBeanDefinition(dataSourceBean.getBeanName(), beanDefinitionBuilder.getBeanDefinition());  
  42.         return applicationContext.getBean(dataSourceBean.getBeanName());  
  43.     }  
  44.   
  45.     private Map<Object, Object> getTargetDataSources() throws NoSuchFieldException, IllegalAccessException {  
  46.         Field field = AbstractRoutingDataSource.class.getDeclaredField(DATA_SOURCES_NAME);  
  47.         field.setAccessible(true);  
  48.         return (Map<Object, Object>) field.get(this);  
  49.     }  
  50.   
  51.   
  52.     private <T> Map<String, Object> getPropertyKeyValues(Class<T> clazz, Object object) throws IllegalAccessException {  
  53.         Field[] fields = clazz.getDeclaredFields();  
  54.         Map<String, Object> result = new HashMap<>();  
  55.         for (Field field : fields) {  
  56.             field.setAccessible(true);  
  57.             result.put(field.getName(), field.get(object));  
  58.         }  
  59.         result.remove("beanName");  
  60.         return result;  
  61.     }  
  62.   
  63.     @Override  
  64.     public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {  
  65.         this.applicationContext=applicationContext;  
  66.     }  
  67. }  

 

 

首先來看覆蓋方法determineCurrentLookupKey(),框架在每次調用數據源時會先調用這個方法,以便知道使用哪一個數據源。在本文的場景中,數據源是由程序員在即將切換數據源以前,將要使用的那個數據源的名稱放到當前線程的ThreadLocal中,這樣在determineCurrentLookupKey()方法中就能夠從ThreadLocal中拿到當前請求鑰匙用的數據源,從而進行初始化數據源並返回該數據源的操做。在ThreadLocal變量中,咱們保存了一個DataSourceBuilder,這是一個建造者模式。讀者直接把他理解爲是一個數據源的描述就好。所以,determineCurrentLookupKey()方法的流程就是下載:先從ThreadLocal中拿出要使用的數據源信息,而後看當前的targetDataSources中是否有了這個數據源。若是有直接返回。若是沒有,建立一個這樣的數據源,放到targetDataSources中而後返回。ide

 

因爲targetDataSources是父類AbstractRoutingDataSource中的一個私有域,所以想要得到他的實例只能經過反射機制。這也是下面的方法存在的意義!ui

 

[java]

  1. private Map<Object, Object> getTargetDataSources() throws NoSuchFieldException, IllegalAccessException {  
  2.      Field field = AbstractRoutingDataSource.class.getDeclaredField(DATA_SOURCES_NAME);  
  3.      field.setAccessible(true);  
  4.      return (Map<Object, Object>) field.get(this);  
  5.  }  

 

 

而後,咱們來看具體是怎麼建立數據源的。

 

[java] 

  1. private Object createDataSource(DataSourceBean dataSourceBean) throws IllegalAccessException {  
  2.        //在spring容器中建立而且聲明bean  
  3.        ConfigurableApplicationContext context = (ConfigurableApplicationContext) applicationContext;  
  4.        DefaultListableBeanFactory beanFactory = (DefaultListableBeanFactory) context.getBeanFactory();  
  5.        BeanDefinitionBuilder beanDefinitionBuilder = BeanDefinitionBuilder.genericBeanDefinition(BasicDataSource.class);  
  6.        //將dataSourceBean中的屬性值賦給目標bean  
  7.        Map<String, Object> properties = getPropertyKeyValues(DataSourceBean.class, dataSourceBean);  
  8.        for (Map.Entry<String, Object> entry : properties.entrySet()) {  
  9.            beanDefinitionBuilder.addPropertyValue((String) entry.getKey(), entry.getValue());  
  10.        }  
  11.        beanFactory.registerBeanDefinition(dataSourceBean.getBeanName(), beanDefinitionBuilder.getBeanDefinition());  
  12.        return applicationContext.getBean(dataSourceBean.getBeanName());  
  13.    }  


你們知道,Spring最主要的功能是做爲bean容器,即他負責bean生命週期的管理。所以,咱們自定義的datasource也不能「逍遙法外」,必須交給Spring容器來管理。這也正是DynamicDataSource類須要實現ApplicationContextAware而且注入ApplicationContext的緣由。上面的代碼就是根據指定的信息建立一個數據源。這種建立是Spring容器級別的建立。建立完畢以後,須要把剛剛建立的這個數據源放到targetDataSources中,而且還要通知Spring容器,targetDataSources對象變了。下面的方法就是在作這樣的事情下載

 

 

[java] 

  1. private void addNewDataSourceToTargerDataSources(DataSourceBean dataSourceBean) throws NoSuchFieldException, IllegalAccessException {  
  2.       getTargetDataSources().put(dataSourceBean.getBeanName(), createDataSource(dataSourceBean));  
  3.       super.afterPropertiesSet();//通知spring有bean更新  
  4.   }  

上面的這一步很重要。沒有這一步的話,Spring壓根就不會知道targetDataSources中多了一個數據源。至此DynamicDataSource類就講完了。其實仔細想一想,思路仍是很清晰的。啃掉了DynamicDataSource類這塊硬骨頭,下面就是一些輔助類了。好比說DataSourceHolder,業務代碼經過使用這個類來通知DynamicDataSource中的determineCurrentLookupKey()方法到底使用那個數據源:

 

 

[java] 

  1. public final class DataSourceHolder {  
  2.     private static ThreadLocal<DataSourceBeanBuilder> threadLocal=new ThreadLocal<DataSourceBeanBuilder>(){  
  3.         @Override  
  4.         protected DataSourceBeanBuilder initialValue() {  
  5.             return null;  
  6.         }  
  7.     };  
  8.   
  9.     static DataSourceBeanBuilder getDataSource(){  
  10.         return threadLocal.get();  
  11.     }  
  12.   
  13.     public static void setDataSource(DataSourceBeanBuilder dataSourceBeanBuilder){  
  14.         threadLocal.set(dataSourceBeanBuilder);  
  15.     }  
  16.   
  17.   
  18.     public static void clearDataSource(){  
  19.         threadLocal.remove();  
  20.     }  
  21. }  
相關文章
相關標籤/搜索