天天看點

Spring 事務源碼解析 → 它是如何保證同個事務的

  在spring jdbcTemplate 事務,各種詭異,包你醍醐灌頂!最後遺留了一個問題:spring是如何保證同個事務的?

  當然,spring事務内容挺多的,如果都要講的話要花很長時間,而本片部落格的主旨是解決上一篇部落格遺留的問題,那麼我們把問題細化下來, 就是spring如何保證一個事務中的jdbc connection是同一個?

  如若沒有事務,這個很好了解,可以了解成spring隻是對我們一般的jdbc操作進行了一些封裝,減少了我們的代碼量

  1、一般寫法

    代碼中的Connection的擷取有很多種方式,不一定是代碼中jdbcTemplate的方式,大家看的時候可以假設成其他的方式

Spring 事務源碼解析 → 它是如何保證同個事務的
Spring 事務源碼解析 → 它是如何保證同個事務的

View Code

  2、spring jdbc的寫法

Spring 事務源碼解析 → 它是如何保證同個事務的
Spring 事務源碼解析 → 它是如何保證同個事務的

  兩者對比,就會發現我們用spring jdbc的代碼量減少了很多,流程如下

  

Spring 事務源碼解析 → 它是如何保證同個事務的

  不考慮事務,一次資料庫操作就對應一個Connection,前後不同的操作對應的不同Connection,那麼每次對資料庫的請求是互不影響的。那麼spring真的是這麼實作的嗎? 暫且先留個懸念。

  如若有需要用事務的需求,那麼我們會怎麼實作了?

  1、jdbc  事務

    這個是最基本的,也是最容易想到的;關閉Connection的自動送出(con.setAutoCommit(false);),用同一個Connection執行不同的資料庫請求,最後送出(con.commit();),有異常則進行復原(con.rollback();),最後釋放資源(如果有連接配接池,那麼con設定成自動送出即可(con.setAutoCommit(true);));

  2、spring 事務

    這種方式可能能想到,但是很多人都不了解,追根究底就是不了解spring事務的源碼

  spring-tx-xx.jar中的包org.springframework.transaction.config下的init方法中有這麼一句:registerBeanDefinitionParser("annotation-driven", new AnnotationDrivenBeanDefinitionParser()); 那麼我們就從AnnotationDrivenBeanDefinitionParser的parse方法開始(為什麼是從這個方法開始,後續會回答)

Spring 事務源碼解析 → 它是如何保證同個事務的
Spring 事務源碼解析 → 它是如何保證同個事務的

  1、跟進AopAutoProxyConfigurer.configureAutoProxyCreator(element, parserContext);這個方法注冊了代理類以及支撐起整個事務功能的三個bean:BeanFactoryTransactionAttributeSourceAdvisor、AnnotationTransactionAttributeSource、TransactionInterceptor,其中AnnotationTransactionAttributeSource和TransactionInterceptor被注冊到了BeanFactoryTransactionAttributeSourceAdvisor中

Spring 事務源碼解析 → 它是如何保證同個事務的
Spring 事務源碼解析 → 它是如何保證同個事務的

  我們先看代理類的注冊,跟進AopNamespaceUtils.registerAutoProxyCreatorIfNecessary(parserContext, element);接着跟進BeanDefinition beanDefinition = AopConfigUtils.registerAutoProxyCreatorIfNecessary(parserContext.getRegistry(), parserContext.extractSource(sourceElement));發現registerOrEscalateApcAsRequired(InfrastructureAdvisorAutoProxyCreator.class, registry, source),我們先來看看InfrastructureAdvisorAutoProxyCreator類的層次結構圖,發現它間接實作了BeanPostProcessor接口,那麼spring在執行個體化bean的時候,都會保證調用其postProcessAfterInitialization方法(spring為什麼一定會調用這個方法,後續會回答)

Spring 事務源碼解析 → 它是如何保證同個事務的

