Spring AOP無法攔截內部方法調用


當在同一個類中,A方法調用B方法時,AOP無法工作的問題

假設一個接口里面有兩個方法:

package demo.long;

public interface CustomerService {  
    public void doSomething1();  
    public void doSomething2();  
} 

 

接口實現類如下:

package demo.long.impl;

import demo.long.CustomerService; 

public class CustomerServiceImpl implements CustomerService {  
  
    public void doSomething1() {  
        System.out.println("CustomerServiceImpl.doSomething1()");  
        doSomething2();  
    }  
  
    public void doSomething2() {  
        System.out.println("CustomerServiceImpl.doSomething2()");  
    }  
  
} 

 

現在我需要在CustomerService接口的每個方法被調用時都在方法前執行一些邏輯,所以需要配置一個攔截器:

package demo.long;

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;

@Aspect
public class CustomerServiceInterceptor {

    @Before("execution(* demo.long..*.*(..))")
    public void doBefore() {
        System.out.println("do some important things before..."); 
    }
}

 

把Bean加到Spring配置中

<aop:aspectj-autoproxy />

<bean id="customerService" class="demo.long.impl.CustomerServiceImpl" />
<bean id="customerServiceInterceptor" class="demo.long.CustomerServiceInterceptor" />
 

如果現在外部對象調用CustomerService的doSomething1()方法的時候,會發現只有doSomething1()方法執行前打印了“do some important things before...”,而doSomething1()內部調用doSomething2()時並沒有打印上述內容;外部對象單獨調用doSomething2()時會打印上述內容。

public class CustomerServiceTest {

    @Autowired
    ICustomerService customerService;

    @Test
    public void testAOP() {
        customerService.doSomething1();
    }
}

 

 

原因分析

攔截器的實現原理就是動態代理,實現AOP機制。Spring 的代理實現有兩種:一是基於 JDK Dynamic Proxy 技術而實現的;二是基於 CGLIB 技術而實現的。如果目標對象實現了接口,在默認情況下Spring會采用JDK的動態代理實現AOP,CustomerServerImpl正是這種情況。

JDK動態代理生成的CustomerServiceImpl的代理類大致如下:

public class CustomerServiceProxy implements CustomerService {  
  
    private CustomerService customerService;  
  
    public void setCustomerService(CustomerService customerService) {  
        this.customerService = customerService;  
    }  
  
    public void doSomething1() {  
        doBefore();  
        customerService.doSomething1();  
    }  
  
    public void doSomething2() {  
        doBefore();  
        customerService.doSomething2();  
    }  
  
    private void doBefore() {  
        // 例如,可以在此處開啟事務或記錄日志
        System.out.println("do some important things before...");  
    }  
  
}  

 

客戶端程序使用代理類對象去調用業務邏輯:

public class TestProxy {  
      
    public static void main(String[] args) {  
        // 創建代理目標對象
        // 對於Spring來說,這一工作是由Spring容器完成的。  
        CustomerService serviceProxyTarget = new CustomerServiceImpl();  
  
        // 創建代理對象
        // 對於Spring來說,這一工作也是由Spring容器完成的。 
        CustomerServiceProxy serviceProxy = new CustomerServiceProxy();  
        serviceProxy.setCustomerService(serviceProxyTarget);  
        CustomerService serviceBean = (CustomerService) serviceProxy;  
  
        // 調用業務邏輯操作  
        serviceBean.doSomething1();  
    }  
}  

 

執行main方法,發現doSomething1()中調用doSomething2()方法的時候並未去執行CustomerServiceProxy類的doBefore()方法。其實doSomething2()等同於this.doSomething2(),在CustomerServiceImpl類中this關鍵字表示的是當前這個CustomerServiceImpl類的實例,所以程序會去執行CustomerServiceImpl對象中的doSomething2()方法,而不會去執行CustomerServiceProxy類對象中的 doSomething2()方法。

在使用Spring AOP的時候,我們從IOC容器中獲取的Bean對象其實都是代理對象,而不是那些Bean對象本身,由於this關鍵字引用的並不是該Service Bean對象的代理對象,而是其本身,因此Spring AOP是不能攔截到這些被嵌套調用的方法的。

解決方案

  1. 修改類,不要出現“自調用”的情況:這是Spring文檔中推薦的“最佳”方案;
  2. 若一定要使用“自調用”,那么this.doSomething2()替換為:((CustomerService) AopContext.currentProxy()).doSomething2();此時需要修改spring的aop配置:
 <aop:aspectj-autoproxy expose-proxy="true" /> 

轉自: https://www.jianshu.com/p/6534945eb3b5


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM