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中。 

流程如图所示,图片代码见附件。




评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值