InfrastructureAdvisorAutoProxyCreator類的層次結構圖

  2、跟進postProcessAfterInitialization方法(在類AbstractAutoProxyCreator中,注意看繼承關系),對指定的、我們定義的bean進行封裝,封裝的工作委托給了調用了wrapIfNecessary(bean, beanName, cacheKey)方法

Spring 事務源碼解析 → 它是如何保證同個事務的
Spring 事務源碼解析 → 它是如何保證同個事務的

  3、跟進wrapIfNecessary方法,發現主要做了兩件事:(1)找出指定bean對應的增強器、(2)根據找出的增強器建立代理

Spring 事務源碼解析 → 它是如何保證同個事務的
Spring 事務源碼解析 → 它是如何保證同個事務的

  (1)找出指定bean對應的增強器

  跟進getAdvicesAndAdvisorsForBean(bean.getClass(), beanName, null)(目前正處于AbstractAutoProxyCreator中,而AbstractAdvisorAutoProxyCreator是AbstractAutoProxyCreator的子類、是InfrastructureAdvisorAutoProxyCreator的父類,而我們的起點類是InfrastructureAdvisorAutoProxyCreator,是以跟進到AbstractAdvisorAutoProxyCreator中去)

Spring 事務源碼解析 → 它是如何保證同個事務的
Spring 事務源碼解析 → 它是如何保證同個事務的

  跟進findEligibleAdvisors(beanClass, beanName)方法,它主要做了兩件事:(a)擷取所有增強器、(b)增強器是否比對

Spring 事務源碼解析 → 它是如何保證同個事務的
Spring 事務源碼解析 → 它是如何保證同個事務的

  (a)擷取所有增強器,

   跟進findCandidateAdvisors(),接着跟進this.advisorRetrievalHelper.findAdvisorBeans(),其中有這麼一句advisorNames = BeanFactoryUtils.beanNamesForTypeIncludingAncestors(this.beanFactory, Advisor.class, true, false); 那麼BeanFactoryUtils.beanNamesForTypeIncludingAncestors的作用是什麼了,作用就是擷取beanFactory容器中的全部的Advisor.class類的名字,而當我們知道了增強器在容器中的beanName時,擷取增強器就不是問題了,        BeanFoctory中提供了方法:<T> T getBean(String name, Class<T> requiredType) throws BeansException;

  不知道大家是否還記得BeanFactoryTransactionAttributeSourceAdvisor,看名字就知道他是一個Advisor,那麼他也理所當然的被提取出來,并随着其他增強器一起在後續的步驟中被織入到代理中

Spring 事務源碼解析 → 它是如何保證同個事務的
Spring 事務源碼解析 → 它是如何保證同個事務的
Spring 事務源碼解析 → 它是如何保證同個事務的

BeanFactoryTransactionAttributeSourceAdvisor類的層次結構圖

  (b)增強器是否比對

     當找出所有的增強器後,接下來的任務就是看這些增強器是否與對應的class比對了,當然不止class,class内部的方法如果比對也可以通過;我們回到步驟4,跟進findAdvisorsThatCanApply(candidateAdvisors, beanClass, beanName);我們接着跟進AopUtils.findAdvisorsThatCanApply(candidateAdvisors, beanClass); 首先處理引介增強,引介是一種特殊的增強,它為類添加一些屬性和方法,我們可以不用管;然後對于普通bean的處理,這是我們需要關注的

  public static List<Advisor> findAdvisorsThatCanApply(List<Advisor> candidateAdvisors, Class<?> clazz)

