文章來源,感謝博主:點擊打開鏈接
-----以下是轉載內容-----
經歷了幾天的研究,終於是明白了ThreadLocal在Spring事務管理過程中發揮的用途。下面就以圖文的形式和大家分享,如有錯誤,歡迎指正。
大家都知道,Spring允許以聲明的方式進行事務管理。通過聲明的方式,程序員可以僅僅專注於業務代碼,事務管理由Spring框架代爲進行。
以JDBC爲例,正常的事務代碼可能如下:
- dbc = new DataBaseConnection();//第1行
- Connection con = dbc.getConnection();//第2行
- con.setAutoCommit(false);// //第3行
- con.executeUpdate(...);//第4行
- con.executeUpdate(...);//第5行
- con.executeUpdate(...);//第6行
- con.commit();//第7行
上述代碼,可以分成三個部分:
事務準備階段:第1~3行
業務處理階段:第4~6行
事務提交階段:第7行
在Spring框架中,程序員專注於設計業務處理階段,事務準備階段和事務提交階段由Spring來完成。在實際開發過程中,我們僅僅編寫了業務處理階段,事務準備階段和事務提交階段會由Spring框架根據我們的事務相關配置文件動態生成--利用AOP。關於AOP,這裏就不說了,網上有很多資料。
但是大家需要注意一個問題,在利用AOP動態生成的代碼中,如何才能讓三個階段使用同一個數據源連接呢?這是很重要的。如果三個階段使用不同的數據源連接,自然是錯誤的。
現在需要辦到的是 讓軟件結構中縱向的三個階段 使用同樣的一個參數,而這三個階段之間不可以進行參數傳遞。解決方案是---線程綁定。
Web容器中,每個完整的請求週期會由一個線程來處理。因此,如果我們能將一些參數綁定到線程的話,就可以實現在軟件架構中跨層次的參數共享(是隱式的共享)。這是一件很牛逼的事情,在框架中被經常使用。而JAVA中恰好提供了綁定的方法--使用ThreadLocal。
ThreadLocal是一種線程本地變量,使用ThreadLocal的形式聲明一個變量,該變量就會在每個線程中創建一個變量的副本。
- public class Demo {
- public static ThreadLocal<String> threadLocalString = new ThreadLocal<String>(){
- protected String initialValue() {
- return "";
- }
- };
- public static ThreadLocal<Long> threadLocalLong =new ThreadLocal<Long>(){
- protected Long initialValue() {
- return 0L;
- }
- };
- public static void main(String [] args){
- threadLocalLong.set(100L);
- threadLocalString.set("test");
- new Thread(new Runnable() {
- @Override
- public void run() {
- threadLocalString.set("thread");
- System.out.println(threadLocalLong.get());
- System.out.println(threadLocalString.get());
- }
- }).start();
- System.out.println(threadLocalLong.get());
- System.out.println(threadLocalString.get());
- }
- }
從上面的代碼可看出,在不同的線程中調用同一個類對象的get()方法,輸出依據線程的不同而不同。
再來看一個關於ThreadLocal的例子:
- import java.util.HashMap;
- import java.util.Map;
- public class Demo {
- public static void main(String [] args){
- ResourceHolder.putResource("conn",new Conn("connection1"));
- new Thread(new Runnable() {
- @Override
- public void run() {
- // 該線程不會得到主線程綁定的變量
- System.out.println(ResourceHolder.getResource("conn"));
- }
- }).start();
- System.out.println(ResourceHolder.getResource("conn"));
- new Demo().function1();
- new Demo().function2();
- System.out.println(ResourceHolder.getResource("conn"));
- }
- public void function1(){
- System.out.println(ResourceHolder.getResource("conn"));
- }
- public void function2(){
- System.out.println(ResourceHolder.getResource("conn"));
- }
- }
- class ResourceHolder{
- public static ThreadLocal<Map<Object,Object>> threadLocalMap=new ThreadLocal<Map<Object,Object>>();
- public static void putResource(Object key,Object value){
- if(threadLocalMap.get()==null)
- threadLocalMap.set(new HashMap<Object,Object>());
- threadLocalMap.get().put(key, value);
- }
- public static Object getResource(Object key){
- if(threadLocalMap.get()==null)
- threadLocalMap.set(new HashMap<Object,Object>());
- return threadLocalMap.get().get(key);
- }
- public static void clearResource(Object key,Object value){
- if(threadLocalMap.get()!=null)
- threadLocalMap.remove();
- }
- }
- class Conn{
- private String name;
- public Conn(String name) {
- super();
- this.name = name;
- }
- public String getName() {
- return name;
- }
- public void setName(String name) {
- this.name = name;
- }
- @Override
- public String toString() {
- return "Conn [name=" + name + "]";
- }
- }
現在我們可以考慮使用ThreadLocal來將 事務準備階段使用的連接 綁定到當前線程 以便在之後的 業務處理階段 和 事務提交階段使用了。不過問題來了,這個ThreadLocal放在哪裏呢?一種方案是寫到DataSource中,但DataSource是策略模式動態配置的,況且都是第三方的,不那麼容易改。
我們再一次想到AOP,爲DataSource創建一個代理類,每次調用DataSource的getConn方法的時候,都由攔截器攔截並轉換爲對DataSource代理類的調用,在代理類中加一些貓膩;
看代碼: (代理類)
- package com.xyz.transaction;
- import java.lang.reflect.InvocationHandler;
- import java.lang.reflect.Method;
- import java.lang.reflect.Proxy;
- public class DataSourceHandler implements InvocationHandler {
- private Object originalDataDource;
- public Object bind(Object obj) {
- this.originalDataDource=obj;
- return Proxy.newProxyInstance(this.originalDataDource.getClass().getClassLoader(),
- originalDataDource.getClass().getInterfaces(), this);
- }
- @Override
- public Object invoke(Object proxy, Method method, Object[] args)
- throws Throwable {
- // TODO Auto-generated method stub
- if("getConn".equals(method.getName())){//默認數據源的獲取連接方法爲getConn()
- if(ResourceHolder.getResource(proxy)==null){
- Object obj=method.invoke(originalDataDource, args);
- ResourceHolder.addResource(proxy, obj);
- }
- return ResourceHolder.getResource(proxy);
- }else{
- return method.invoke(originalDataDource, args);
- }
- }
- }
- package com.xyz.transaction;
- import java.util.HashMap;
- import java.util.Map;
- public class ResourceHolder {
- private static ThreadLocal<Map<Object,Object>> threadLocalMap=new ThreadLocal<Map<Object,Object>>();
- public static void addResource(Object key,Object value){
- if(threadLocalMap.get()==null)
- threadLocalMap.set(new HashMap<Object, Object>());
- threadLocalMap.get().put(key, value);
- }
- public static Object getResource(Object key){
- if(threadLocalMap.get()==null)
- threadLocalMap.set(new HashMap<Object, Object>());
- return threadLocalMap.get().get(key);
- }
- public static void clear(){
- threadLocalMap.remove();
- }
- }
來看以上代碼,每次訪問getConn方法的時候,都查看是否在當前線程綁定的Map中有對應的連接,如果有直接返回。如果沒有,再向真實的getConn請求獲得一個連接並放到當前線程綁定的Map中。
流程如圖所示,圖片代碼見附件。