Spring 框架之 AOP 原理剖析

前言
AOP(Aspect Oriented Programming)面向切面編程是 Spring 框架最核心的組件之一,它通過對程序結構的另一種考慮,補充了 OOP(Object-Oriented Programming)面向對象編程。在 OOP 中模塊化的關鍵單元是類,而在 AOP 中,模塊化單元是切面。也就是說 AOP 關注的不再是類,而是一系列類裏面需要共同能力的行爲。

本文內容主要包括:

講解 OOP 與 AOP 的簡單對比,以及 AOP 的基礎名詞,比如面試中經常會被問到的 point cut、advice、join point 等。
講解面試經常會被問到的 JDK 動態代理和 CGLIB 代理原理以及區別。
講解 Spring 框架中基於 Schema 的 AOP 實現原理。
講解 Spring 框架中如何基於 AOP 實現的事務管理。
AOP 基礎概念
我們知道在 OOP 中模塊化的關鍵單元是類,類封裝了一類對象的行爲和狀態,當多個類有共同的屬性和行爲時候我們把這些共同的東西封裝爲一個基類,然後多個類可以通過繼承基類的方式來複用這些共同的東西,如果子類需要定製基類行爲則可以使用多態。OOP 中使用類來提供封裝,繼承,多態三個特性。

當我們需要在多個不相關的類的某些已有的行爲裏面添加一個共同的非業務邏輯時候,比如我們需要統計一些業務方法的執行耗時時候,以往做法需要在統計耗時的行爲裏面寫入計算耗時的代碼,在 OOP 裏面這種不涉及業務的散落在多個類的行爲裏面的代碼叫做橫切(Cross-cutting)代碼,OOP 中這種方式缺點一是業務邏輯行爲受到了計算耗時代碼干擾(業務邏輯行爲應該只專注業務),缺點二是計算耗時的代碼不能被複用。

而在 AOP 中模塊化單元是切面(Aspect),它將那些影響多個類的共同行爲封裝到可重用的模塊中,然後你就可以決定在什麼時候對哪些類的哪些行爲執行進行攔截(切點),並使用封裝好的可重用模塊裏面的行爲(通知)對其攔截的業務行爲進行功能增強,而不需要修改業務模塊的代碼,切面就是對此的一個抽象描述。

AOP 中有以下基礎概念:

Join point(連接點):程序執行期間的某一個點,例如執行方法或處理異常時候的點。在 Spring AOP 中,連接點總是表示方法的執行。
Advice(通知):通知是指一個切面在特定的連接點要做的事情。通知分爲方法執行前通知,方法執行後通知,環繞通知等。許多 AOP 框架(包括 Spring)都將通知建模爲攔截器,在連接點周圍維護一系列攔截器(形成攔截器鏈),對連接點的方法進行增強。
Pointcut(切點):一個匹配連接點(Join point)的謂詞表達式。通知(Advice)與切點表達式關聯,並在切點匹配的任何連接點(Join point)(例如,執行具有特定名稱的方法)上運行。切點是匹配連接點(Join point)的表達式的概念,是AOP的核心,並且 Spring 默認使用 AspectJ 作爲切入點表達式語言。
Aspect(切面):它是一個跨越多個類的模塊化的關注點,它是通知(Advice)和切點(Pointcut)合起來的抽象,它定義了一個切點(Pointcut)用來匹配連接點(Join point),也就是需要對需要攔截的那些方法進行定義;它定義了一系列的通知(Advice)用來對攔截到的方法進行增強;
Target object(目標對象):被一個或者多個切面(Aspect)通知的對象,也就是需要被 AOP 進行攔截對方法進行增強(使用通知)的對象,也稱爲被通知的對象。由於在 AOP 裏面使用運行時代理,所以目標對象一直是被代理的對象。
AOP proxy(AOP 代理):爲了實現切面(Aspect)功能使用 AOP 框架創建一個對象,在 Spring 框架裏面一個 AOP 代理要麼指 JDK 動態代理,要麼指 CgLIB 代理。
Weaving(織入):是將切面應用到目標對象的過程,這個過程可以是在編譯時(例如使用 AspectJ 編譯器),類加載時,運行時完成。Spring AOP 和其它純 Java AOP 框架一樣,是在運行時執行植入。
Advisor:這個概念是從 Spring 1.2的 AOP 支持中提出的,一個 Advisor 相當於一個小型的切面,不同的是它只有一個通知(Advice),Advisor 在事務管理裏面會經常遇到,這個後面會講到。
相比 OOP,AOP 有以下優點:

業務代碼更加簡潔,例如當需要在業務行爲前後做一些事情時候,只需要在該行爲前後配置切面進行處理,無須修改業務行爲代碼。
切面邏輯封裝性好,並且可以被複用,例如我們可以把打日誌的邏輯封裝爲一個切面,那麼我們就可以在多個相關或者不相關的類的多個方法上配置該切面。
JDK 動態代理和 CGLIB 代理原理以及區別
在 Spring 中 AOP 代理使用 JDK 動態代理和 CGLIB 代理來實現,默認如果目標對象是接口,則使用 JDK 動態代理,否則使用 CGLIB 來生成代理類,本節就簡單來介紹這兩種代理的原理和區別。

JDK 動態代理
由於 JDK 代理是對接口進行代理,所以首先寫一個接口類:

public interface UserServiceBo {
    
    public  int add();
}

然後實現該接口如下:

public class UserServiceImpl implements UserServiceBo {

    @Override
    public int add() {
        System.out.println("--------------------add----------------------");
        return 0;
    }
}

JDK 動態代理是需要實現 InvocationHandler 接口,這裏創建一個 InvocationHandler 的實現類:

public class MyInvocationHandler implements InvocationHandler {
  
    private Object target;

