向您生動地講解Spring AOP 源碼(3)

前言

往期文章:

在上一章向您生動地講解Spring AOP 源碼(2)中,作者介紹了【如何獲取對應 Bean 適配的Advisors 鏈】。

在本章中,作者會向您介紹,Spring AOP 是如何解析我們配置的Aspect,並將advice織入的?

在本章的附錄部分,還會介紹如何保存 JDK動態代理和 Cglib生成的類文件。

閒話不多說,讓我們直接開始。

創建代理類

上一章結束之後,Spring AOP的核心邏輯已經走了一半了,獲取了目標類所適用的增強器列表,下面開始分析獲取代理的過程。

未免讀者閱讀不連貫,我們再貼一下向您生動地講解Spring AOP 源碼(1)中我們最後講解的一段源碼,由此繼續往下講述。

源碼位置:AbstractAutoProxyCreator#wrapIfNecessary(..)

wrapIfNecessary

TODO-2 createProxy

image.png

稍微提一下 TargetSource 這個概念,它用於封裝真實實現類的信息,在我理解看來就是把獲取目標對象這個步驟做了一個代理的操作,提供一個擴展點給外部,使得使用者可以通過這個擴展點去對目標對象做一些處理;

上面用了 SingletonTargetSource 這個實現類,其實我們這裏也不太需要關心這個,知道有這麼回事就可以了,個人感覺這個擴展點用處不是特別的大。


來條分割線,正式進入今天的核心內容。

現在,讓我們開始解析,Spring AOP創建代理類的流程。

源碼位置:AbstractAutoProxyCreator#createProxy(..)

流程:

  1. 獲取當前類中的屬性
  2. 添加代理接口
  3. 封裝Advisor並加入到ProxyFactory中
  4. 設置要代理的類
  5. Spring中爲子類提供了定製的函數customizeProxyFactory,子類可以在此函數中對ProxyFactory的進一步封裝
  6. ★★★ 獲取代理操作

主要分析關鍵的生成代理類的操作。

源碼位置:ProxyFactory#getProxy(..)

	public Object getProxy(ClassLoader classLoader) {
		return createAopProxy().getProxy(classLoader);
	}

這裏要分爲兩步,

  1. 創建AopProxy
  2. 獲取代理類

1. 創建 AopProxy

	protected final synchronized AopProxy createAopProxy() {
		if (!this.active) {
			activate();
		}
		return getAopProxyFactory().createAopProxy(this);
	}

這一步之後我們根據ProxyConfig 獲取到了對應的AopProxy的實現類,分別是JdkDynamicAopProxyObjenesisCglibAopProxy

image.png

2. 獲取代理類

JDK 動態代理

源碼位置:JdkDynamicAopProxy#getProxy(..)

我們關注的是最後一行代碼Proxy.newProxyInstance(classLoader, proxiedInterfaces, this)

    public static Object newProxyInstance(ClassLoader loader,
                                          Class<?>[] interfaces,
                                          InvocationHandler h)  {
        ...
    }

注:到這裏,你需要了解一下JDK動態代理的使用知識,如果能瞭解原理,那就更好了

第一個參數是類加載器,第二個參數是目標類的接口集合,第三個參數則是InvocationHandler的實現類,JdkDynamicAopProxy在創建代理的時候,是將自身作爲 InvocationHandler傳入的,由此可知JdkDynamicAopProxy本身實現了InvocationHandler接口。

熟悉JDK動態代理實現機制的同學應該會知道,調用代理類的對應方法時,代理類實際上是通過invoke(Object proxy, Method method, Object[] args)方法來完成 target class 方法的調用,並在裏面進行一些代理類想做的其他的操作。

在AOP中,invoke方法會完成AOP編織實現的封裝。所以讓我們看看這個invoke方法是怎麼實現的。

invoke方法的關鍵就在於,利用責任鏈模式,遞歸調用的方法,來完成advice 的織入

ReflectiveMethodInvocation構造方法

關鍵的ReflectiveMethodInvocation#proceed()方法

我們來看看((MethodInterceptor) interceptorOrInterceptionAdvice).invoke(this),這個方法有多種實現,其中一些我們熟悉的或者說需要關注的實現,對應的就是我們Advice的類型,或者說增強的時機。

術語 概念
Before 在方法被調用之前執行增強
After 在方法被調用之後執行增強
After-returning 在方法成功執行之後執行增強
After-throwing 在方法拋出指定異常後執行增強
Around 在方法調用的前後執行自定義的增強行爲(最靈活的方式)

這裏我們用概覽的方式過一下這幾種的實現,

MethodBeforeAdviceInterceptor#invoke(..)

AspectJAfterAdvice#invoke(..)

AfterReturningAdviceInterceptor#invoke(..)

AspectJAfterThrowingAdvice#invoke(..)

AspectJAroundAdvice#invoke(..)

Cglib 代理

Cglib 代理 和 JDK 代理 在流程上相似,只是在具體實現上不一樣。核心就是Enhancer和獲得callbacks的過程。這裏就不分析了。

小結

本章的核心內容就是,創建代理類時,Spring 根據 AOP 配置選擇JDK動態代理或是 Cglib 代理,增強器的織入是按照事先排序好的順序、advice 的類型來起作用的。

個人認爲核心難點還是在對JDK動態代理和Cglib代理 原理的理解。讀者如果對這塊不熟悉,可以查閱其他的文章進行學習。

可以學習到責任鏈的設計模式、JDK 動態代理和反射、Cglib代理等Java 核心知識。

最後,作者寫到這裏,也是長呼一口氣,源碼分析不像新技術那樣,一開始就抓人眼球,很難寫得引人入勝,通常篇幅過長,寫的人會乏,看的人也會乏。所幸作者堅持了下來,在這期間對AOP的源碼也有了更深的理解。

附錄

理解JDK 動態代理 和 CGLIB 代理 生成的代理類的源碼會讓你對advice織入的時機有更深的理解。

JDK 動態代理 類源碼

TestSvc

public interface TestSvc {
    void process();
}
@Service("testSvc")
public class TestSvcImpl implements TestSvc {

    @Override
    public void process() {
        System.out.println("test svc is working");
    }
}

生成代理類:

關鍵點:實現接口,method.invoke(…) 反射調用

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 ric.study.demo.aop.svc.TestSvc;

public final class $Proxy19
  extends Proxy
  implements TestSvc
{
  private static Method m1;
  private static Method m2;
  private static Method m0;
  private static Method m3;
  
  public $Proxy19(InvocationHandler paramInvocationHandler)
  {
    super(paramInvocationHandler);
  }
  
  public final boolean equals(Object paramObject)
  {
    try
    {
      return ((Boolean)this.h.invoke(this, m1, new Object[] { paramObject })).booleanValue();
    }
    catch (Error|RuntimeException localError)
    {
      throw localError;
    }
    catch (Throwable localThrowable)
    {
      throw new UndeclaredThrowableException(localThrowable);
    }
  }
  
  public final String toString()
  {
    try
    {
      return (String)this.h.invoke(this, m2, null);
    }
    catch (Error|RuntimeException localError)
    {
      throw localError;
    }
    catch (Throwable localThrowable)
    {
      throw new UndeclaredThrowableException(localThrowable);
    }
  }
  
  public final int hashCode()
  {
    try
    {
      return ((Integer)this.h.invoke(this, m0, null)).intValue();
    }
    catch (Error|RuntimeException localError)
    {
      throw localError;
    }
    catch (Throwable localThrowable)
    {
      throw new UndeclaredThrowableException(localThrowable);
    }
  }
  
  public final void process()
  {
    try
    {
      this.h.invoke(this, m3, null);
      return;
    }
    catch (Error|RuntimeException localError)
    {
      throw localError;
    }
    catch (Throwable localThrowable)
    {
      throw new UndeclaredThrowableException(localThrowable);
    }
  }
  
  static
  {
    try
    {
      m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[] { Class.forName("java.lang.Object") });
      m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);
      m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);
      m3 = Class.forName("ric.study.demo.aop.svc.TestSvc").getMethod("process", new Class[0]);
      return;
    }
    catch (NoSuchMethodException localNoSuchMethodException)
    {
      throw new NoSuchMethodError(localNoSuchMethodException.getMessage());
    }
    catch (ClassNotFoundException localClassNotFoundException)
    {
      throw new NoClassDefFoundError(localClassNotFoundException.getMessage());
    }
  }
}

如何保存JDK 動態代理的源文件

只需要在系統變量中設置sun.misc.ProxyGenerator.saveGeneratedFilestrue即可。比如這樣,

image.png

會在項目目錄下生成com.sun.proxy目錄,並存儲對應的文件。想要找到你的代理類到底是哪個,你還需要打印出(或者debug查看)這個代理類的類名,像我上圖一樣。

image.png

Cglib 代理 類源碼

關鍵:繼承;MethodInterceptor.intercept();

BTW:Cglib 的 源碼未免太過冗長,放上來的閱讀體驗非常不好(1000+行)。讀者可以按照我後面提到的方法自己生成,然後利用反編譯工具查看。

如何保存 Cglib 生成代理類 的源文件

和JDK 動態代理類似,System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "...");,設置class 文件的輸出目錄即可。

image.png

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