Spring 事務源碼解析 → 它是如何保證同個事務的
Spring 事務源碼解析 → 它是如何保證同個事務的

  (b).1、跟進canApply(candidate, clazz, hasIntroductions),由于我們隻關注我們自己的加了事務的類,那麼clazz就代表這個類(這裡具體到類的話,就是我上篇部落格附件中的類DaoImpl),candidate就代表BeanFactoryTransactionAttributeSourceAdvisor(隻關注我們需要關注的,簡化難度);根據BeanFactoryTransactionAttributeSourceAdvisor類的層次結構圖,我們知道,BeanFactoryTransactionAttributeSourceAdvisor間接實作了PointcutAdvisor,那麼在canApply方法中的第二個if判斷時就會通過,會将BeanFactoryTransactionAttributeSourceAdvisor中的getPoint()方法的傳回值作為參數繼續調用canApply方法,那麼getPoint()方法傳回的是什麼了,我們跟進去看看。

  public static boolean canApply(Advisor advisor, Class<?> targetClass, boolean hasIntroductions)

Spring 事務源碼解析 → 它是如何保證同個事務的
Spring 事務源碼解析 → 它是如何保證同個事務的

   (b).2、跟進BeanFactoryTransactionAttributeSourceAdvisor的getPoint()方法,發現傳回的是TransactionAttributeSourcePointcut類型的執行個體,對于transactionAttributeSource屬性大家還有印象嗎,在自定義标簽的時候注入進來的(步驟1),是AnnotationTransactionAttributeSource的執行個體

   我們回到步驟(b).1的canApply方法,繼續跟進第二個if中的canApply方法,此時此方法的參數Pointcut pc表示的TransactionAttributeSourcePointcut。此方法先擷取MethodMatcher:MethodMatcher methodMatcher = pc.getMethodMatcher(),通過代碼跟進就發現,pc.getMethodMatcher()傳回的就是自身(TransactionAttributeSourcePointcut父類StaticMethodMatcherPointcut中的getMethodMatcher()),也就是pc自己;接着進行引介的處理,我們不需要關注他;接着擷取目标類(這裡具體到類的話,就是我上篇部落格附件中的類DaoImpl)的全部接口(Set<Class> classes = new LinkedHashSet<Class>(ClassUtils.getAllInterfacesForClassAsSet(targetClass)))以及目标類(classes.add(targetClass);),都放進set集合classes中;然後對classes進行周遊,周遊過程中又對類中的方法進行周遊,一旦比對成功,就任務目前目标類适用于目前增強器(就是說DaoImpl适用于BeanFactoryTransactionAttributeSourceAdvisor)。

  public static boolean canApply(Pointcut pc, Class<?> targetClass, boolean hasIntroductions)

Spring 事務源碼解析 → 它是如何保證同個事務的
Spring 事務源碼解析 → 它是如何保證同個事務的

  我們都知道事務的配置不僅僅局限于在方法上設定,類、接口上事務的配置可以延伸到類中的每個方法,那麼,如果針對每個方法進行檢測,在類本身上配置的事務屬性豈不是檢測不到了嗎?帶着疑問我們繼續跟進matches方法

  (b).3、methodMatcher.matches(method, targetClass)用到的是TransactionAttributeSourcePointcut類的matches方法

Spring 事務源碼解析 → 它是如何保證同個事務的
Spring 事務源碼解析 → 它是如何保證同個事務的

  跟進computeTransactionAttribute,提取事務标簽,内容已經很明了,如果方法中存在事務屬性,則使用方法上的屬性,否則使用方法所在的類上的屬性,如果方法所在的類的屬性上還是沒有搜尋到對應的事務屬性,那麼再搜尋接口中的方法,再沒有的話,最後嘗試搜尋接口的類上面的聲明

  private TransactionAttribute computeTransactionAttribute(Method method, Class<?> targetClass)

