死磕Spring之AOP篇 - Spring AOP總覽

月圓吖發表於2021-04-16

該系列文章是本人在學習 Spring 的過程中總結下來的,裡面涉及到相關原始碼,可能對讀者不太友好,請結合我的原始碼註釋 Spring 原始碼分析 GitHub 地址 進行閱讀。

Spring 版本:5.1.14.RELEASE

在開始閱讀 Spring AOP 原始碼之前,需要對 Spring IoC 有一定的瞭解,可檢視我的 《死磕Spring之IoC篇 - 文章導讀》 這一系列文章

瞭解 AOP 相關術語,可先檢視 《Spring AOP 常見面試題) 》 這篇文章

該系列其他文章請檢視:《死磕 Spring 之 AOP 篇 - 文章導讀》

通過上一篇 《初識 JDK、CGLIB 兩種動態代理》 文章我們對 Spring AOP 底層的 JDK 動態代理和 CGLIB 動態代理有了一定的瞭解,也知道如何簡單地使用兩種動態代理建立代理物件。相信上篇文章可以讓你對 Spring AOP 有了一個初步的認識,那麼接下來我們準備進入 Spring AOP 原始碼學習階段。

在開始 Spring AOP 原始碼學習前,本文會對 Spring AOP 涉及到的大部分主要的 API 進行介紹,讓我們對 Spring AOP 有一個全面的認識,這樣在學習 Spring AOP 原始碼的過程中有一個清晰的思路會更加易於理解,然後在後續的文章從 Spring AOP 的自動代理開始深入學習,直接進入正題吧。

Spring AOP API 總覽

如下圖所示:

死磕Spring之AOP篇 - Spring AOP總覽

上面這張圖片列出了 Spring AOP 涉及到的大部分 API,接下來我們依次簡單介紹一下:

  • Joinpoint:連線點,也就是我們需要執行的目標方法

  • Pointcut:切點,提供 ClassFilter 類過濾器和 MethodMatcher 方法匹配器支援對類和方法進行篩選。

  • Advice:通知器,一般都是 MethodInterceptor 方法攔截器,不是的話會通過 AdvisorAdapter 介面卡轉換成對應的 MethodInterceptor 方法攔截器。

  • Advisor:Advice 容器介面,與 Advice 是一對一的關係;它的子介面 PointcutAdvisor 相當於一種 Pointcut 和 Advice 的容器,將 Pointcut 過濾 Joinpoint 的能力和 Advice 進行整合,這樣一來就將兩者關聯起來了。

  • Advisor 介面卡AdvisorAdapter 是 Advisor 的介面卡,當篩選出能夠應用於方法的所有 Advisor 後,需要獲取對應的 Advice;

    • 如果不是 MethodInterceptor 型別,則需要通過 AdvisorAdapter 介面卡轉換成對應的 MethodInterceptor 方法攔截器。
  • AOP 代理物件:AOP 代理物件,由 JDK 動態代理或者 CGLIB 動態代理建立的代理物件;

    • 選擇哪種動態代理是通過 AopProxyFactory 代理工廠根據目標類來決定的。
  • AOP 代理配置AdvisedSupport 配置管理器中儲存了對應代理物件的配置資訊,例如滿足條件的 Advisor 物件、TargetSource 目標類來源;

    • 在建立代理物件的過程,AdvisedSupport 扮演著一個非常重要的角色。
  • AOP 代理物件建立:AOP 代理物件的建立分為手動模式自動模式;不管哪種模式都和 AdvisedSupport 配置管理器有關;

    • 手動模式就是通過 Spring AOP 提供的 API 進行建立;
    • 自動模式則是和 Spring IoC 進行整合,在 Bean 的載入過程中如果需要進行代理,則建立對應的代理物件;
  • AdvisorChainFactoryAdvisor 鏈路工廠

    • AdvisedSupport 配置管理器中儲存了代理物件的所有 Advisor 物件,當攔截某個方法時,需要通過 AdvisorChainFactory 篩選出能夠應用於該方法的 Advisor 們;
    • 另外還需要藉助 AdvisorAdapter 介面卡獲取 Advisor 對應的 MethodInterceptor 方法攔截器,將這些 MethodInterceptor 有序地形成一條鏈路並返回。
  • IntroductionInfo引介介面,支援代理物件實現目標類沒有真正實現的額外的介面;

    • 在 Advisor 的子介面 IntroductionAdvisor 中會繼承這個 IntroductionInfo 介面,通過 @DeclareParents 註解定義的欄位會被解析成 IntroductionAdvisor 物件。
  • AOP 代理目標物件來源目標類來源,和代理物件進行關聯,用於獲取被代理代理的目標物件;

    • 在代理物件中最終的方法執行都需要先通過 TargetSource 獲取對應的目標物件,然後執行目標方法。