    public MyInvocationHandler(Object target) {
        super();
        this.target = target;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        //(1)
        System.out.println("-----------------begin "+method.getName()+"-----------------");
        //(2)
        Object result = method.invoke(target, args);
        //(3)
        System.out.println("-----------------end "+method.getName()+"-----------------");
        return result;
    }
    
    public Object getProxy(){
        //(4)
        return Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(), target.getClass().getInterfaces(), this);
    }

}

建立一個測試類:

public static void main(String[] args) {

  //(5)打開這個開關,可以把生成的代理類保存到磁盤
 System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");  
  //(6)創建目標對象(被代理對象)
  UserServiceBo service = new UserServiceImpl();
  //(7)創建一個InvocationHandler實例,並傳遞被代理對象
  MyInvocationHandler handler = new MyInvocationHandler(service);
  //(8)生成代理類
  UserServiceBo proxy = (UserServiceBo) handler.getProxy();
  proxy.add();
}

其中代碼(6)創建了一個 UserServiceImpl 的實例對象,這個對象就是要被代理的目標對象。

代碼(7)創建了一個 InvocationHandler 實例,並傳遞被代理目標對象 service 給內部變量 target。

代碼(8)調用 MyInvocationHandler 的 getProxy 方法使用 JDK 生成一個代理對象。

代碼(5)設置系統屬性變量 sun.misc.ProxyGenerator.saveGeneratedFiles 爲 true,這是爲了讓代碼(8)生成的代理對象的字節碼文件保存到磁盤。

運行上面代碼會在項目的 com.sun.proxy 下面會生成 $Proxy0.class 類,經反編譯後核心 Java 代碼如下:

package com.sun.proxy;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;
import proxy.JDK.UserServiceBo;

public final class $Proxy0
  extends Proxy
  implements UserServiceBo
{
  private static Method m1;
  private static Method m3;
  private static Method m0;
  private static Method m2;
  
  public $Proxy0(InvocationHandler paramInvocationHandler)
  {
    super(paramInvocationHandler);
  }
  
  public final int add()
  {
    try
    {
    //(9)第一個參數是代理類本身,第二個是實現類的方法,第三個是參數
      return h.invoke(this, m3, null);
    }
    catch (Error|RuntimeException localError)
    {
      throw localError;
    }
    catch (Throwable localThrowable)
    {
      throw new UndeclaredThrowableException(localThrowable);
    }
  }
  
 ...
  
  static
  {
    try
    {
   
      m3 = Class.forName("proxy.JDK.UserServiceBo").getMethod("add", new Class[0]);
    ...
      return;
    }
    catch (NoSuchMethodException localNoSuchMethodException)
    ...
  }
}

代理類 $Proxy0 中代碼(9)中的 h 就是 main 函數裏面創建的 MyInvocationHandler 的實例,h.invoke(this, m3, null) 實際就是調用的 MyInvocationHandler 的 invoke 方法,而後者則是委託給被代理對象進行執行,這裏可以對目標對象方法進行攔截,然後對其功能進行增強。

另外代碼(8)生成的 proxy 對象實際就是 $Proxy0 的一個實例,當調用 proxy.add() 時候,實際是調用的代理類 $Proxy0 的 add 方法,後者內部則委託給 MyInvocationHandler 的 invoke 方法,invoke 方法內部有調用了目標對象 service 的 add 方法。

那麼接口(UserServiceBo)、目標對象(被代理對象 UserServiceImpl)、代理對象($Proxy0)三者具體關係可以使用下圖表示:

可知 JDK 動態代理是對接口進行的代理;代理類實現了接口,並繼承了 Proxy 類;目標對象與代理對象沒有什麼直接關係,只是它們都實現了接口,並且代理對象執行方法時候內部最終是委託目標對象執行具體的方法。

CGLIB 動態代理
相比 JDK 動態代理對接口進行代理,CGLIB 則是對實現類進行代理,這意味着無論目標對象是否有接口,都可以使用 CGLIB 進行代理。

下面結合一個使用 CGLIB 對實現類進行動態代理的簡單代碼來講解。

使用 CGLIB 進行代理需要實現 MethodInterceptor,創建一個方法攔截器 CglibProxy 類:

public class CglibProxy implements MethodInterceptor {
    //(10)
    private Enhancer enhancer = new Enhancer();
    //(11)
    public Object getProxy(Class clazz) {
        //(12)設置被代理類的Class對象
        enhancer.setSuperclass(clazz);
        //(13)設置攔截器回調
        enhancer.setCallback( this);
        return enhancer.create();
    }


    @Override
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
        System.out.println(obj.getClass().getName()+"."+method.getName());
       Object result = proxy.invokeSuper(obj, args);
        
        return result;
    }
}

public void testCglibProxy() {

//(14)生成代理類到本地
 System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "/Users/zhuizhumengxiang/Downloads");
    //(15)生成目標對象
    UserServiceImpl service = new UserServiceImpl();
    //(16)創建CglibProxy對象
    CglibProxy cp = new CglibProxy();
    //(17)生成代理類
    UserServiceBo proxy = (UserServiceBo) cp.getProxy(service.getClass());
    proxy.add();
}

執行上面代碼會在 /Users/zhuizhumengxiang/Downloads 目錄生成代理類 UserServiceImpl$$EnhancerByCGLIB$$d0bce05a.class 文件,反編譯後部分代碼如下:

public class UserServiceImpl$$EnhancerByCGLIB$$d0bce05a extends UserServiceImpl
  implements Factory
{
static void CGLIB$STATICHOOK1()
  {
    //(18)空參數
    CGLIB$emptyArgs = new Object[0];
    
    //(19)獲取UserServiceImpl的add方法列表
    Method[] tmp191_188 = ReflectUtils.findMethods(new String[] { "add", "()I" }, (localClass2 = Class.forName("zlx.cglib.zlx.UserServiceImpl")).getDeclaredMethods());
    CGLIB$add$0$Method = tmp191_188[0];

    //(20)創建CGLIB$add$0,根據創建一個MethodProxy
    CGLIB$add$0$Proxy = MethodProxy.create(localClass2, localClass1, "()I", "add", "CGLIB$add$0");

  }
  static
  {
    CGLIB$STATICHOOK1();
  }
  //(21)
  final int CGLIB$add$0()
  {
    return super.add();
  }
  //(22)
  public final int add()
  {
    ...
    //(23)獲取攔截器,這裏爲CglibProxy的實例
    MethodInterceptor tmp17_14 = this.CGLIB$CALLBACK_0;
    if (tmp17_14 != null)
    { //(24)調用攔截器的intercept方法
      Object tmp36_31 = tmp17_14.intercept(this, CGLIB$add$0$Method, CGLIB$emptyArgs, CGLIB$add$0$Proxy);
      tmp36_31;
      return tmp36_31 == null ? 0 : ((Number)tmp36_31).intValue();
    }
    return super.add();
  }
 }

代碼(18)創建了一個 CGLIB$emptyArgs,因爲 add 方法是無入參的,所以這裏創建的 Object 對象個數爲0,這對象是 CglibProxy 攔截器的 intercept 的第三個參數。

代碼(19)獲取 UserServiceImpl 的 add 方法列表,並把唯一方法賦值給 CGLIB$add$0$Method,這個對象是 CglibProxy 攔截器的 intercept 第二個參數。

代碼(20)創建了一個 MethodProxy 對象,當調用 MethodProxy 對象的 invokeSuper 方法時候會直接調用代理對象的 CGLIB$add$0 方法,也就是直接調用父類 UserServiceImpl 的 add 方法,這避免了一次反射調用,創建的 MethodProxy 對象是 CglibProxy 攔截器的 intercept 的第四個參數。

代碼(22)是代理類的 add 方法,代碼(17)生成的代理對象 proxy 就是 UserServiceImpl$$EnhancerByCGLIB$$d0bce05a 的一個實例,當調用 proxy.add() 方法時候就是調用的代碼(22),從代碼(22)可知內部調用了攔截器 CglibProxy 的 intercept 方法,可知 intercept 的第一個參數就是代理對象本身。

那麼接口(UserServiceBo)、目標對象(被代理對象 UserServiceImpl),代理對象(UserServiceImpl$$EnhancerByCGLIB$$d0bce05a)三者具體關係可以使用下圖表示:

可知接口和代理對象沒有啥關係,代理對象是繼承了目標對象和實現了 Factory 接口。

總結
JDK 動態代理機制只能對接口進行代理,其原理是動態生成一個代理類,這個代理類實現了目標對象的接口,目標對象和代理類都實現了接口,但是目標對象和代理類的 Class 對象是不一樣的,所以兩者是沒法相互賦值的。

CGLIB 是對目標對象本身進行代理,所以無論目標對象是否有接口,都可以對目標對象進行代理,其原理是使用字節碼生成工具在內存生成一個繼承目標對象的代理類,然後創建代理對象實例。由於代理類的父類是目標對象,所以代理類是可以賦值給目標對象的,自然如果目標對象有接口,代理對象也是可以賦值給接口的。

CGLIB 動態代理中生成的代理類的字節碼相比 JDK 來說更加複雜。

JDK 使用反射機制調用目標類的方法,CGLIB 則使用類似索引的方式直接調用目標類方法,所以 JDK 動態代理生成代理類的速度相比 CGLIB 要快一些,但是運行速度比 CGLIB 低一些,並且 JDK 代理只能對有接口的目標對象進行代理。

Spring 框架中基於 Schema 的 AOP 實現原理
Spring 提供了兩種方式對 AOP 進行支持:基於 Schema 的 AOP,基於註解的 AOP。

基於 Schema 的 AOP 允許您基於 XML 的格式配置切面功能,Spring 2.0 提供了新的“aop”命名空間標記來定義切面的支持,基於註解的 AOP 則允許您使用 @Aspect 風格來配置切面。

本文就來先講講基於 Schema 的 AOP 的實現原理。

AOP 簡單使用
一個切面實際上是一個被定義在 Spring application context 裏面的一個正常的 Java 對象,配置切面對目標對象進行增強時候,一般使用下面配置格式:

    <!--(1) -->
    <bean id="helloService" class="zlx.test.aop.HelloServiceBoImpl" />

    <!--(2) -->
    <bean id="myAspect" class="zlx.test.aop.MyAspect" />

    <aop:config>
        <!--(3) -->
        <aop:pointcut id="pointcut"
            expression="execution(* *..*BoImpl.sayHello(..))"/>
        <!--(4) -->
        <aop:aspect ref="myAspect">
            <!--(4.1) -->
            <aop:before pointcut-ref="pointcut" method="beforeAdvice" />
            <!--(4.2) -->
            <aop:after pointcut="execution(* *..*BoImpl.sayHello(..))"
                method="afterAdvice" />
            <!--(4.3) -->                
             <aop:after-returning
                pointcut="execution(* *..*BoImpl.sayHelloAfterReturn(..))" method="afterReturningAdvice"
                arg-names="content" returning="content" />
            <!--(4.4) -->                
            <aop:after-throwing pointcut="execution(* *..*BoImpl.sayHelloThrowExecption(..))"
                method="afterThrowingAdvice" arg-names="e" throwing="e" />
            <!--(4.5) -->                
            <aop:around pointcut="execution(* *..*BoImpl.sayHelloAround(..))"
                method="aroundAdvice" /> 
        </aop:aspect>
    </aop:config>
    <!--(5) -->
    <bean id = "tracingInterceptor" class="zlx.test.aop.TracingInterceptor"/>

    <!--(6) -->
    <aop:config>
        <aop:pointcut id="pointcutForadVisor" expression="execution(* *..*BoImpl.sayHelloAdvisor(..))" />
        <aop:advisor pointcut-ref="pointcutForadVisor" advice-ref="tracingInterceptor" />
    </aop:config>

代碼(1)創建一個要被 AOP 進行功能增強的目標對象(Target object),HelloServiceBoImpl 的代碼如下:

public class HelloServiceBoImpl implements HelloServiceBo{

    @Override
    public void sayHello(String content) {
        System.out.println("sayHello:" + content);
    }

    @Override
    public String sayHelloAround(String content) {
        System.out.println(" sayHelloAround:" + content);
        return content;
    }

    @Override
    public String sayHelloAfterReturn(String content) {
        System.out.println("sayHelloAround:" + content);
        return content;
    }

    @Override
    public void sayHelloThrowExecption() {
        System.out.println("sayHelloThrowExecption");
        throw new RuntimeException("hello Im an exception");
    }
}

public interface HelloServiceBo {
    
    public void sayHello(String content);
    
    public String sayHelloAround(String content);
    
    public String sayHelloAfterReturn(String content);

    public void sayHelloThrowExecption();

}

代碼(2)實質是定義了切面要使用的一系列的通知方法(Advice),用來對目標對象(Target object)的方法進行增強,MyAspect 的代碼如下:

public class MyAspect {
    public void beforeAdvice(String content) {
        System.out.println("---before advice "+ "---");
    }
 
    public void afterAdvice(JoinPoint jp) {
        System.out.println("---after advice " + jp.getArgs()[0].toString()+"---");
    }

    public Object afterReturningAdvice(Object value) {
        System.out.println("---afterReturning advice " + value+"---");
        return value + " ha";
    }

    public void afterThrowingAdvice(Exception e) {
        System.out.println("---after throwing advice exception:" + e+"---");
    }

    public Object aroundAdvice(ProceedingJoinPoint pjp) throws Throwable {
        
        Object[] obj = pjp.getArgs();
        String content = (String) obj[0];
        System.out.println("---before sayHelloAround execute---");
        String  retVal = (String) pjp.proceed();
        System.out.println("---after sayHelloAround execute---");
        
        return retVal+ " suffix";
    }
}

代碼(3)定義了一個切點(pointcut),這裏是對滿足* *..*BoImpl表達式的類裏面的方法名稱爲 sayHello 的方法進行攔截。

代碼(4)定義一個切面,一個切面中可以定義多個攔擊器,其中(4.1)定義了一個前置攔截器,這個攔截器對滿足代碼(3)中定義的切點的連接點(方法)進行攔截,並使用 MyAspect 中定義的通知方法 beforeAdvice 進行功能增強。其中(4.2)定義了一個後置攔截器(finally),對滿足 execution(* *..*BoImpl.sayHello(..)) 條件的連接點方法使用 MyAspect 中的通知方法 afterAdvice 進行功能增強。其中(4.3)定義了一個後置攔截器,對滿足 execution(* *..*BoImpl.sayHelloAfterReturn(..)) 條件的連接點方法使用 MyAspect 中的通知方法 afterReturningAdvice 進行功能增強,這個後置連接器與(4.2)不同在於如果被攔截方法拋出了異常,則這個攔截器不會被執行,而(4.2)的攔截器一直會被執行。其中(4.4)定義了一個當被攔截方法拋出異常後對異常進行攔截的攔截器,具體攔截哪些方法由 execution(* *..*BoImpl.sayHelloThrowExecption(..)) 來決定,具體的通知方法是 MyAspect 中的 afterThrowingAdvice 方法。其中(4.5)對滿足 execution(* *..*BoImpl.sayHelloAround(..)) 條件的連接點方法使用 MyAspect 中的通知方法 aroundAdvice 進行增強。

代碼(5)創建一個方法攔截器,它是一個通知,代碼如下:

class TracingInterceptor implements MethodInterceptor {
    public Object invoke(MethodInvocation i) throws Throwable {
        System.out
                .println("---method " + i.getMethod() + " is called on " + i.getThis() + " with args " + i.getArguments()+"---");
        Object ret = i.proceed();
        System.out.println("---method " + i.getMethod() + " returns " + ret+"---");
        return ret;
    }
}

代碼(6)創建了一個新的 aop:config 標籤,內部首先創建了一個切點,然後創建了一個 advisor(一個小型切面),它對應的通知方法是 tracingInterceptor,對應的切點是 pointcutForadVisor。

需要注意的是爲了能夠使用 AOP 命名空間下的 aop 標籤,您需要在 XML 引入下面的 spring-aop schema:

<?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:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.3.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.3.xsd">
<!-- <bean/> definitions here -->
</beans>

把上面配置收集起來,放入到 beanaop.xml 配置文件後,寫下下面代碼,就可以進行測試:

public class TestAOP {

    public static final String xmlpath = "beanaop.xml";
    public static void main(String[] args) {
 
        ClassPathXmlApplicationContext cpxa = new ClassPathXmlApplicationContext(xmlpath);
        HelloServiceBo serviceBo = cpxa.getBean("helloService",HelloServiceBo.class);
        serviceBo.sayHello(" I love you");
        
        String result = serviceBo.sayHelloAround("I love you");
        System.out.println(result);
        
        result = serviceBo.sayHelloAfterReturn("I love you");
        System.out.println(result);
        
        serviceBo.sayHelloAdvisor("I love you");

        serviceBo.sayHelloThrowExecption();

    }
}