Spring 事務源碼解析 → 它是如何保證同個事務的
Spring 事務源碼解析 → 它是如何保證同個事務的

  具體的搜尋事務屬性的任務委托給了findTransactionAttribute方法,跟進進去,來到determineTransactionAttribute方法,繼續進去parseTransactionAnnotation方法(SpringTransactionAnnotationParser類的方法),進行事務注解解析

  注解屬性的解析就不跟進去了,大家有興趣的可以自己去跟;

   至此,我們完成了事務标簽的解析。再回顧下,我們現在的任務是找出某個增強器是否适合于對應的類 ,而是否比對的關鍵則在于是否從指定的類或類的方法中找到對應的事務屬性,現在,我們以DaoImpl為例,已經在他的方法上找到了事務屬性,是以,他是與事務增強器比對的,也就是說他會被事務功能修飾。

  (2)根據找出的增強器建立代理

    這裡我就不跟了,大家自己去跟下

  事務增強器BeanFactoryTransactionAttributeSourceAdvisor作為Advisor的實作類,自然要遵從Advisor的處理方式(處理方式是什麼,後面會有解答),當代理被調用時會調用這個類的增強方法,也就是此bean的Advise,又因為在解析事務标簽時我們把TransactionInterceptor類型的bean注入到了BeanTransactionAttributeSourceAdvisor中,是以,在調用事務增強器增強的代理類時會首先執行TransactionInterceptor進行增強,同時,也就是在TransactionInterceptor類中的invoke方法中完成整個事務的邏輯(可以去看下aop源碼實作,這裡就不做闡述了,篇幅已經很大了)

  從TransactionInterceptor(步驟1有此類的出現)的invoke方法開始,跟進到invokeWithinTransaction方法,包括聲明式事務處理以及程式設計式事務處理,我們這裡隻看聲明式事務,包括事務建立,執行被增強方法(DaoImpl加了事務的方法)、異常復原、送出事務。

  protected Object invokeWithinTransaction(Method method, Class targetClass, final InvocationCallback invocation) throws Throwable

Spring 事務源碼解析 → 它是如何保證同個事務的
Spring 事務源碼解析 → 它是如何保證同個事務的

  1、事務建立

    跟進createTransactionIfNecessary,接着跟進status = tm.getTransaction(txAttr); 可以看裡面相應的注釋來了解這個方法做了什麼

Spring 事務源碼解析 → 它是如何保證同個事務的
Spring 事務源碼解析 → 它是如何保證同個事務的

    那麼我們把目光聚集在我們需要關注的點:doBegin方法上(DataSourceTransactionManager類中)

Spring 事務源碼解析 → 它是如何保證同個事務的
Spring 事務源碼解析 → 它是如何保證同個事務的

    從doBegin方法中,我們其實已經找到了最初問題(spring如何保證一個事務中的jdbc connection是同一個)的答案,就是将連接配接綁定到線程,那麼同一個線程中用到的就是同一個connection;到了這裡不知道大家有沒有這樣一個疑問:一個業務進行中需要對資料庫進行多次操作,每次資料庫操作都重新從連接配接池擷取connection,那麼将connection綁定到線程有什麼用,還是不能保證多次擷取的connection是同一個connection? 我自己也有過這樣的疑問,可是我們仔細回顧一下這個疑問,connection綁定到線程了,那麼每次擷取connection還是從連接配接池嗎? 不知道大家有沒有這麼考慮過;如果大家還沒想明白,我們就來跟下jdbc操作中connection的擷取是怎麼樣的

  我就不具體的一步一步的去跟進了,我們來看一下關鍵代碼,JdbcTemplate類中有個execute方法,他是執行資料庫操作的核心,這裡我們也隻關注資料庫連接配接的擷取,其他的大家自己去閱讀就好了

  public <T> T execute(PreparedStatementCreator psc, PreparedStatementCallback<T> action) throws DataAccessException

