ThreadLocal與AOP

文章來源,感謝博主:點擊打開鏈接


-----以下是轉載內容-----

經歷了幾天的研究,終於是明白了ThreadLocal在Spring事務管理過程中發揮的用途。下面就以圖文的形式和大家分享,如有錯誤,歡迎指正。 

大家都知道,Spring允許以聲明的方式進行事務管理。通過聲明的方式,程序員可以僅僅專注於業務代碼,事務管理由Spring框架代爲進行。 

以JDBC爲例,正常的事務代碼可能如下: 

Java代碼  收藏代碼
  1. dbc = new DataBaseConnection();//第1行  
  2. Connection con = dbc.getConnection();//第2行  
  3. con.setAutoCommit(false);// //第3行  
  4. con.executeUpdate(...);//第4行  
  5. con.executeUpdate(...);//第5行  
  6. con.executeUpdate(...);//第6行  
  7. con.commit();//第7行  

上述代碼,可以分成三個部分: 


事務準備階段:第1~3行 
業務處理階段:第4~6行 
事務提交階段:第7行 


在Spring框架中,程序員專注於設計業務處理階段,事務準備階段和事務提交階段由Spring來完成。在實際開發過程中,我們僅僅編寫了業務處理階段,事務準備階段和事務提交階段會由Spring框架根據我們的事務相關配置文件動態生成--利用AOP。關於AOP,這裏就不說了,網上有很多資料。 

但是大家需要注意一個問題,在利用AOP動態生成的代碼中,如何才能讓三個階段使用同一個數據源連接呢?這是很重要的。如果三個階段使用不同的數據源連接,自然是錯誤的。 

現在需要辦到的是 讓軟件結構中縱向的三個階段 使用同樣的一個參數,而這三個階段之間不可以進行參數傳遞。解決方案是---線程綁定。 

Web容器中,每個完整的請求週期會由一個線程來處理。因此,如果我們能將一些參數綁定到線程的話,就可以實現在軟件架構中跨層次的參數共享(是隱式的共享)。這是一件很牛逼的事情,在框架中被經常使用。而JAVA中恰好提供了綁定的方法--使用ThreadLocal。 

ThreadLocal是一種線程本地變量,使用ThreadLocal的形式聲明一個變量,該變量就會在每個線程中創建一個變量的副本。 

Java代碼  收藏代碼
  1. public class Demo {  
  2.     public static ThreadLocal<String> threadLocalString = new ThreadLocal<String>(){  
  3.         protected String initialValue() {  
  4.             return "";  
  5.         }  
  6.     };  
  7.     public static ThreadLocal<Long> threadLocalLong =new ThreadLocal<Long>(){  
  8.         protected Long initialValue() {  
  9.             return 0L;  
  10.         }  
  11.     };  
  12.     public static void main(String [] args){  
  13.         threadLocalLong.set(100L);  
  14.         threadLocalString.set("test");  
  15.           
  16.         new Thread(new Runnable() {  
  17.             @Override  
  18.             public void run() {  
  19.                 threadLocalString.set("thread");  
  20.                 System.out.println(threadLocalLong.get());  
  21.                 System.out.println(threadLocalString.get());  
  22.             }  
  23.         }).start();  
  24.           
  25.         System.out.println(threadLocalLong.get());  
  26.         System.out.println(threadLocalString.get());  
  27.     }  
  28. }  