原理剖析
aop:config 標籤的解析
既然本文講解基於 XML 配置的 AOP 的原理,那麼我們就先從解析 XML 裏面配置的 aop:config 講起。首先看看 Spring 框架的 ConfigBeanDefinitionParser 類是如何解析aop:config 標籤,主要時序圖如下:

代碼(3)註冊了一個 AspectJAwareAdvisorAutoProxyCreator 類到 Spring IOC 容器,該類的作用是自動創建代理類,這個後面會講。這裏需要注意的是在 registerAspectJAutoProxyCreatorIfNecessary的useClassProxyingIfNecessary 方法裏面解析了 aop:config 標籤裏面的 proxy-target-class 和 expose-proxy 屬性值,代碼如下:

    private static void useClassProxyingIfNecessary(BeanDefinitionRegistry registry, @Nullable Element sourceElement) {
        //解析proxy-target-class屬性
        if (sourceElement != null) {
            boolean proxyTargetClass = Boolean.valueOf(sourceElement.getAttribute(PROXY_TARGET_CLASS_ATTRIBUTE));  //設置proxy-target-class屬性值到AspectJAwareAdvisorAutoProxyCreator裏面
            if (proxyTargetClass) {
                AopConfigUtils.forceAutoProxyCreatorToUseClassProxying(registry);
            }
            //解析expose-proxy屬性值
            boolean exposeProxy = Boolean.valueOf(sourceElement.getAttribute(EXPOSE_PROXY_ATTRIBUTE));        
            //設置expose-proxy屬性屬性值到AspectJAwareAdvisorAutoProxyCreator
            if (exposeProxy) {
    AopConfigUtils.forceAutoProxyCreatorToExposeProxy(registry);
            }
        }
    }

針對 proxy-target-class 屬性(默認 false),如果設置爲 true,則會強制使用 CGLIB 生成代理類;對於 expose-proxy 屬性(默認 false)如果設置爲 true,則對一個類裏面的嵌套方法調用的方法也進行代理,具體說是如果一個 ServiceImpl 類裏面有 a 和 b 方法,如果 a 裏面調用了 b:

public void a(){
  this.b();
}

那麼默認情況下在對方法 a 進行功能增強時候,裏面的 b 方法是不會被增強的,如果也需要對 b 方法進行增強則可以設置 expose-proxy 爲 true,並且在調用 b 方法時候使用下面方式:

public void a(){
 ((ServiceBo)AopContext.currentProxy()).b();
}
1
2
3
需要注意的是 AopContext.currentProxy() 返回的是代理後的類,如果使用 JDK 代理則這裏類型轉換時候必須要用接口類,如果是 CGLIB 代理則這裏類型轉換爲實現類 ServiceImpl。

代碼(4)是對 aop:config 標籤裏面的 aop:pointcut 元素進行解析,每個 pointcut 元素會創建一個 AspectJExpressionPointcut 的 bean 定義,並註冊到Spring IOC,parsePointcut 的主幹代碼如下:

private AbstractBeanDefinition parsePointcut(Element pointcutElement, ParserContext parserContext) {
    //獲取aop:pointcut標籤的id屬性值
    String id = pointcutElement.getAttribute(ID);
    //獲取aop:pointcut標籤的expression屬性值
    String expression = pointcutElement.getAttribute(EXPRESSION);

    AbstractBeanDefinition pointcutDefinition = null;

    try {
        //創建AspectJExpressionPointcut的bean定義,並設置屬性
        pointcutDefinition = createPointcutDefinition(expression);
        pointcutDefinition.setSource(parserContext.extractSource(pointcutElement));

        //如果aop:pointcut標籤的id屬性值不爲空,則id作爲該bean在Spring容器裏面的bean名字,並註冊該bean到Spring容器。
        String pointcutBeanName = id;
        if (StringUtils.hasText(pointcutBeanName)) {
            parserContext.getRegistry().registerBeanDefinition(pointcutBeanName, pointcutDefinition);
        }
        else {//否者系統自動生成一個名字,並注入該bean到Spring容器
            pointcutBeanName = parserContext.getReaderContext().registerWithGeneratedName(pointcutDefinition);
        }
        ...
    }
    ...
    return pointcutDefinition;
}

注: AspectJExpressionPointcut 的成員變量 expression 保存了 aop:pointcut 標籤的 expression 屬性值,這個在自動代理時候會用到。

代碼(5)是對 aop:config 標籤裏面的 aop:advisor 元素進行解析,每個 advisor 元素會創建一個 DefaultBeanFactoryPointcutAdvisor 的 bean 定義,並註冊到 Spring IOC,parseAdvisor代碼如下:

private void parseAdvisor(Element advisorElement, ParserContext parserContext) {
    //創建DefaultBeanFactoryPointcutAdvisor的定義,並設置對advice的引用
    AbstractBeanDefinition advisorDef = createAdvisorBeanDefinition(advisorElement, parserContext);
    String id = advisorElement.getAttribute(ID);

    try {
        ...    
        //註冊DefaultBeanFactoryPointcutAdvisor到Spring容器
        String advisorBeanName = id;
        if (StringUtils.hasText(advisorBeanName)) {
            parserContext.getRegistry().registerBeanDefinition(advisorBeanName, advisorDef);
        }
        else {
            advisorBeanName = parserContext.getReaderContext().registerWithGeneratedName(advisorDef);
        }
        //解析aop:advisor標籤中引用的切點,並設置到DefaultBeanFactoryPointcutAdvisor的定義
        Object pointcut = parsePointcutProperty(advisorElement, parserContext);
        if (pointcut instanceof String) {
                advisorDef.getPropertyValues().add(POINTCUT, new RuntimeBeanReference((String) pointcut));
                ...
        }
        ...
    }
    ...
}