Spring 事務源碼解析 → 它是如何保證同個事務的
Spring 事務源碼解析 → 它是如何保證同個事務的

  我們跟進execute方法中的Connection con = DataSourceUtils.getConnection(getDataSource()); 去看看到底連接配接是怎麼擷取的,跟進發現connection擷取的操作交給了 doGetConnection(dataSource),繼續跟進

  public static Connection doGetConnection(DataSource dataSource) throws SQLException

  我們發現,如果目前線程存在connection,說明目前業務需要事務,那麼就将目前線程中的connection傳回回去,否則就從連接配接池中擷取connection并傳回回去;到這裡,相信大家就明白了。

  篇幅不小,一不小心可能就跟丢了,我這裡再總結一下,回顧下事務源碼實作的整個流程

  1、事務标簽的解析,注冊事務相關的類到容器中,代理類以及事務功能的三個bean:BeanFactoryTransactionAttributeSourceAdvisor、AnnotationTransactionAttributeSource、TransactionInterceptor

  2、找出指定bean對應的增強器,具體一點,就是找到DaoImpl的增強器BeanFactoryTransactionAttributeSourceAdvisor,也就是說BeanFactoryTransactionAttributeSourceAdvisor适用于DaoImpl

  3、建立代理,将增強方法與目标方法集合,實作增強效果,實際上就是将TransactionInterceptor與DaoImpl結合,對DaoImpl的目标方法(@Transactional标注的,當然不止這一種方式)進行around增強,這裡有一個很重要的操作:将connection綁定到目前線程

  4、jdbc操作,先判斷目前線程是否有connection,有則直接傳回,那麼就保證了目标方法中的所有資料庫操作用的是同一個connection,否則則會每次資料庫操作都從連接配接池中擷取connection

  一旦事務建立成功,那麼我們擷取的是目标bean的代理,而不是目标bean對應的類生成的bean,代理在目标bean的基礎上進行了around增強;

  事務也是aop實作的一種具體表現,如果大家對aop有所了解的話,那麼事務應該很好了解,如果大家對aop沒有了解,那麼我建議大家現在就去了解(最好将aop的源碼讀一遍),再回過頭來看這篇部落格,那麼就很好了解了;

  可能有人會有這樣的疑問:你說了這麼多,也就隻是保證所有資料庫操作用的是同一個connection,這可不能算事務完成了,事務的内容可不止這些,包括各種事務的送出、復原等等;事務的送出、復原等,spring管理起來了,無需我們管理,這也是spring事務帶給我們的便利;另外,本部落格旨在說明spring一個事務中的jdbc connection是同一個,至于其他的就需要大家自己去閱讀源碼了

  部落格中提到了3個問題:1、有事務與沒事務,connection的擷取方式是一樣的嗎,2、事務自定義标簽解析,為什麼是從AnnotationDrivenBeanDefinitionParser的parse方法開始,3、spring在執行個體化bean的時候,為什麼會調用postProcessAfterInitialization方法

  關于問題1:其實這個問題應該是這樣的:有事務與沒事務,jdbc操作中connection擷取的方式是一樣的嗎,我想這個大家應該已經有答案了,兩種情形共用的是一套相同的擷取connection的代碼,就是上文中提到的doGetConnection方法,那麼擷取方式肯定相同啦

  關于問題2:spring配置檔案中,有一個配置<tx:annotation-driven />,此配置是事務的開關;大家可以打斷點去跟下标簽的解析,包括預設标簽、自定義标簽,找到如下方法,跟進parseCustomElement方法就能找到答案了

  關于問題3,這個也得需要大家去跟源代碼,當你找到如下源代碼的時候,你就會找到答案了

  本部落格篇幅不小,沒讀過spring源碼的人肯定是看不懂的,是以需要大家去看spring的源代碼;建議大家看源代碼的時候最後配合斷點調試,來跟蹤代碼,如果直接跟源代碼的話,很容易跟丢;另外,如果大家覺得看全英文的比較吃力,那麼就配合着中文書籍來看,<<spring源碼深度解析>>這本書個人覺得,隻要你能耐心看的話,是一本不錯的書!

  <<spring源碼深度解析>>

   主題:Spring源代碼解析(六):Spring聲明式事務處理

  Java程式員從笨鳥到菜鳥之(七十四)細談Spring(六)spring之AOP基本概念和配置詳解

繼續閱讀