從上面的代碼可看出,在不同的線程中調用同一個類對象的get()方法,輸出依據線程的不同而不同。 
再來看一個關於ThreadLocal的例子: 
Java代碼  收藏代碼
  1. import java.util.HashMap;  
  2. import java.util.Map;  
  3.   
  4. public class Demo {  
  5.     public static void main(String [] args){  
  6.         ResourceHolder.putResource("conn",new Conn("connection1"));  
  7.         new Thread(new Runnable() {  
  8.             @Override  
  9.             public void run() {  
  10.                 // 該線程不會得到主線程綁定的變量  
  11.                 System.out.println(ResourceHolder.getResource("conn"));  
  12.             }  
  13.         }).start();  
  14.           
  15.         System.out.println(ResourceHolder.getResource("conn"));  
  16.         new Demo().function1();  
  17.         new Demo().function2();  
  18.         System.out.println(ResourceHolder.getResource("conn"));  
  19.     }  
  20.     public void function1(){  
  21.         System.out.println(ResourceHolder.getResource("conn"));  
  22.     }  
  23.     public void function2(){  
  24.         System.out.println(ResourceHolder.getResource("conn"));  
  25.     }  
  26. }  
  27.   
  28. class ResourceHolder{  
  29.       
  30.     public static ThreadLocal<Map<Object,Object>> threadLocalMap=new ThreadLocal<Map<Object,Object>>();  
  31.     public static void putResource(Object key,Object value){  
  32.         if(threadLocalMap.get()==null)  
  33.             threadLocalMap.set(new HashMap<Object,Object>());  
  34.         threadLocalMap.get().put(key, value);  
  35.     }  
  36.     public static Object getResource(Object key){  
  37.         if(threadLocalMap.get()==null)  
  38.             threadLocalMap.set(new HashMap<Object,Object>());  
  39.         return threadLocalMap.get().get(key);  
  40.     }  
  41.     public static void clearResource(Object key,Object value){  
  42.         if(threadLocalMap.get()!=null)  
  43.             threadLocalMap.remove();  
  44.     }  
  45. }  
  46. class Conn{  
  47.     private String name;  
  48.       
  49.     public Conn(String name) {  
  50.         super();  
  51.         this.name = name;  
  52.     }  
  53.   
  54.     public String getName() {  
  55.         return name;  
  56.     }  
  57.   
  58.     public void setName(String name) {  
  59.         this.name = name;  
  60.     }  
  61.     @Override  
  62.     public String toString() {  
  63.         return "Conn [name=" + name + "]";  
  64.     }  
  65. }  


現在我們可以考慮使用ThreadLocal來將 事務準備階段使用的連接 綁定到當前線程 以便在之後的 業務處理階段 和 事務提交階段使用了。不過問題來了,這個ThreadLocal放在哪裏呢?一種方案是寫到DataSource中,但DataSource是策略模式動態配置的,況且都是第三方的,不那麼容易改。 

我們再一次想到AOP,爲DataSource創建一個代理類,每次調用DataSource的getConn方法的時候,都由攔截器攔截並轉換爲對DataSource代理類的調用,在代理類中加一些貓膩; 

看代碼: (代理類) 
Java代碼  收藏代碼
  1. package com.xyz.transaction;  
  2. import java.lang.reflect.InvocationHandler;  
  3. import java.lang.reflect.Method;  
  4. import java.lang.reflect.Proxy;  
  5.   
  6. public class DataSourceHandler implements InvocationHandler {  
  7.       
  8.     private Object originalDataDource;  
  9.     public Object bind(Object obj) {  
  10.         this.originalDataDource=obj;  
  11.         return Proxy.newProxyInstance(this.originalDataDource.getClass().getClassLoader(),  
  12.                 originalDataDource.getClass().getInterfaces(), this);  
  13.     }  
  14.     @Override  
  15.     public Object invoke(Object proxy, Method method, Object[] args)  
  16.             throws Throwable {  
  17.         // TODO Auto-generated method stub  
  18.         if("getConn".equals(method.getName())){//默認數據源的獲取連接方法爲getConn()  
  19.             if(ResourceHolder.getResource(proxy)==null){  
  20.                 Object obj=method.invoke(originalDataDource, args);  
  21.                 ResourceHolder.addResource(proxy, obj);  
  22.             }  
  23.             return ResourceHolder.getResource(proxy);  
  24.         }else{  
  25.             return method.invoke(originalDataDource, args);  
  26.         }  
  27.     }  
  28.       
  29. }  

Java代碼  收藏代碼
  1. package com.xyz.transaction;  
  2.   
  3. import java.util.HashMap;  
  4. import java.util.Map;  
  5.   
  6. public class ResourceHolder {  
  7.     private static ThreadLocal<Map<Object,Object>> threadLocalMap=new ThreadLocal<Map<Object,Object>>();  
  8.     public static void addResource(Object key,Object value){  
  9.         if(threadLocalMap.get()==null)  
  10.             threadLocalMap.set(new HashMap<Object, Object>());  
  11.         threadLocalMap.get().put(key, value);  
  12.     }  
  13.     public static Object getResource(Object key){  
  14.         if(threadLocalMap.get()==null)  
  15.             threadLocalMap.set(new HashMap<Object, Object>());  
  16.         return threadLocalMap.get().get(key);  
  17.     }  
  18.     public static void clear(){  
  19.         threadLocalMap.remove();  
  20.     }  
  21. }  


來看以上代碼,每次訪問getConn方法的時候,都查看是否在當前線程綁定的Map中有對應的連接,如果有直接返回。如果沒有,再向真實的getConn請求獲得一個連接並放到當前線程綁定的Map中。 

流程如圖所示,圖片代碼見附件。




發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章