**注:**每個 DefaultBeanFactoryPointcutAdvisor 對象裏面通過成員變量 adviceBeanName 保存引用通知在 BeanFactory 裏面的 Bean 名稱,通過成員變量 pointcut 保存切點。

DefaultBeanFactoryPointcutAdvisor 繼承自 Advisor 接口,這裏需要注意的是 adviceBeanName 保存的只是通知的字符串名稱,那麼如何獲取真正的通知對象呢,其實 DefaultBeanFactoryPointcutAdvisor 實現了 BeanFactoryAware,其內部保證着 BeanFactory 的引用,既然有了 BeanFactory,那麼就可以根據 Bean 名稱拿到想要的 Bean,這個可以參考 Chat:Spring 框架常用擴展接口揭祕。

代碼(6)是對 aop:config 標籤裏面的 aop:aspect 元素進行解析,會解析 aop:aspect 元素內的子元素,每個子元素會對應創建一個 AspectJPointcutAdvisor 的 bean 定義,並註冊到 Spring IOC,parseAspect的代碼如下:

 
private void parseAspect(Element aspectElement, ParserContext parserContext) {
        //獲取 aop:aspect 元素的id屬性
        String aspectId = aspectElement.getAttribute(ID);
        //獲取 aop:aspect 元素的ref屬性
        String aspectName = aspectElement.getAttribute(REF);

        try {
            this.parseState.push(new AspectEntry(aspectId, aspectName));
            List<BeanDefinition> beanDefinitions = new ArrayList<>();
            List<BeanReference> beanReferences = new ArrayList<>();

            ...
            //循環解析```aop:aspect```元素內所有通知
            NodeList nodeList = aspectElement.getChildNodes();
            boolean adviceFoundAlready = false;
            for (int i = 0; i < nodeList.getLength(); i++) {
                Node node = nodeList.item(i);
                //判斷是否爲通知節點,比如前置,後者通知等
                if (isAdviceNode(node, parserContext)) {
                    if (!adviceFoundAlready) {
                        adviceFoundAlready = true;
                        ...
                        beanReferences.add(new RuntimeBeanReference(aspectName));
                    }
                    //根據通知類型的不同,創建不同的通知對象,最後封裝爲AspectJPointcutAdvisor的bean定義,並註冊到Spring容器
                    AbstractBeanDefinition advisorDefinition = parseAdvice(
                            aspectName, i, aspectElement, (Element) node, parserContext, beanDefinitions, beanReferences);
                    beanDefinitions.add(advisorDefinition);
                }
            }
            //循環解析```aop:aspect```元素內所有切點,並註冊到Spring容器
            List<Element> pointcuts = DomUtils.getChildElementsByTagName(aspectElement, POINTCUT);
            for (Element pointcutElement : pointcuts) {
                parsePointcut(pointcutElement, parserContext);
            }
            ...
        }
        finally {
            ...
        }
}

需要注意的是 parseAdvice 函數內部會根據通知類型的不同創建不同的 advice 對象,對應 before 通知會創建通知對象爲 AspectJMethodBeforeAdvice,對應 after 通知創建的通知對象爲 AspectJAfterAdvice,對應 after-returning 通知創建的通知對象爲 AspectJAfterReturningAdvice,對應 after-throwing 通知創建的通知對象爲 AspectJAfterThrowingAdvice,對應 around 通知創建的通知對象爲 AspectJAroundAdvice,一個共同點是這些通知對象都實現了 MethodInterceptor 接口。

**注:**每個 AspectJPointcutAdvisor 對象裏面通過 advice 維護着一個通知,通過 pointcut 維護這麼一個切點,AspectJPointcutAdvisor 繼承自 Advisor 接口。

至此 Spring 框架把 aop:config 標籤裏面的配置全部轉換爲了 Bean 定義的形式並注入到 Spring 容器了,需要注意的是對應一個 Spring 應用程序上下文的 XML 配置裏面可以配置多個 aop:config 標籤,每次解析一個 aop:config 標籤的時候都會重新走這個流程。

代理類的生成
(1)代理類生成的主幹流程

