Spring 事務機制簡述

概述

事務管理對於企業應用來說是至關重要的,即使出現異常情況,它也可以保證數據的一致性。
Spring Framework對事務管理提供了一致的抽象,其特點如下:

  • 爲不同的事務API提供一致的編程模型,比如JTA(Java Transaction API), JDBC, Hibernate, JPA(Java Persistence API和JDO(Java Data Objects)
  • 支持聲明式事務管理,特別是基於註解的聲明式事務管理,簡單易用
  • 提供比其他事務API如JTA更簡單的編程式事務管理API
  • 與spring數據訪問抽象的完美集成

編程式事務管理使用TransactionTemplate或者直接使用底層的PlatformTransactionManager。對於編程式事務管理,spring推薦使用TransactionTemplate。

Spring聲明式事務讓我們從複雜的事務處理中得到解脫。使得我們再也無需要去處理獲得連接、關閉連接、事務提交和回滾等這些操作。再也無需要我們在與事務相關的方法中處理大量的try…catch…finally代碼。我們在使用Spring聲明式事務時,有一個非常重要的概念就是事務屬性。事務屬性通常由事務的傳播行爲,事務的隔離級別,事務的超時值和事務只讀標誌組成。我們在進行事務劃分時,需要進行事務定義,也就是配置事務的屬性。

聲明式事務管理建立在AOP之上的。其本質是對方法前後進行攔截,然後在目標方法開始之前創建或者加入一個事務,在執行完目標方法之後根據執行情況提交或者回滾事務。聲明式事務最大的優點就是不需要通過編程的方式管理事務,這樣就不需要在業務邏輯代碼中摻雜事務管理的代碼,只需在配置文件中做相關的事務規則聲明(或通過基於@Transactional註解的方式),便可以將事務規則應用到業務邏輯中。

顯然聲明式事務管理要優於編程式事務管理,這正是spring倡導的非侵入式的開發方式。聲明式事務管理使業務代碼不受污染,一個普通的POJO對象,只要加上註解就可以獲得完全的事務支持。和編程式事務相比,聲明式事務唯一不足地方是,後者的最細粒度只能作用到方法級別,無法做到像編程式事務那樣可以作用到代碼塊級別。但是即便有這樣的需求,也存在很多變通的方法,比如,可以將需要進行事務管理的代碼塊獨立爲方法等等。

聲明式事務管理也有兩種常用的方式,一種是基於tx和aop名字空間的xml配置文件,另一種就是基於@Transactional註解如果同時配置這兩種事務管理方式,優先使用基於@Transactional註解的方式。顯然基於註解的方式更簡單易用,更清爽。

下面分別詳細講解,事務的四種屬性,僅供諸位學習參考:

Spring在TransactionDefinition接口中定義這些屬性,以供PlatfromTransactionManager使用, PlatfromTransactionManager是spring事務管理的核心接口。

           public interface TransactionDefinition {

int getPropagationBehavior();//返回事務的傳播行爲。
int getIsolationLevel();//返回事務的隔離級別,事務管理器根據它來控制另外一個事務可以看到本事務內的哪些數據。
int getTimeout();//返回事務必須在多少秒內完成。
boolean isReadOnly();//事務是否只讀,事務管理器能夠根據這個返回值進行優化,確保事務是隻讀的。
}

1. TransactionDefinition接口中定義五個隔離級別:

  • TransactionDefinition.ISOLATION_DEFAULT:這是默認值,表示使用底層數據庫的默認隔離級別。對大部分數據庫而言,通常這值就是TransactionDefinition.ISOLATION_READ_COMMITTED。另外四個與JDBC的隔離級別相對應;
  • TransactionDefinition.ISOLATION_READ_UNCOMMITTED:該隔離級別表示一個事務可以讀取另一個事務修改但還沒有提交的數據。該級別不能防止髒讀,不可重複讀和幻讀,因此很少使用該隔離級別。比如PostgreSQL實際上並沒有此級別。
  • TransactionDefinition.ISOLATION_READ_COMMITTED:該隔離級別表示一個事務只能讀取另一個事務已經提交的數據。該級別可以防止髒讀,這也是大多數情況下的推薦值。
  • TransactionDefinition.ISOLATION_REPEATABLE_READ:該隔離級別表示一個事務在整個過程中可以多次重複執行某個查詢,並且每次返回的記錄都相同。該級別可以防止髒讀和不可重複讀。
  • TransactionDefinition.ISOLATION_SERIALIZABLE:所有的事務依次逐個執行,這樣事務之間就完全不可能產生干擾,也就是說,該級別可以防止髒讀、不可重複讀以及幻讀。但是這將嚴重影響程序的性能。通常情況下也不會用到該級別。

2. 在TransactionDefinition接口中定義了七個事務傳播行爲:

(1)PROPAGATION_REQUIRED 如果存在一個事務,則支持當前事務。如果沒有事務則開啓一個新的事務。

(2)PROPAGATION_SUPPORTS 如果存在一個事務,支持當前事務。如果沒有事務,則非事務的執行。但是對於事務同步的事務管理器,PROPAGATION_SUPPORTS與不使用事務有少許不同。

(3)PROPAGATION_MANDATORY 如果已經存在一個事務,支持當前事務。如果沒有一個活動的事務,則拋出異常。

(4)PROPAGATION_REQUIRES_NEW 總是開啓一個新的事務。如果一個事務已經存在,則將這個存在的事務掛起。

(5)PROPAGATION_NOT_SUPPORTED  總是非事務地執行,並掛起任何存在的事務。使用PROPAGATION_NOT_SUPPORTED,也需要使用JtaTransactionManager作爲事務管理器。

(6)PROPAGATION_NEVER 總是非事務地執行,如果存在一個活動事務,則拋出異常;

(7)PROPAGATION_NESTED如果一個活動的事務存在,則運行在一個嵌套的事務中. 如果沒有活動事務, 則按TransactionDefinition.PROPAGATION_REQUIRED 屬性執行。這是一個嵌套事務,需要JDBC 驅動的java.sql.Savepoint類。使用PROPAGATION_NESTED,還需要把PlatformTransactionManager的nestedTransactionAllowed屬性設爲true;而 nestedTransactionAllowed屬性值默認爲false;

PROPAGATION_NESTED 與PROPAGATION_REQUIRES_NEW的區別:它們非常類似,都像一個嵌套事務,如果不存在一個活動的事務,都會開啓一個新的事務。使用 PROPAGATION_REQUIRES_NEW時,內層事務與外層事務就像兩個獨立的事務一樣,一旦內層事務進行了提交後,外層事務不能對其進行回滾。兩個事務互不影響。兩個事務不是一個真正的嵌套事務。

PROPAGATION_REQUIRES_NEW 啓動一個新的, 不依賴於環境的 "內部" 事務. 這個事務將被完全 commited 或 rolled back 而不依賴於外部事務, 它擁有自己的隔離範圍, 自己的鎖, 等等. 當內部事務開始執行時, 外部事務將被掛起, 內務事務結束時, 外部事務將繼續執行。

另一方面, PROPAGATION_NESTED 開始一個 "嵌套的" 事務,  它是已經存在事務的一個真正的子事務. 套事務開始執行時,  它將取得一個 savepoint. 如果這個嵌套事務失敗, 我們將回滾到此 savepoint. 嵌套事務是外部事務的一部分, 只有外部事務結束後它纔會被提交。

3. 事務超時

所謂事務超時,就是指一個事務所允許執行的最長時間,如果超過該時間限制但事務還沒有完成,則自動回滾事務。在 TransactionDefinition 中以 int 的值來表示超時時間,其單位是秒。默認設置爲底層事務系統的超時值,如果底層數據庫事務系統沒有設置超時值,那麼就是none,沒有超時限制。

4. 事務只讀屬性

只讀事務用於客戶代碼只讀但不修改數據的情形,只讀事務用於特定情景下的優化,比如使用Hibernate的時候。默認爲讀寫事務。

5. 事務回滾規則

指示spring事務管理器回滾一個事務的推薦方法是在當前事務的上下文內拋出異常。spring事務管理器會捕捉任何未處理的異常,然後依據規則決定是否回滾拋出異常的事務。

默認配置下,spring只有在拋出的異常爲運行時unchecked異常時纔回滾該事務,也就是拋出的異常爲RuntimeException的子類(Errors也會導致事務回滾),而拋出checked異常則不會導致事務回滾。可以明確的配置在拋出那些異常時回滾事務,包括checked異常。也可以明確定義那些異常拋出時不回滾事務。

還可以編程性的通過setRollbackOnly()方法來指示一個事務必須回滾,在調用完setRollbackOnly()後你所能執行的唯一操作就是回滾。


@Transactional註解

屬性 類型 描述
value String 可選的限定描述符,指定使用的事務管理器
propagation enum: Propagation 可選的事務傳播行爲設置
isolation enum: Isolation 可選的事務隔離級別設置
readOnly boolean 讀寫或只讀事務,默認讀寫
timeout int (in seconds granularity) 事務超時時間設置
rollbackFor Class對象數組,必須繼承自Throwable 導致事務回滾的異常類數組
rollbackForClassName 類名數組,必須繼承自Throwable 導致事務回滾的異常類名字數組
noRollbackFor Class對象數組,必須繼承自Throwable 不會導致事務回滾的異常類數組
noRollbackForClassName 類名數組,必須繼承自Throwable 不會導致事務回滾的異常類名字數組

用法

@Transactional 可以作用於接口、接口方法、類以及類方法上。當作用於類上時,該類的所有 public 方法將都具有該類型的事務屬性,同時,我們也可以在方法級別使用該標註來覆蓋類級別的定義。

雖然 @Transactional 註解可以作用於接口、接口方法、類以及類方法上,但是 Spring 建議不要在接口或者接口方法上使用該註解,因爲這隻有在使用基於接口的代理時它纔會生效。另外, @Transactional 註解應該只被應用到 public 方法上,這是由 Spring AOP 的本質決定的。如果你在 protected、private 或者默認可見性的方法上使用 @Transactional 註解,這將被忽略,也不會拋出任何異常。

默認情況下,只有來自外部的方法調用纔會被AOP代理捕獲,也就是,類內部方法調用本類內部的其他方法並不會引起事務行爲,即使被調用方法使用@Transactional註解進行修飾。

@Transactional(readOnly = true)
public class DefaultFooService implements FooService {
 
  public Foo getFoo(String fooName) {
    // do something
  }
 
  // these settings have precedence for this method
  //方法上註解屬性會覆蓋類註解上的相同屬性
  @Transactional(readOnly = false, propagation = Propagation.REQUIRES_NEW)
  public void updateFoo(Foo foo) {
    // do something
  }
}


自動提交(AutoCommit)與連接關閉時的是否自動提交

自動提交

默認情況下,數據庫處於自動提交模式。每一條語句處於一個單獨的事務中,在這條語句執行完畢時,如果執行成功則隱式的提交事務,如果
執行失敗則隱式的回滾事務。

對於正常的事務管理,是一組相關的操作處於一個事務之中,因此必須關閉數據庫的自動提交模式。不過,這個我們不用擔心,spring會將底層連接的自動提交特性設置爲false。
org/springframework/jdbc/datasource/DataSourceTransactionManager.java

// switch to manual commit if necessary. this is very expensive in some jdbc drivers,
// so we don't want to do it unnecessarily (for example if we've explicitly
// configured the connection pool to set it already).
if (con.getautocommit()) {
    txobject.setmustrestoreautocommit(true);
    if (logger.isdebugenabled()) {
        logger.debug("switching jdbc connection [" + con + "] to manual commit");
    }
    con.setautocommit(false);
}

有些數據連接池提供了關閉事務自動提交的設置,最好在設置連接池時就將其關閉。但C3P0沒有提供這一特性,只能依靠spring來設置。
因爲JDBC規範規定,當連接對象建立時應該處於自動提交模式,這是跨DBMS的缺省值,如果需要,必須顯式的關閉自動提交。C3P0遵守這一規範,讓客戶代碼來顯式的設置需要的提交模式。

連接關閉時的是否自動提交

當一個連接關閉時,如果有未提交的事務應該如何處理?JDBC規範沒有提及,C3P0默認的策略是回滾任何未提交的事務。這是一個正確的策略,但JDBC驅動提供商之間對此問題並沒有達成一致。
C3P0的autoCommitOnClose屬性默認是false,沒有十分必要不要動它。或者可以顯式的設置此屬性爲false,這樣會更明確。

Spring事務配置的五種方式

Spring配置文件中關於事務配置總是由三個組成部分,分別是DataSource、TransactionManager和代理機制這三部分,無論哪種配置方式,一般變化的只是代理機制這部分。

DataSource、TransactionManager這兩部分只是會根據數據訪問方式有所變化,比如使用Hibernate進行數據訪問時,DataSource實際爲SessionFactory,TransactionManager的實現爲HibernateTransactionManager。

具體如下圖:

Spring事務配置 (2)


根據代理機制的不同,總結了五種Spring事務的配置方式,配置文件如下:

第一種方式:每個Bean都有一個代理

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi
="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:context
="http://www.springframework.org/schema/context"
    xmlns:aop
="http://www.springframework.org/schema/aop"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
           http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
           http://www.springframework.org/schema/context
           http://www.springframework.org/schema/context/spring-context-2.5.xsd
           http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.5.xsd"
>

   
<bean id="sessionFactory" 
            class
="org.springframework.orm.hibernate3.LocalSessionFactoryBean"> 
       
<propertyname="configLocation" value="classpath:hibernate.cfg.xml" /> 
       
<propertyname="configurationClass" value="org.hibernate.cfg.AnnotationConfiguration" />
   
</bean> 

   
<!-- 定義事務管理器(聲明式的事務)--> 
   
<bean id="transactionManager"
        class
="org.springframework.orm.hibernate3.HibernateTransactionManager">
       
<propertyname="sessionFactory" ref="sessionFactory" />
   
</bean>
   
   
<!-- 配置DAO-->
   
<bean id="userDaoTarget" class="com.bluesky.spring.dao.UserDaoImpl">
       
<propertyname="sessionFactory" ref="sessionFactory" />
   
</bean>
   
   
<bean id="userDao" 
        class
="org.springframework.transaction.interceptor.TransactionProxyFactoryBean"> 
          
<!-- 配置事務管理器--> 
          
<propertyname="transactionManager" ref="transactionManager" />    
       
<propertyname="target" ref="userDaoTarget" /> 
        
<propertyname="proxyInterfaces" value="com.bluesky.spring.dao.GeneratorDao" />
       
<!-- 配置事務屬性--> 
       
<propertyname="transactionAttributes"> 
           
<props> 
               
<propkey="*">PROPAGATION_REQUIRED</prop>
           
</props> 
       
</property> 
   
</bean> 
</beans>

 

第二種方式:所有Bean共享一個代理基類

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi
="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:context
="http://www.springframework.org/schema/context"
    xmlns:aop
="http://www.springframework.org/schema/aop"
    xsi:schemaLocation
="http://www.springframework.org/schema/beans
           http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
           http://www.springframework.org/schema/context
           http://www.springframework.org/schema/context/spring-context-2.5.xsd
           http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.5.xsd"
>

   
<bean id="sessionFactory" 
            class
="org.springframework.orm.hibernate3.LocalSessionFactoryBean"> 
       
<propertyname="configLocation" value="classpath:hibernate.cfg.xml" /> 
       
<propertyname="configurationClass" value="org.hibernate.cfg.AnnotationConfiguration" />
   
</bean> 

   
<!-- 定義事務管理器(聲明式的事務)--> 
   
<bean id="transactionManager"
        class
="org.springframework.orm.hibernate3.HibernateTransactionManager">
       
<propertyname="sessionFactory" ref="sessionFactory" />
   
</bean>
   
   
<beanid="transactionBase" 
            class
="org.springframework.transaction.interceptor.TransactionProxyFactoryBean" 
            lazy-init
="true" abstract="true"> 
       
<!-- 配置事務管理器--> 
       
<propertyname="transactionManager" ref="transactionManager" /> 
       
<!-- 配置事務屬性--> 
       
<propertyname="transactionAttributes"> 
           
<props> 
               
<propkey="*">PROPAGATION_REQUIRED</prop> 
           
</props> 
       
</property> 
   
</bean>   
  
   
<!-- 配置DAO-->
   
<bean id="userDaoTarget" class="com.bluesky.spring.dao.UserDaoImpl">
       
<propertyname="sessionFactory" ref="sessionFactory" />
   
</bean>
   
   
<beanid="userDao" parent="transactionBase" > 
       
<propertyname="target" ref="userDaoTarget" />  
   
</bean>
</beans>


第三種方式:使用攔截器

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi
="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:context
="http://www.springframework.org/schema/context"
    xmlns:aop
="http://www.springframework.org/schema/aop"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
           http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
           http://www.springframework.org/schema/context
           http://www.springframework.org/schema/context/spring-context-2.5.xsd
           http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.5.xsd"
>

   
<bean id="sessionFactory" 
            class
="org.springframework.orm.hibernate3.LocalSessionFactoryBean"> 
       
<propertyname="configLocation" value="classpath:hibernate.cfg.xml" /> 
       
<propertyname="configurationClass" value="org.hibernate.cfg.AnnotationConfiguration" />
   
</bean> 

   
<!-- 定義事務管理器(聲明式的事務)--> 
   
<bean id="transactionManager"
        class
="org.springframework.orm.hibernate3.HibernateTransactionManager">
       
<propertyname="sessionFactory" ref="sessionFactory" />
   
</bean> 
  
   
<beanid="transactionInterceptor" 
        class
="org.springframework.transaction.interceptor.TransactionInterceptor"> 
       
<propertyname="transactionManager" ref="transactionManager" /> 
       
<!-- 配置事務屬性--> 
       
<propertyname="transactionAttributes"> 
           
<props> 
               
<propkey="*">PROPAGATION_REQUIRED</prop> 
           
</props> 
       
</property> 
   
</bean>
     
   
<bean class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator"> 
       
<propertyname="beanNames"> 
           
<list> 
               
<value>*Dao</value>
            </list> 
       
</property> 
       
<propertyname="interceptorNames"> 
           
<list> 
               
<value>transactionInterceptor</value> 
           
</list> 
       
</property> 
   
</bean> 
 
   
<!-- 配置DAO-->
   
<bean id="userDao" class="com.bluesky.spring.dao.UserDaoImpl">
       
<propertyname="sessionFactory" ref="sessionFactory" />
   
</bean>
</beans>


第四種方式:使用tx標籤配置的攔截器

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi
="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:context
="http://www.springframework.org/schema/context"
    xmlns:aop
="http://www.springframework.org/schema/aop"
    xmlns:tx
="http://www.springframework.org/schema/tx"
    xsi:schemaLocation
="http://www.springframework.org/schema/beans
           http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
           http://www.springframework.org/schema/context
           http://www.springframework.org/schema/context/spring-context-2.5.xsd
           http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.5.xsd
           http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.5.xsd"
>

   
<context:annotation-config/>
   
<context:component-scanbase-package="com.bluesky" />

   
<bean id="sessionFactory" 
            class
="org.springframework.orm.hibernate3.LocalSessionFactoryBean"> 
       
<propertyname="configLocation" value="classpath:hibernate.cfg.xml" /> 
       
<propertyname="configurationClass" value="org.hibernate.cfg.AnnotationConfiguration" />
   
</bean> 

   
<!-- 定義事務管理器(聲明式的事務)--> 
   
<bean id="transactionManager"
        class
="org.springframework.orm.hibernate3.HibernateTransactionManager">
       
<propertyname="sessionFactory" ref="sessionFactory" />
   
</bean>

   
<tx:adviceid="txAdvice" transaction-manager="transactionManager">
       
<tx:attributes>
           
<tx:methodname="*" propagation="REQUIRED" />
       
</tx:attributes>
   
</tx:advice>
   
   
<aop:config>
       
<aop:pointcutid="interceptorPointCuts"
            expression
="execution(* com.bluesky.spring.dao.*.*(..))" />
       
<aop:advisoradvice-ref="txAdvice"
            pointcut-ref
="interceptorPointCuts" />       
   
</aop:config>     
</beans>


第五種方式:全註解

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi
="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:context
="http://www.springframework.org/schema/context"
    xmlns:aop
="http://www.springframework.org/schema/aop"
    xmlns:tx
="http://www.springframework.org/schema/tx"
    xsi:schemaLocation
="http://www.springframework.org/schema/beans
           http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
           http://www.springframework.org/schema/context
           http://www.springframework.org/schema/context/spring-context-2.5.xsd
           http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.5.xsd
           http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.5.xsd"
>

   
<context:annotation-config/>
   
<context:component-scanbase-package="com.bluesky" />

   
<tx:annotation-driventransaction-manager="transactionManager"/>

   
<bean id="sessionFactory" 
            class
="org.springframework.orm.hibernate3.LocalSessionFactoryBean"> 
       
<propertyname="configLocation" value="classpath:hibernate.cfg.xml" /> 
       
<propertyname="configurationClass" value="org.hibernate.cfg.AnnotationConfiguration" />
   
</bean> 

   
<!-- 定義事務管理器(聲明式的事務)--> 
   
<bean id="transactionManager"
        class
="org.springframework.orm.hibernate3.HibernateTransactionManager">
       
<propertyname="sessionFactory" ref="sessionFactory" />
   
</bean>
   
</beans>

此時在DAO上需加上@Transactional註解,如下:

package com.bluesky.spring.dao;

import java.util.List;

import org.hibernate.SessionFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.orm.hibernate3.support.HibernateDaoSupport;
import org.springframework.stereotype.Component;

import com.bluesky.spring.domain.User;

@Transactional
@Component(
"userDao")
public class UserDaoImplextends HibernateDaoSupportimplements UserDao {

   
public List<User> listUsers() {
       
return this.getSession().createQuery("from User").list();
    }
   
   
}

關於攔截方法調用其他內部方法無法被攔截問題的解決

攔截器的實現原理很簡單,就是動態代理,實現AOP機制。當外部調用被攔截bean的攔截方法時,可以選擇在攔截之前或者之後等條件執行攔截方法之外的邏輯,比如特殊權限驗證,參數修正等操作。但是如果現在一個需求是,當外部調用攔截bean的時候,不但要執行攔截當前方法,如果當前方法內部同時調用了其他內部方法,也要被攔截。按照目前的攔截器實現邏輯,是無法攔截當前方法內部調用的方法的,這樣說有點抽象,看一個代碼:

public class BeanA{  
public void method1(){  
method2();  
}  
public void method2(){  
...  
}  
}

當外部調用beanA.method1();的時候,攔截器執行攔截邏輯,執行完畢後進入method1方法執行,當調用method2的時候,攔截器是否會再次攔截?是不會的,這裏涉及到攔截器的一個原理,攔截器涉及兩個對象,代理對象和原始對象,攔截器所執行的代理對象執行完畢後,當執行method1即進入了原始對象,那麼在原始對象中調用method2,是無法進行攔截的。所以很顯眼,這樣無法滿足我們的需求。

在spring的源代碼中通過一個增強對象的檢查,控制了當前的內部調用是否使用代理來執行,這讓人感到無奈。spring的作者們很隱晦的提出避免內部調用的方法。

我們可能會想,在外部調用兩次beanA,第一次調用method1,第二次調用method2,這樣做可以解決問題,但是這樣的直接後果是我們的邏輯代碼將變得紊亂,並非所有的場景下都可以通過這樣的設計來完成。雖然這是spring官方推薦的避免內部調用的idea。

查看了相關資料,得到了一種方法,即在method1的內部,通過直接獲取當前代理對象的方式然後通過代理對象調用method2,這樣觸發攔截。

看看代碼:

    public void method1(){  
        logger.error("1");  
          
        // 如果希望調用的內部方法也被攔截,那麼必須用過上下文獲取代理對象執行調用,而不能直接內部調用,否則無法攔截  
        if(null != AopContext.currentProxy()){  
            ((NorQuickNewsDAO)AopContext.currentProxy()).method2();  
        }else{  
            method2();  
        }         
    }  
      
    public void method2(){  
        logger.error("2");  
    }

我們顯示的調用了AopContext來獲取當前代理對象,然後調用其方法,這樣做還必須的一個步驟是將當前的代理暴露給線程使用,在配置文件中需要配置一個參數:

 <property name="exposeProxy">  
       <value>true</value>  
 </property>

它是ProxyConfig的一個參數,默認是false,如果不設置這個參數,那麼上述java代碼將無法獲取當前線程中的代理對象。

這種方法可以成功觸發攔截,但是也帶來了其他問題,比如代碼的織入,我們的代碼將變得複雜而且晦澀,而且嚴格要求系統針對於當前的bean必須配置攔截器,否則會因爲找不到攔截器而拋出異常。

這樣做有什麼負面影響?對事務的影響,對安全的影響,現在不得而知,還需要逐步去測試以嘗試。


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