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

前言

往期文章:java

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

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

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

閒話很少說,讓咱們直接開始。

建立代理類

上一章結束以後,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

若是本文有幫助到你,但願能點個贊,這是對個人最大動力。

相關文章
相關標籤/搜索