1. 程式人生 > >spring aop類內部呼叫不攔截原因及解決方案

spring aop類內部呼叫不攔截原因及解決方案

       spring對應java web開發的同學來說,都不陌生,其中事務@Transactional在service層更是常常使用。

1.aop類內部呼叫不攔截原因

細心的同學也許早就發現當service中的某個沒標註@Transactional的方法呼叫另一個標註了@Transactional的方法時,居然沒開啟事務。例如

@Service
public class UserService {

	@Autowired
	private UserMapper userMapper;
	
	@Transactional(propagation=Propagation.REQUIRED,isolation=Isolation.DEFAULT,readOnly=false)
	public void insert01(User u){
		this.userMapper.insert(u);
		throw new RuntimeException("測試插入事務");
	}
	
	public void insert02(User u){
		this.insert01(u);
		throw new RuntimeException("測試插入事務");
	}
   當controller或其他service直接呼叫insert01(User u)時,事務是正常的,資料庫裡面的確沒資料;但是如果呼叫的是insert02(User u)方法,異常丟擲了,但是資料庫裡面居然有資料,說明事務不正常了。

    我們知道@Transactional其實就是一個aop代理,是一個cglib動態代理(常用的動態代理有cglib動態代理,有jdk動態代理)。

    直接呼叫insert01(User u)時,其實流程是這樣的:


也就是說controller或其他service首先呼叫的是AOP代理物件而不是目標物件,首先執行事務切面,事務切面內部通過TransactionInterceptor環繞增強進行事務的增強,即進入目標方法之前開啟事務,退出目標方法時提交/回滾事務。

  controller或其他service呼叫insert02(User u)時,流程是這樣的:
此處的this指向目標物件,因此呼叫this.b()將不會執行b事務切面,即不會執行事務增強。
我們通過debugger方式看看controller中的userService和insert02(xxx)中的this,會發現controller中的userService是一個代理物件
看到userService=UserService$$EnhanceBySpringCGLIB$$c2174c0b
這this明顯和controller中的userSerivce不是同一個了。      如果同學瞭解cglib(執行期進行程式碼植入)或aspectJ(編譯期就進行程式碼植入,反編譯後看更清晰瞭解,
看博文
)。為了讓同學對上面說的代理物件有個更直觀的印象,我參考aspectJ那樣編譯後的程式碼植入方式,寫個UserServiceProxy.java.(spring 對aop的真實處理不是這樣的,我這只是舉個例子直觀說明,有些同學對invoke()方法不直觀)
public class UserServiceProxy extends UserService{

	//模擬目標物件例項化
	private UserService targetService = new UserService();

	@Override
	public void insert01(User u) {
		System.out.println("開啟事務.....transational starting....");
		targetService.insert01(u);
		System.out.println("結束事務.....transational end......");
	}
	@Override
	public void insert02(User u) {
		targetService.insert01(u);
	}
}

   也就是說controller中的userSerivce實際是UserServiceProxy,那麼UserServiceProxy.insert02()的時候裡面呼叫的是目標物件UserService.insert01()了,並不是代理物件UserServiceProxy.insert01(),所以事務沒有開啟。

2.aop類內部呼叫攔截生效的解決方案

   2.1 方案一--從beanFactory中獲取物件

   剛剛上面說到controller中的UserService是代理物件,它是從beanFactory中得來的,那麼service類內呼叫其他方法時,也先從beanFacotry中拿出來就OK了。

public void insert02(User u){
		getService().insert01(u);
	}
	private UserService getService(){
		return SpringContextUtil.getBean(this.getClass());
	}

  2.2 方案二--獲取代理物件

private UserService getService(){
		// 採取這種方式的話,
		//@EnableAspectJAutoProxy(exposeProxy=true,proxyTargetClass=true)
		//必須設定為true
		return AopContext.currentProxy() != null ? (UserService)AopContext.currentProxy() : this;
	}
  如果aop是使用註解的話,那需要@EnableAspectJAutoProxy(exposeProxy=true,proxyTargetClass=true),如果是xml配置的,把expose-proxy設定為true,如
<aop:config expose-proxy="true">
       <aop:aspect ref="XXX">
          <!-- 省略--->
       </aop:aspect>
</aop:config>

  2.3方案三--將專案轉為aspectJ專案

   將專案轉為aspectJ專案,aop轉為aspect 類。具體就不寫了

  2.4 方案四--BeanPostProcessor

   通過BeanPostProcessor 在目標物件中注入代理物件,定義InjectBeanSelfProcessor類,實現BeanPostProcessor。也不具體寫了

   也許還有其他方案。