上節在解析 aop:config 標籤時候注入了一個 AspectJAwareAdvisorAutoProxyCreator 類到 Spring 容器,其實這個類就是實現動態生成代理類的,AspectJAwareAdvisorAutoProxyCreator 實現了 BeanPostProcessor 接口(這個接口是 Spring 框架在 bean 初始化前後做事情的擴展接口,具體可以參考:http://gitbook.cn/gitchat/activity/5a84589a1f42d45a333f2a8e),所以會有 Object postProcessAfterInitialization(@Nullable Object bean, String beanName) 方法,下面看下這個方法代碼執行時序圖:

代碼(3)在 Spring 容器中查找可以對當前 bean 進行增強的通知 bean,內部首先執行代碼(4)在 Spring 容器查找所有實現了 Advisor 接口的 Bean,也就是上節講解的從 aop:config 標籤裏面解析的所有 DefaultBeanFactoryPointcutAdvisor 和 AspectJPointcutAdvisor 的實例都會被找到。代碼(5)則看找到的 Advisor 裏面哪些可以應用到當前 bean 上,這個是通過切點表達式來匹配的。代碼(6)則對匹配的 Advisor 進行排序,根據每個 Advisor 對象的 getOrder 方法的值進行排序。

代碼(13)這裏根據一些條件決定是使用 JDK 動態代理,還是使用 CGLIB 進行代理,屬於設計模式裏面的策略模式。

public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException {
    //如果optimize =true或者proxyTargetClass=ture 或者沒有指定代理接口,則使用CGLIB進行代理
    if (config.isOptimize() || config.isProxyTargetClass() || hasNoUserSuppliedProxyInterfaces(config)) {
        Class<?> targetClass = config.getTargetClass();
        //如果目標類爲接口或者目標類是使用JDK動態代理生成的類,則是要使用JDK對其進行代理
        if (targetClass.isInterface() || Proxy.isProxyClass(targetClass)) {
            return new JdkDynamicAopProxy(config);
        }
        //否者使用CGLIB進行代理
        return new ObjenesisCglibAopProxy(config);
    }
    //使用JDK進行代理
    else {
        return new JdkDynamicAopProxy(config);
    }

對於 proxyTargetClass 前面已經講過了可以通過在 aop:config 這個標籤裏面配置,那麼 optimize 是在哪裏配置的呢?其實除了本文講的基於標籤的 AOP 模式,Spring 還提供了比如下面的方式進行動態代理:

 <bean class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator">
        <property name="beanNames" value="*impl"></property>  <!-- 只爲後綴爲"impl"的bean生產代理 -->
        <property name="interceptorNames" value="myAdvisor"></property>   <!--自定義方法攔擊器-->
        <property name="optimize" value="true"></property>  
  </bean>

BeanNameAutoProxyCreator 可以針對指定規則的 beanName 的 bean 使用 interceptorNames 進行增強,由於這時候不能確定匹配的 bean 是否有接口,所以這裏 optimize 設置爲 true,然後在創建代理工廠時候具體看被代理的類是否是接口決定是使用 JDK 代理還是 CGLIB 代理。

代碼(14)則是具體調用 JdkDynamicAopProxy 或者 ObjenesisCglibAopProxy 的 getProxy 方法獲取代理類,下面兩節具體介紹如何生成。

(2)JDK 動態代理生成代理對象

首先看下 JdkDynamicAopProxy 的 getProxy 方法:

public Object getProxy(@Nullable ClassLoader classLoader) {
        if (logger.isDebugEnabled()) {
            logger.debug("Creating JDK dynamic proxy: target source is " + this.advised.getTargetSource());
        }
        Class<?>[] proxiedInterfaces = AopProxyUtils.completeProxiedInterfaces(this.advised, true);
        findDefinedEqualsAndHashCodeMethods(proxiedInterfaces);
        return Proxy.newProxyInstance(classLoader, proxiedInterfaces, this);
    }

由於 JdkDynamicAopProxy 類實現了 InvocationHandler 接口,所以這裏使用 Proxy.newProxyInstance 創建代理對象時候第三個參數傳遞的爲 this。下面看下 JdkDynamicAopProxy 的 invoke 方法:

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        ...
        try {
            ...
            //獲取可以運用到該方法上的攔截器列表
            List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);

            
            //創建一個invocation方法,這個裏面是一個interceptor鏈,是一個責任鏈模式
            invocation = new ReflectiveMethodInvocation(proxy, target, method, args, targetClass, chain);
            //使用攔截器鏈處理這個連接點方法
            retVal = invocation.proceed();
            ...
            return retVal;
        }
        finally {
            ...
        }
}

下面看下上面小節“AOP 簡單使用”例子中調用 serviceBo.sayHello 時候的時序圖從而加深理解:

“AOP 簡單使用”這一小節例子中我們在 sayHello 方法執行前加了一個前置攔截器,在 sayHello 方法執行後加了個後置攔截器;

當執行 serviceBo.sayHello 時候實際上執行的代理類的 sayHello 方法,所以會被 JdkDynamicAopProxy 的 invoke 方法攔截,invoke 方法內首先調用 getInterceptorsAndDynamicInterceptionAdvice 方法獲取配置到 sayHello 方法上的攔截器列表,然後創建一個 ReflectiveMethodInvocation 對象(內部是一個基於數數組的責任鏈),然後調用該對象的 procced 方法激活攔截器鏈對 sayHello 方法進行增強,這裏是首先調用了前置連接器對 sayHello 進行增強,然後調用實際業務方法 sayHello,最後調用了後置攔截器對 sayHello 進行增強。

(3)CGLIB 動態代理生成代理對象

首先看下 ObjenesisCglibAopProxy 的 getProxy 方法:

public Object getProxy(@Nullable ClassLoader classLoader) {
    
    try {
        Class<?> rootClass = this.advised.getTargetClass();
        
        //創建CGLIB Enhancer
        Enhancer enhancer = createEnhancer();
        ...
        enhancer.setSuperclass(proxySuperClass);
        enhancer.setInterfaces(AopProxyUtils.completeProxiedInterfaces(this.advised));
        enhancer.setNamingPolicy(SpringNamingPolicy.INSTANCE);
        ...
        //獲取callback,主要是DynamicAdvisedInterceptor
        Callback[] callbacks = getCallbacks(rootClass);
            Class<?>[] types = new Class<?>[callbacks.length];
            for (int x = 0; x < types.length; x++) {
                types[x] = callbacks[x].getClass();
        }
        //設置callback
        enhancer.setCallbackTypes(types);

        //產生代理類並創建一個代理實例
        return createProxyClassAndInstance(enhancer, callbacks);
    }
    catch (CodeGenerationException | IllegalArgumentException ex) {
        ...
    }
    ...
}

下面看下攔截器 DynamicAdvisedInterceptor 的 intercept 方法,代碼如下:

public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
    ...
    TargetSource targetSource = this.advised.getTargetSource();
    try {
        ...
        //獲取可以運用到該方法上的攔截器列表
        List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);
        Object retVal;
        //如果攔截器列表爲空,則直接反射調用業務方法
        if (chain.isEmpty() && Modifier.isPublic(method.getModifiers())) {
            Object[] argsToUse = AopProxyUtils.adaptArgumentsIfNecessary(method, args);
            retVal = methodProxy.invoke(target, argsToUse);
        }
        else {
            //創建一個方法invocation,其實這個是個攔截器鏈,這裏調用proceed激活攔截器鏈對當前方法進行功能增強
            retVal = new CglibMethodInvocation(proxy, target, method, args, targetClass, chain, methodProxy).proceed();
        }
        //處理返回值
        retVal = processReturnType(proxy, target, method, retVal);
        return retVal;
    }
    finally {
        ...
    }
}