Joinpoint

類圖

死磕Spring之AOP篇 - Spring AOP總覽
  • ConstructorInvocation,是基於構造器的一個呼叫器,在 Spring AOP 中僅支援方法級別的代理,所以這個介面並沒有被使用

  • ReflectiveMethodInvocation,Joinpoint 的底層實現類,一個基於反射的方法呼叫器,本文不進行分析,在後續的文章進行講解?

  • CglibMethodInvocation,和上者實現類差不多,新增了一個 CGLIB 中的 MethodProxy 方法代理物件,本文不進行分析,在後續的文章進行講解?

org.aopalliance.intercept.Joinpoint,連線點,也就是我們需要執行的目標方法,是由 AOP 聯盟提供的一個介面,如下:

public interface Joinpoint {

	/**
	 * 執行方法呼叫器中的下一個攔截器,執行完所有的攔截器則執行目標方法
	 */
	Object proceed() throws Throwable;

	/**
	 * 返回目標物件
	 */
	Object getThis();

	/**
	 * 返回目標方法
	 */
	AccessibleObject getStaticPart();
}

org.aopalliance.intercept.Invocation,呼叫器,是由 AOP 聯盟提供的一個介面,如下:

public interface Invocation extends Joinpoint {

	/**
	 * 獲取引數陣列
	 */
	Object[] getArguments();
}

MethodInvocation

org.aopalliance.intercept.MethodInvocation,方法呼叫器,是由 AOP 聯盟提供的一個介面,如下:

public interface MethodInvocation extends Invocation {

	/**
	 * 獲取目標方法
	 */
	Method getMethod();
}

ProxyMethodInvocation

org.springframework.aop.ProxyMethodInvocation,MethodInvocation 方法呼叫器的擴充套件,允許訪問代理物件,如下:

public interface ProxyMethodInvocation extends MethodInvocation {

	/**
	 * 返回代理物件
	 */
	Object getProxy();

	/**
	 * 克隆方法
	 */
	MethodInvocation invocableClone();

	/**
	 * 克隆方法,使用引數
	 */
	MethodInvocation invocableClone(Object... arguments);

	/**
	 * 設定引數陣列
	 */
	void setArguments(Object... arguments);

	/**
	 * 新增自定義屬性
	 */
	void setUserAttribute(String key, @Nullable Object value);

	/**
	 * 獲取自定義屬性
	 */
	@Nullable
	Object getUserAttribute(String key);
}

Pointcut

類圖

死磕Spring之AOP篇 - Spring AOP總覽
  • AspectJExpressionPointcut,基於 AspectJ 處理表示式的能力實現的一個 Pointcut,本文不進行分析,在後續的文章進行講解?

上述類圖僅列出了部分 Pointcut 相關的介面

org.springframework.aop.Pointcut,切點,提供 ClassFilter 類過濾器和 MethodMatcher 方法匹配器支援對類和方法進行篩選,如下:

public interface Pointcut {

	/**
	 * 返回類過濾器
	 */
	ClassFilter getClassFilter();

	/**
	 * 返回方法匹配器
	 */
	MethodMatcher getMethodMatcher();

	/**
	 * 總是匹配通過的 Pointcut 例項物件
	 */
	Pointcut TRUE = TruePointcut.INSTANCE;
}

org.springframework.aop.support.ExpressionPointcut,支援表示式匹配的 Pointcut,如下:

public interface ExpressionPointcut extends Pointcut {
    
	/**
	 * 返回表示式
	 */
	@Nullable
	String getExpression();
}

StaticMethodMatcherPointcut

org.springframework.aop.support.StaticMethodMatcherPointcut,抽象類,本身是一個方法匹配器的 Pointcut,如下:

public abstract class StaticMethodMatcherPointcut extends StaticMethodMatcher implements Pointcut {

    /** 預設情況類過濾器總是匹配通過 */
	private ClassFilter classFilter = ClassFilter.TRUE;

	/**
	 * 設定類過濾器
	 */
	public void setClassFilter(ClassFilter classFilter) {
		this.classFilter = classFilter;
	}

    /**
     * 獲取類過濾器
     */
	@Override
	public ClassFilter getClassFilter() {
		return this.classFilter;
	}

    /**
     * 返回方法匹配器,就是當前物件,因為當前物件繼承的 StaticMethodMatcher 就是一個 MethodMatcher
     */
	@Override
	public final MethodMatcher getMethodMatcher() {
		return this;
	}
}

