zoukankan      html  css  js  c++  java
  • spring aop实现原理

    Spring事务处理你未关注过的原理
    本文对Spring实现事务处理的真正原理进行追究,从而从中提炼出一些见解。其中讲解内容可能会存在一定的误导,还希望指出,内容仅供参考!(经过本人后期继续研读Spring关于Mybatis的事务处理,其实在mybatis的里面调用了spring的方法来获取Connection,所以本文所提供的一种实现,是另一种Spring的实现猜想,仅供参考!)
      说到Spring事务原理,百度一下最多的就是Spring的AOP了,本文当然不是给你将AOP的原理,如果是这样,我也就没必要写这篇文章了,直接转载一篇就行了。借助Spring的AOP的原理,提出一个问题。
                此处先粘贴出Spring事务需要的配置内容:
    [html] view plain copy
     
    1.        <bean id="transactionManager"  
    2. class="org.springframework.jdbc.datasource.DataSourceTransactionManager">  
    3. <property name="dataSource" ref="dataSource" />.....  
    4. lt;/bean>  

     
    [html] view plain copy
     
    1.         <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">  
    2. <property name="dataSource" ref="dataSource" />  
    3. .....         
    4. lt;/bean>  
                上面两段配置文件一个是Spring事务管理器的配置文件,另一个是一个普通的JPA框架(此处是mybatis)的配置文件,这两个里面都配置了datasource,而且这个datasource的对象是在Spring的容器里面。一下提几个问题:
                1、当JPA框架对数据库进行操作的时候,是从那里获取Connection?
                2、jdbc对事务的配置,比如事务的开启,提交以及回滚是在哪里设置的?
                3、Spring是通过aop拦截切面的所有需要进行事务管理的业务处理方法,那如何获取业务处理方法里面对数据库操作的事务呢?
               现在我来对上面的问题来一一回答
               1、这个问题很简单,既然在JPA的框架里面配置了datasource,那自然会从这个datasource里面去获得连接。
               2、jdbc的事务配置是在Connection对消里面有对应的方法,比如setAutoCommit,commit,rollback这些方法就是对事务的操作。
               3、Spring需要操作事务,那必须要对Connection来进行设置。Spring的AOP可以拦截业务处理方法,并且也知道业务处理方法里面的DAO操作的JAP框架是从datasource里面获取Connection对象,那么Spring需要对当前拦截的业务处理方法进行事务控制,那必然需要得到他内部的Connection对象。整体的结构图如下:
               
               上图是一个标准的业务处理在一个线程上的基本流程。JPA框架在需要对数据库进行操作的时候,就会从Datasource里面去获取Connection对象,那么Spring是怎么样拿到在JPA内部调用的Connection并且加上用户配置的事务处理规则的呢?现在我来揭开这个谜底。
               上面也看到注入到Spring的事务管理的datasource和注入到第三方JPA框架的datasource都是在Spring容器里面的,并且是同一个对象。既然Spring可以拿到你这个datasource对象,那它为什么不进行一下封装呢?不管是哪家的Datasource他们都会实现javax.sql.DataSource这个接口,这个接口里面主要有两个方法
    [java] view plain copy
     
    1. <span style="font-size:18px">public interface DataSource  extends CommonDataSource,Wrapper {  
    2.   
    3.   /** 
    4.    * <p>Attempts to establish a connection with the data source that 
    5.    * this <code>DataSource</code> object represents. 
    6.    * 
    7.    * @return  a connection to the data source 
    8.    * @exception SQLException if a database access error occurs 
    9.    */  
    10.   Connection getConnection() throws SQLException;  
    11.         
    12.   /** 
    13.    * <p>Attempts to establish a connection with the data source that 
    14.    * this <code>DataSource</code> object represents. 
    15.    * 
    16.    * @param username the database user on whose behalf the connection is  
    17.    *  being made 
    18.    * @param password the user's password 
    19.    * @return  a connection to the data source 
    20.    * @exception SQLException if a database access error occurs 
    21.    * @since 1.4 
    22.    */  
    23.   Connection getConnection(String username, String password)   
    24.     throws SQLException;</span><span style="font-size:18px">  
    25. </span>  

                  这两个方法均是获取Connection对象的。Spring有没有可能对这个接口创建一个代理呢?通过spring的AOP。然后偷偷将Spring容器里面的datasource的bean指向这个代理对象(此处称该对象为datasourceproxy,替换之前的叫datasource)。于是不管是从哪里调用Datasource,那必然会被Spring拦截。下面是模拟了一个简单实现:
    [java] view plain copy
     
    1. public class DatasourceHandler implements InvocationHandler {  
    2.   
    3.     private DataSource dataSource;  
    4.     /** 
    5.      * @param dataSource 
    6.      */  
    7.     public DatasourceHandler(DataSource dataSource) {  
    8.         super();  
    9.         this.dataSource = dataSource;  
    10.     }  
    11.     /* (non-Javadoc) 
    12.      * @see java.lang.reflect.InvocationHandler#invoke(java.lang.Object, java.lang.reflect.Method, java.lang.Object[]) 
    13.      */  
    14.     @Override  
    15.     public Object invoke(Object proxy, Method method, Object[] args)  
    16.             throws Throwable {  
    17.         if(method.getName().equals("getConnection")){  
    18.               
    19.             if(ResourceHolder.getResource(proxy)!=null){  
    20.                 Connection connection = (Connection) method.invoke(this.dataSource, args);  
    21.                 ResourceHolder.addResource(proxy, connection);  
    22.             }  
    23.             return ResourceHolder.getResource(proxy);  
    24.         }else{  
    25.             return method.invoke(this.dataSource, args);  
    26.         }  
    27.     }  
    28. }  


    [java] view plain copy
     
    1.   
    [java] view plain copy
     
    1.   

             上面这个类是一个InvocationHandler的实现,假设这个就是Spring Aop拦截Datasource的实现。那么这个对象里面有一个datasource对象,这个对象是Spring替换代理Datasource之前的那个对象(datasource)。看到invoke的实现,其实就是将代理(datasourceproxy)调用的类发转到datasource去调用,其实还是调用了datasource,但是这里就加入了一些特殊的东西,那就是ResourceHolder
    [java] view plain copy
     
    1. <span style="font-size:18px">public class ResourceHolder {  
    2.   
    3.     private static ThreadLocal<Map<Object,Object>> resources= new ThreadLocal<Map<Object,Object>>();  
    4.       
    5.     public static void addResource(Object key,Object value){  
    6.         if(resources.get()==null){  
    7.             resources.set(new HashMap<Object,Object>());  
    8.         }  
    9.         resources.get().put(key, value);  
    10.     }  
    11.       
    12.     public static Object getResource(Object key){  
    13.           
    14.         return resources.get().get(key);  
    15.     }  
    16.       
    17.     public static void clear(){  
    18.         resources.remove();  
    19.     }  
    20. }</span>  
     上面是这个对象的实现,里面有一个ThreadLocal静态属性,用于存放一些数据信息。ThreadLocal用过的人都知道他是线程的局部变量,在整个线程过程中都是有效的。那么在invoke里面当每次调用的时候,判断调用的方法是不是getConnection,如果是,则进行如下操作
    if(ResourceHolder.getResource(proxy)!=null){
    Connection connection =(Connection) method.invoke(this.dataSource, args);
    ResourceHolder.addResource(proxy, connection);
    }
    return ResourceHolder.getResource(proxy);
    其中proxy就是Spring自动生成的datasourceproxy,将proxy和connection的关系添加到ResourceHolder里面去,而ResourceHolder又是将这个关系添加到ThreadLocal<Map<Object,Object>> resources这个静态变量里面,添加到这个里面,那么以后如果在当前线程从datasourceproxy获取connection对象,都将是一个对象,这就保证了一个业务方法里面进行多次dao操作,调用的都是一个connection对象,同时保证了多个dao都是在一个事务里面。既然这样,那么Spring的事务管理就可以在调用业务方法之前,先从datasource里面先获得一个connection对象,并且对connection添加上用户配置的事务规则,由于这个connection对象会自动添加到ThreadLocal里面,那么后面的业务处理方法将会是调用已经添加好事务规则的connection对象,当业务方法处理完毕,那么spring事务就可以对这个connection进行回滚或者提交了。经过这样一个过程,那么在一个处理某个业务的线程里面执行流程应该是这样的:
                   
            总结一下:这里首选是对DataSource生成一个代理类,从而可以监控获取Connection的过程,在通过ThreadLocal对Connection线程级别的缓存从而促使在同一个业务处理方法相对于某个DataSource都是在一个Connection中,从而保证处于同一事务中,因为这些执行都是在一个线程中的。这里处理Spring的AOP之外,还有一个ThreadLocal的使用。在实践编程中,有时候你会发现ThreadLocal会带来很大的帮助。
            比如,你要在某个操作中的每个处理流程都要知道操作人信息,而且这个流程可能不是在一个方法或者一个类中处理完,如果在session环境中,你可能会考虑用session,但不是所有的开发都是在Session环境中的,那么此时ThreadLocal边是最好的帮手,可以在用户触发这个操作时候将用户信息放在ThreadLocal中,那么后面的每个流程都可以从ThreadLocal中获取,而且这个是线程范围的,每个线程中的ThreadLocal是不相干的,这样也防止了多线程的操作。
  • 相关阅读:
    系统tabbar出现两个tabbar的问题解决方案。
    iOS 开发苹果由http改为https 之后,如果服务器不做相应的修改,那么客户端需要做点更改
    UIAlertController的一些简单实用方法
    ios开发同一个lab显示不同的颜色
    ios开发同一个版本多次提交不想改变版本号的解决方法
    iOS开发textfield的一些方法汇总
    C#笔记
    Shader之性能优化
    Shader之ShaderUI使用方法
    Shader Example
  • 原文地址:https://www.cnblogs.com/zhangshitong/p/5760686.html
Copyright © 2011-2022 走看看