下面看“AOP 簡單使用”這一節例子中調用 serviceBo.sayHello 時候的調用鏈路時序圖從而加深理解:

由於默認使用的 JDK 動態代理,要使用 CGLIB 進行代理,需要在 aop:config 標籤添加屬性如下:
<aop:config  proxy-target-class="true">

執行流程類似於 JDK 動態代理,這裏不再累述。
Spring 框架中如何基於 AOP 實現的事務管理
事務的簡單配置
XML 使用標籤配置事務,一般是按照下面方式進行配置:

<aop:config>
  <!--(1) -->
  <aop:pointcut id="businessService"
        expression="execution(* com.xyz.myapp.service.*.*(..))"/>
  <!--(2) -->
  <aop:advisor 
      pointcut-ref="businessService"
      advice-ref="tx-advice"/>
</aop:config>
  <!--(3) -->
<tx:advice id="tx-advice">
  <tx:attributes>
    <tx:method name="*" propagation="REQUIRED"/>
  </tx:attributes>
</tx:advice>

如上配置(1),配置了一個 id 爲 businessService 的切點用來匹配要對哪些方法進行事務增強;
配置(2)配置了一個 advisor ,使用 businessService 作爲切點,tx-advice 作爲通知方法;
配置(3)則使用 tx:advice 標籤配置了一個通知,這個是重點,下節專門講解。
注:這個配置作用是對滿足 id 爲 businessService 的切點條件的方法進行事務增強,並且設置事務傳播性級別爲 REQUIRED。

原理剖析
tx:advice 標籤的解析
tx:advice 標籤是使用 TxAdviceBeanDefinitionParser 進行解析的,下面看看解析時序圖:

如上時序圖 BeanDefinitionBuilder 是一個建造者模式,用來構造一個 bean 定義,這個 bean 定義最終會生成一個 TransactionInterceptor 的實例;
步驟(5)通過循環解析 tx:attributes 標籤裏面的所有 tx:method 標籤,每個 tx:method 對應一個 RuleBasedTransactionAttribute 對象,其中 tx:method 標籤中除了可以配置事務傳播性,還可以配置事務隔離級別,超時時間,是否只讀,和回滾策略。
步驟(12)把所有的 method 標籤的 RuleBasedTransactionAttribute 對象存在到了一個 map 數據結構,步驟(13)設置解析的所有屬性到建造者模式的對象裏面,步驟(14)使用建造者對象創建一個 bean 定義,步驟(15)則註冊該 bean 到 Spring 容器。
注: tx:advice 作用是創建一個 TransactionInterceptor 攔截器,內部維護事務配置信息。

事務攔截器原理簡單剖析
下面看下 TransactionInterceptor 的 invoke 方法:

public Object invoke(final MethodInvocation invocation) throws Throwable {
    ...
    return invokeWithinTransaction(invocation.getMethod(), targetClass, invocation::proceed);
}

protected Object invokeWithinTransaction(Method method, @Nullable Class<?> targetClass,
            final InvocationCallback invocation) throws Throwable {

        // If the transaction attribute is null, the method is non-transactional.
        TransactionAttributeSource tas = getTransactionAttributeSource();
        final TransactionAttribute txAttr = (tas != null ? tas.getTransactionAttribute(method, targetClass) : null);
        final PlatformTransactionManager tm = determineTransactionManager(txAttr);
        final String joinpointIdentification = methodIdentification(method, targetClass, txAttr);

        if (txAttr == null || !(tm instanceof CallbackPreferringPlatformTransactionManager)) {
            // 標準事務,內部有getTransaction(開啓事務) 和commit(提交)/rollback(回滾)事務被調用.
            TransactionInfo txInfo = createTransactionIfNecessary(tm, txAttr, joinpointIdentification);
            Object retVal = null;
            try {
                //這是一個環繞通知,調用proceedWithInvocation激活攔截器鏈裏面的下一個攔擊器
                retVal = invocation.proceedWithInvocation();
            }
            catch (Throwable ex) {
                // target invocation exception
                completeTransactionAfterThrowing(txInfo, ex);
                throw ex;
            }
            finally {
                cleanupTransactionInfo(txInfo);
            }
            commitTransactionAfterReturning(txInfo);
            return retVal;
        }

        ...
}

其中 createTransactionIfNecessary 是重要方法,其內部邏輯處理流程如下圖:

注: Spring 事務管理通過配置一個 AOP 切面來實現,其中定義了一個切點用來決定對哪些方法進行方法攔截,定義了一個 TransactionInterceptor 通知,來對攔截到的方法進行事務增強,具體事務攔擊器裏面是怎麼做的,讀者可以結合上面的 TransactionInterceptor 方法的流程圖結合源碼來研究下,如果必要後面會再開一個 Chat 專門講解 Spring 事務的實現,以及事務隔離性與傳播性。

總結
本文以大綱的形式剖析了 AOP 原理,以及事務攔擊器如何使用 AOP 來實現,由於篇幅限制並沒有深入到每個實現細節進行講解,希望讀者能夠依靠本文作爲大綱,對照源碼深入到大綱裏面的細節進行研究,以便加深對 AOP 原理的理解和掌握。
————————————————
版權聲明:本文爲CSDN博主「CSDN官方博客」的原創文章,遵循 CC 4.0 BY-SA 版權協議,轉載請附上原文出處鏈接及本聲明。
原文鏈接:https://blog.csdn.net/blogdevteam/article/details/103045864

發佈了28 篇原創文章 · 獲贊 125 · 訪問量 125萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章