我們自定義的 Pointcut 通常可以繼承 StaticMethodMatcherPointcut 來實現,重寫 MethodMatcher matches(Method method, Class<?> targetClass, Object... args) 方法即可,也可以再實現 ClassFilter,實現 matches(Class clazz) 方法

ClassFilter

org.springframework.aop.ClassFilter,函式式介面,類過濾器,用於判斷這個 Class 是否滿足 Pointcut 切點,如下:

@FunctionalInterface
public interface ClassFilter {

	/**
	 * 匹配這個 Class 類
	 */
	boolean matches(Class<?> clazz);


	/**
	 * 總是匹配通過的類過濾器
	 */
	ClassFilter TRUE = TrueClassFilter.INSTANCE;
}

MethodMatcher

org.springframework.aop.MethodMatcher,方法匹配器,用於判斷這個 Method 是否滿足 Pointcut 切點,如下:

public interface MethodMatcher {

	/**
	 * 匹配這個方法
	 */
	boolean matches(Method method, Class<?> targetClass);

	/**
	 * 當前方法匹配器是否為執行狀態
	 */
	boolean isRuntime();

	/**
	 * 匹配這個方法,並校驗引數
	 */
	boolean matches(Method method, Class<?> targetClass, Object... args);

	/**
	 * 總是匹配通過的方法匹配器
	 */
	MethodMatcher TRUE = TrueMethodMatcher.INSTANCE;
}

Advice

類圖

死磕Spring之AOP篇 - Spring AOP總覽
  • Advice,標記介面
  • BeforeAdvice,標記介面,後置通知
  • AfterAdvice,標記介面,前置通知
  • Interceptor,標記介面,攔截器
  • AbstractAspectJAdvice,包裝了一個 Aspect 切面或者 AspectJ 註解標註的方法,Around、Before、After、AfterReturning、AfterThrowing 幾種型別的 Advice 最終都會轉換成 AbstractAspectJAdvice 對應的子類並實現 MethodInterceptor 方法攔截器介面,本文不進行分析,在後續的文章進行講解?

上述類圖僅列出了部分 Advice 相關的介面

MethodInterceptor

org.aopalliance.intercept.MethodInterceptor,方法攔截器,函式式介面,如下:

@FunctionalInterface
public interface MethodInterceptor extends Interceptor {

	/**
	 * 執行 MethodInvocation 呼叫器
	 */
	Object invoke(MethodInvocation invocation) throws Throwable;
}

Advisor

類圖

死磕Spring之AOP篇 - Spring AOP總覽

上面只列出了 Advisor 的部分實現類,在 Spring AOP 中還有非常多的實現類,在後續的文章進行分析?

  • AbstractBeanFactoryPointcutAdvisor,關聯 BeanFactory 的 PointcutAdvisor 抽象類,支援從 IoC 容器中獲取 Advice 物件,我們可以通過繼承這個類來自定義一個 PointcutAdvisor 物件

org.springframework.aop.Advisor,Advice 容器介面,與 Advice 是一對一的關係,如下:

public interface Advisor {

	/**
	 * 空的 Advice
	 */
	Advice EMPTY_ADVICE = new Advice() {};

	/**
	 * 獲取 Advice
	 */
	Advice getAdvice();

	/**
	 * 返回這個 Advice 是否有一個指定的目標例項物件,預設都是 true
	 */
	boolean isPerInstance();
}

PointcutAdvisor

org.springframework.aop.PointcutAdvisor,繼承 Advisor 介面,Pointcut 和 Advice 的容器,將 Pointcut 過濾 Joinpoint 的能力和 Advice 進行整合,這樣一來就將兩者關聯起來了,如下:

public interface PointcutAdvisor extends Advisor {

	/**
	 * 獲取 Pointcut 切點
	 */
	Pointcut getPointcut();
}

IntroductionAdvisor

org.springframework.aop.IntroductionAdvisor,繼承 Advisor 和 IntroductionInfo 介面,支援代理物件實現目標類沒有真正實現的額外的介面;通過 @DeclareParents 註解定義的欄位會被解析成 IntroductionAdvisor 物件,如下:

public interface IntroductionAdvisor extends Advisor, IntroductionInfo {

	/**
	 * 獲取類載入器,因為沒有 Pointcut 切點,則提供一個類過濾器
	 */
	ClassFilter getClassFilter();

	/**
	 * 校驗是否實現 IntroductionInfo 中的介面
	 */
	void validateInterfaces() throws IllegalArgumentException;
}

public interface IntroductionInfo {

	/**
	 * 返回額外的介面
	 */
	Class<?>[] getInterfaces();
}

