未找到出處,作者發現請告訴我
由於Spring事務管理是基於接口代理或動態字節碼技術,通過AOP實施事務增強的。雖然Spring還支持AspectJ LTW在類加載期實施增強,但這種方法很少使用,所以我們不予關注。
對於基於接口動態代理的AOP事務增強來說,由於接口的方法都必然是public的,這就要求實現類的實現方法也必須是public的(不能是protected、private等),同時不能使用static的修飾符。所以,可以實施接口動態代理的方法只能是使用“public”或“public final”修飾符的方法,其他方法不可能被動態代理,相應的也就不能實施AOP增強,換句話說,即不能進行Spring事務增強了。
基於CGLib字節碼動態代理的方案是通過擴展被增強類,動態創建其子類的方式進行AOP增強植入的。由於使用final、static、private修飾符的方法都不能被子類覆蓋,相應的,這些方法將無法實施AOP增強。所以方法簽名必須特別注意這些修飾符的使用,以免使方法不小心成爲事務管理的漏網之魚。
事務增強遺漏實例
本節中,我們通過具體的實例說明基於CGLib字節碼動態代理無法享受Spring AOP事務增強的特殊方法。
Spring通過CGLib動態代理技術對UserService Bean實施AOP事務增強的關鍵配置,具體如下所示:
在①處,我們通過proxy-target-class="true"顯式使用CGLib動態代理技術,在②處通過AspjectJ切點表達式表達UserService所有的方法,希望對UserService所有方法都實施Spring AOP事務增強。
在UserService添加一個可執行的方法,如下所示:
在運行UserService之前,將Log4J日誌級別設置爲DEBUG,運行以上代碼查看輸出日誌,如下所示:
①未啓用事務
before method1
in method1
after method1
②未啓用事務
before method2
in method2
after method2
③未啓用事務
before method3
in method3
after method3
④啓用事務
before method4
Creating new transaction with name [com.baobaotao.special.UserService.method4]: PROPAGATION_REQUIRED,ISOLATION_DEFAULT
…
in method4
Initiating transaction commit
Committing JDBC transaction on Connection [jdbc:mysql://localhost:3306/sampledb, UserName=root@localhost, MySQL-AB JDBC Driver]
Releasing JDBC Connection [jdbc:mysql://localhost:3306/sampledb, UserName=root@localhost, MySQL-AB JDBC Driver] after transaction
Returning JDBC Connection to DataSource
after method4
⑤未用事務
before method5
in method5
after method5
⑥啓用事務
before method6
Creating new transaction with name [com.baobaotao.special.UserService.method6]: PROPAGATION_REQUIRED,ISOLATION_DEFAULT
…
in method6
Initiating transaction commit
Committing JDBC transaction on Connection [jdbc:mysql://localhost:3306/sampledb, UserName=root@localhost, MySQL-AB JDBC Driver]
…
after method6
觀察以上輸出日誌,很容易發現method1~method3及method5這4個方法都沒有被實施Spring的事務增強,而method4和method6被實施了事務增強。這個結果驗證了我們前面的論述。
我們通過下表描述哪些特殊方法將成爲Spring AOP事務增強的漏網之魚。
不過,需要特別指出的是,這些不能被Spring事務增強的特殊方法並非就不工作在事務環境下。只要它們被外層的事務方法調用了,由於Spring事務管理的傳播級別,內部方法也可以工作在外部方法所啓動的事務上下文中。我們說,這些方法不能被Spring進行AOP事務增強,是指這些方法不能啓動事務,但是外層方法的事務上下文依舊可以順利地傳播到這些方法中。
這些不能被Spring事務增強的方法和可被Spring事務增強的方法唯一的區別在於“是否可以主動啓動一個新事務”:前者不能而後者可以。對於事務傳播行爲來說,二者是完全相同的,前者也和後者一樣不會造成數據連接的泄漏問題。換句話說,如果這些“特殊方法”被無事務上下文的方法調用,則它們就工作在無事務上下文中;反之,如果被具有事務上下文的方法調用,則它們就工作在事務上下文中。
對於private的方法,由於最終都會被public方法封裝後再開放給外部調用,而public方法是可以被事務增強的,所以基本上沒有什麼問題。在實際開發中,最容易造成隱患的是基於CGLib的動態代理時的“public static”和“public final”這兩種特殊方法。原因是它們本身是public的,因此可以直接被外部類(如Web層的Controller類)調用,只要調用者沒有事務上下文,這些特殊方法也就以無事務的方式運作。
由於Spring事務管理是基於接口代理或動態字節碼技術,通過AOP實施事務增強的。雖然Spring還支持AspectJ LTW在類加載期實施增強,但這種方法很少使用,所以我們不予關注。
對於基於接口動態代理的AOP事務增強來說,由於接口的方法都必然是public的,這就要求實現類的實現方法也必須是public的(不能是protected、private等),同時不能使用static的修飾符。所以,可以實施接口動態代理的方法只能是使用“public”或“public final”修飾符的方法,其他方法不可能被動態代理,相應的也就不能實施AOP增強,換句話說,即不能進行Spring事務增強了。
基於CGLib字節碼動態代理的方案是通過擴展被增強類,動態創建其子類的方式進行AOP增強植入的。由於使用final、static、private修飾符的方法都不能被子類覆蓋,相應的,這些方法將無法實施AOP增強。所以方法簽名必須特別注意這些修飾符的使用,以免使方法不小心成爲事務管理的漏網之魚。
事務增強遺漏實例
本節中,我們通過具體的實例說明基於CGLib字節碼動態代理無法享受Spring AOP事務增強的特殊方法。
- package com.baobaotao.special;
- import org.springframework.stereotype.Service;
- @Service("userService")
- public class UserService {
- //① private方法因訪問權限的限制,無法被子類覆蓋
- private void method1() {
- System.out.println("method1");
- }
- //② final方法無法被子類覆蓋
- public final void method2() {
- System.out.println("method2");
- }
- //③ static是類級別的方法,無法被子類覆蓋
- public static void method3() {
- System.out.println("method3");
- }
- //④ public方法可以被子類覆蓋,因此可以被動態字節碼增強
- public void method4() {
- System.out.println("method4");
- }
- }
Spring通過CGLib動態代理技術對UserService Bean實施AOP事務增強的關鍵配置,具體如下所示:
- …
- <aop:config proxy-target-class="true"><!-- ①顯式使用CGLib動態代理 -->
- <!-- ②希望對UserService所有方法實施事務增強 -->
- <aop:pointcut id="serviceJdbcMethod"
- expression="execution(* com.baobaotao.special.UserService.*(..))"/>
- <aop:advisor pointcut-ref="serviceJdbcMethod" advice-ref="jdbcAdvice" order="0"/>
- </aop:config>
- <tx:advice id="jdbcAdvice" transaction-manager="jdbcManager">
- <tx:attributes>
- <tx:method name="*"/>
- </tx:attributes>
- </tx:advice>
- …
在①處,我們通過proxy-target-class="true"顯式使用CGLib動態代理技術,在②處通過AspjectJ切點表達式表達UserService所有的方法,希望對UserService所有方法都實施Spring AOP事務增強。
在UserService添加一個可執行的方法,如下所示:
- package com.baobaotao.special;
- import org.springframework.context.ApplicationContext;
- import org.springframework.context.support.ClassPathXmlApplicationContext;
- import org.springframework.stereotype.Service;
- @Service("userService")
- public class UserService {
- …
- public static void main(String[] args) {
- ApplicationContext ctx =
- new ClassPathXmlApplicationContext("user/special/applicationContext.xml");
- UserService service = (UserService) ctx.getBean("userService");
- System.out.println("before method1");
- service.method1();
- System.out.println("after method1");
- System.out.println("before method2");
- service.method2();
- System.out.println("after method2");
- System.out.println("before method3");
- service.method3();
- System.out.println("after method3");
- System.out.println("before method4");
- service.method4();
- System.out.println("after method4");
- }
- }
在運行UserService之前,將Log4J日誌級別設置爲DEBUG,運行以上代碼查看輸出日誌,如下所示:
引用
①未啓用事務
before method1
in method1
after method1
②未啓用事務
before method2
in method2
after method2
③未啓用事務
before method3
in method3
after method3
④啓用事務
before method4
Creating new transaction with name [com.baobaotao.special.UserService.method4]: PROPAGATION_REQUIRED,ISOLATION_DEFAULT
…
in method4
Initiating transaction commit
Committing JDBC transaction on Connection [jdbc:mysql://localhost:3306/sampledb, UserName=root@localhost, MySQL-AB JDBC Driver]
Releasing JDBC Connection [jdbc:mysql://localhost:3306/sampledb, UserName=root@localhost, MySQL-AB JDBC Driver] after transaction
Returning JDBC Connection to DataSource
after method4
⑤未用事務
before method5
in method5
after method5
⑥啓用事務
before method6
Creating new transaction with name [com.baobaotao.special.UserService.method6]: PROPAGATION_REQUIRED,ISOLATION_DEFAULT
…
in method6
Initiating transaction commit
Committing JDBC transaction on Connection [jdbc:mysql://localhost:3306/sampledb, UserName=root@localhost, MySQL-AB JDBC Driver]
…
after method6
觀察以上輸出日誌,很容易發現method1~method3及method5這4個方法都沒有被實施Spring的事務增強,而method4和method6被實施了事務增強。這個結果驗證了我們前面的論述。
我們通過下表描述哪些特殊方法將成爲Spring AOP事務增強的漏網之魚。
序 號 | 動態代理策略 | 不能被事務增強的方法 |
1 | 基於接口的動態代理 | 除public外的其他所有的方法,此外public static也不能被增強 |
2 | 基於CGLib的動態代理 | private、static、final的方法 |
不過,需要特別指出的是,這些不能被Spring事務增強的特殊方法並非就不工作在事務環境下。只要它們被外層的事務方法調用了,由於Spring事務管理的傳播級別,內部方法也可以工作在外部方法所啓動的事務上下文中。我們說,這些方法不能被Spring進行AOP事務增強,是指這些方法不能啓動事務,但是外層方法的事務上下文依舊可以順利地傳播到這些方法中。
這些不能被Spring事務增強的方法和可被Spring事務增強的方法唯一的區別在於“是否可以主動啓動一個新事務”:前者不能而後者可以。對於事務傳播行爲來說,二者是完全相同的,前者也和後者一樣不會造成數據連接的泄漏問題。換句話說,如果這些“特殊方法”被無事務上下文的方法調用,則它們就工作在無事務上下文中;反之,如果被具有事務上下文的方法調用,則它們就工作在事務上下文中。
對於private的方法,由於最終都會被public方法封裝後再開放給外部調用,而public方法是可以被事務增強的,所以基本上沒有什麼問題。在實際開發中,最容易造成隱患的是基於CGLib的動態代理時的“public static”和“public final”這兩種特殊方法。原因是它們本身是public的,因此可以直接被外部類(如Web層的Controller類)調用,只要調用者沒有事務上下文,這些特殊方法也就以無事務的方式運作。