AdvisorAdapter

類圖

死磕Spring之AOP篇 - Spring AOP總覽

org.springframework.aop.framework.adapter.AdvisorAdapter,Advisor 的介面卡,當篩選出能夠應用於方法的所有 Advisor 後,需要獲取對應的 Advice;如果不是 MethodInterceptor 型別,則需要通過 AdvisorAdapter 介面卡轉換成對應的 MethodInterceptor 方法攔截器,如下:

public interface AdvisorAdapter {

	/**
	 * 是否支援該型別的 Advice 適配
	 */
	boolean supportsAdvice(Advice advice);

	/**
	 * 獲取 Advisor 的 Advice,幷包裝成對應的 MethodInterceptor 方法攔截器
	 */
	MethodInterceptor getInterceptor(Advisor advisor);
}

org.springframework.aop.framework.adapter.MethodBeforeAdviceAdapter,前置 Advice 的介面卡,如下:

class MethodBeforeAdviceAdapter implements AdvisorAdapter, Serializable {

	@Override
	public boolean supportsAdvice(Advice advice) {
		return (advice instanceof MethodBeforeAdvice);
	}

	@Override
	public MethodInterceptor getInterceptor(Advisor advisor) {
		MethodBeforeAdvice advice = (MethodBeforeAdvice) advisor.getAdvice();
		return new MethodBeforeAdviceInterceptor(advice);
	}

}

可以看到這個 MethodBeforeAdviceAdapter 介面卡只能在 Spring 內部使用,先判斷 Advice 是否為 MethodBeforeAdvice 型別,如果是的話,則可以將 Advice 包裝成 MethodBeforeAdviceInterceptor 物件,其他的介面卡類似。

當然,AdvisorAdapter 介面卡不是直接被使用,而是通過 DefaultAdvisorAdapterRegistry 介面卡註冊中心使用的,在後續文章進行分析?

AOP 代理物件

類圖

死磕Spring之AOP篇 - Spring AOP總覽

org.springframework.aop.framework.AopProxy,AOP 代理介面,用於建立代理物件,如下:

public interface AopProxy {

	/**
	 * 建立一個代理物件
	 */
	Object getProxy();

	/**
	 * 建立一個代理物件,傳入指定的 ClassLoader 類載入器
	 */
	Object getProxy(@Nullable ClassLoader classLoader);
}

在 Spring AOP 中有 JdkDynamicAopProxy 和 CglibAopProxy 兩種實現類,分別代表 JDK 動態代理和 CGLIB 動態代理。選擇哪種動態代理是通過 DefaultAopProxyFactory 代理工廠根據目標類來決定的,這些內容在後續文章進行分析?

AOP 代理配置

類圖

死磕Spring之AOP篇 - Spring AOP總覽

org.springframework.aop.framework.ProxyConfig,代理配置類,如下:

/**
 * @see AdvisedSupport
 */
public class ProxyConfig implements Serializable {

	/** use serialVersionUID from Spring 1.2 for interoperability. */
	private static final long serialVersionUID = -8409359707199703185L;
    /** 是否使用類代理 */
	private boolean proxyTargetClass = false;
	private boolean optimize = false;
	boolean opaque = false;
    /** 是否暴露代理物件 */
	boolean exposeProxy = false;
	/** 配置是否被凍結 */
	private boolean frozen = false;
}

這個類僅提供一些頂層的配置資訊,更多和建立代理物件相關的配置資訊都在子類 AdvisedSupport 中,這些內容在後續文章進行分析?

AOP 代理物件建立

類圖

死磕Spring之AOP篇 - Spring AOP總覽

AOP 代理物件的建立分為手動模式自動模式;不管哪種模式都和 AdvisedSupport 配置管理器有關;

  • 手動模式就是通過 Spring AOP 提供的 API 進行建立,例如 ProxyFactory 代理工廠;
  • 自動模式則是和 Spring IoC 進行整合,在 Bean 的載入過程中如果需要進行代理,則建立對應的代理物件;可以看到 AbstractAutoProxyCreator 實現了 BeanPostProcessor 相關介面,在載入 Bean 的過程中,初始化後會呼叫 BeanPostProcessor#postProcessAfterInitialization 的初始化後置處理方法,在 AbstractAutoProxyCreator 中則可以建立對應的代理物件(如果有必要的話)

上面這些 AOP 自動代理類在後續文章會進行分析?

AOP 代理目標物件來源

類圖

死磕Spring之AOP篇 - Spring AOP總覽

org.springframework.aop.TargetSource目標類來源,和代理物件進行關聯,用於獲取被代理代理的目標物件,如下:

public interface TargetSource extends TargetClassAware {

	/**
	 * 獲取目標物件的 Class 物件
	 */
	@Override
	@Nullable
	Class<?> getTargetClass();

	/**
	 * 是否是靜態的,返回 `fasle` 表示每次獲取目標物件都需要進行建立,那麼每次建立都需要釋放,例如原型模式的 Bean
	 */
	boolean isStatic();

	/**
	 * 獲取目標物件
	 */
	@Nullable
	Object getTarget() throws Exception;

	/**
	 * 釋放目標物件
	 */
	void releaseTarget(Object target) throws Exception;
}

SingletonTargetSource

org.springframework.aop.target.SingletonTargetSource,單例 Bean 的目標物件來源,如下:

public class SingletonTargetSource implements TargetSource, Serializable {

	/** use serialVersionUID from Spring 1.2 for interoperability. */
	private static final long serialVersionUID = 9031246629662423738L;

	/** 儲存目標物件 */
	private final Object target;

	/**
	 * 建立例項物件的時候就是設定目標物件
	 */
	public SingletonTargetSource(Object target) {
		Assert.notNull(target, "Target object must not be null");
		this.target = target;
	}

	@Override
	public Class<?> getTargetClass() {
		return this.target.getClass();
	}

	@Override
	public Object getTarget() {
		return this.target;
	}

	@Override
	public void releaseTarget(Object target) {
		// nothing to do
	}

	@Override
	public boolean isStatic() {
		return true;
	}

	@Override
	public boolean equals(Object other) {
		if (this == other) {
			return true;
		}
		if (!(other instanceof SingletonTargetSource)) {
			return false;
		}
		SingletonTargetSource otherTargetSource = (SingletonTargetSource) other;
		return this.target.equals(otherTargetSource.target);
	}

	@Override
	public int hashCode() {
		return this.target.hashCode();
	}

	@Override
	public String toString() {
		return "SingletonTargetSource for target object [" + ObjectUtils.identityToString(this.target) + "]";
	}
}

單例 Bean 對應的 TargetSource 都是 SingletonTargetSource,裡面儲存了這個單例 Bean。

總結

本文開頭的圖片展示了 Spring AOP 涉及到的大部分主要的 API,圍繞著這張圖片對這些 API 進行簡單的講述,對 Spring AOP 可以有一個整體的認識。不過 Spring AOP 還有一個部分,如何開啟整個 AOP 模組在本文沒有體現出來,簡單提一下,通過 @EnableAspectJAutoProxy 註解或者 <aop:aspectj-autoproxy /> XML 配置可以啟用 AOP 模組,底層會註冊一個 AbstractAutoProxyCreator 型別的 Bean 完成 AOP 自動代理,具體過程在後面的文章進行分析?

  • 在 Spring AOP 中有著 Joinpoint 連線點,Pointcut 切點和 Advice 通知,通過 Advisor 介面儲存一個 Advice,其子介面 PointcutAdvisor 又關聯一個 Pointcut,將 Pointcut 過濾 Joinpoint 的能力和 Advice 進行整合,這樣一來就將兩者關聯起來了。

  • Spring AOP 傳遞 Advice 的過程通常是通過 Advisor 實現的,而使用 Advice 的過程都是通過 MethodInterceptor 執行,如果不是該型別則通過 AdvisorAdapter 介面卡將其包裝成對應的 MethodInterceptor 方法攔截器。

  • Sping AOP 底層有 JDK 動態代理和 CGLIB 動態代理兩種方式,分別對應 JdkDynamicAopProxyCglibAopProxy,選擇哪種代理方式需要通過 DefaultAopProxyFactory 代理工廠根據目標類來決定。

  • 在 Spring AOP 中一個 AdvisedSupport 配置管理器,裡面儲存對應代理物件的配置資訊,例如滿足條件的 Advisor 物件、TargetSource 目標類來源,AdvisedSupport 在 Spring AOP 中扮演一個重要的角色。

  • Spring AOP 中的自動代理主要由 AbstractAutoProxyCreator 這個 Bean 進行建立,因為它實現了幾種 BeanPostProcessor,例如在 Bean 載入過程中,初始化後會呼叫 AbstractAutoProxyCreator 的方法進行處理,返回一個代理物件(如果有必要的話)。

在後續的文章我們主要圍繞自動代理進行分析,對於 Bean 的載入過程不熟悉的小夥伴可檢視我前面的《死磕Spring之IoC篇 - 文章導讀》文章,或者直接檢視《死磕Spring之IoC篇 - Bean 的建立過程》 這篇文章

相關文章