死磕Spring之AOP篇 - Spring AOP兩種代理物件的攔截處理

月圓吖發表於2021-04-22

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

Spring 版本:5.1.14.RELEASE

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

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

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

在前面幾篇文章依次介紹了 Spring AOP 自動代理的整個過程,入口在 AbstractAutoProxyCreator 這個類中,它實現了幾種 BeanPostProcessor介面,結合 Spring IoC,在 Bean 的載入過程中支援建立代理物件,通常在 Bean 的初始化後,也就是 Bean 處於一個“成熟態”的時候進行 AOP 代理。整個的處理過程比較複雜,需要找到當前 Spring 上下文所有的 Advisor,也就是 Advice 的容器介面,通常都是 PointcutAdvisor,還包含了一個 Pointcut 切點。接著就是從這些 Advisor 中篩選出能夠應用於這個 Bean 的 Advisor 出來,經過一些處理過程,最後通過 JdkDynamicAopProxy(JDK 動態代理)或者 ObjenesisCglibAopProxy(CGLIB 動態代理)建立一個代理物件。

本文將會分析 Spring AOP 建立的兩種代理物件的攔截處理是如何進行的。開始之前,我們得知道JDK 動態代理建立的代理物件,攔截處理在 InvocationHandler 實現類中;CGLIB 動態代理建立的代理物件,攔截處理在傳入的 Callback 回撥中,對於這兩種代理物件不是很熟悉的小夥伴可檢視我前面的文章?

JDK 動態代理

我們先來簡單回顧一下 Spring AOP 中 JDK 動態代理建立代理物件的過程,如下:

// JdkDynamicAopProxy.java
@Override
public Object getProxy(@Nullable ClassLoader classLoader) {
    // <1> 獲取需要代理的介面(目標類實現的介面,會加上 Spring 內部的幾個介面,例如 SpringProxy)
    Class<?>[] proxiedInterfaces = AopProxyUtils.completeProxiedInterfaces(this.advised, true);
    // <2> 判斷目標類是否重寫了 `equals` 或者 `hashCode` 方法
    // 沒有重寫在攔截到這兩個方法的時候,會呼叫當前類的實現
    findDefinedEqualsAndHashCodeMethods(proxiedInterfaces);
    // <3> 呼叫 JDK 的 Proxy#newProxyInstance(..) 方法建立代理物件
    // 傳入的引數就是當前 ClassLoader 類載入器、需要代理的介面、InvocationHandler 實現類
    return Proxy.newProxyInstance(classLoader, proxiedInterfaces, this);
}

可以看到呼叫 Proxy#newProxyInstance(..) 方法的 InvocationHandler 入參就是 this,也就是當前物件。我在前面的文章也講到過,JDK 動態代理建立的代理物件實現了入參中的介面,且繼承 Proxy,代理物件的攔截處理通過 Proxy 父類中 InvocationHandler 來完成,也就是入參中的 InvocationHandler 物件。那麼我們一起來看看 JdkDynamicAopProxy(實現了 InvocationHandler)是如何攔截處理的。

JdkDynamicAopProxy

org.springframework.aop.framework.JdkDynamicAopProxy,JDK 動態代理類,實現了 InvocationHandler 介面,可建立代理物件

invoke 方法

invoke(Object proxy, Method method, Object[] args) 方法,JDK 動態代理建立的代理物件的攔截處理,如下:

final class JdkDynamicAopProxy implements AopProxy, InvocationHandler, Serializable {
	/** 代理物件的配置資訊,例如儲存了 TargetSource 目標類來源、能夠應用於目標類的所有 Advisor */
	private final AdvisedSupport advised;

	/** 目標物件是否重寫了 equals 方法 */
	private boolean equalsDefined;

	/** 目標物件是否重寫了 hashCode 方法 */
	private boolean hashCodeDefined;

	/**  代理物件的攔截處理 */
	@Override
	@Nullable
	public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
		Object oldProxy = null;
		boolean setProxyContext = false;

		// <1> 獲取目標類的 TargetSource 物件,用於獲取目標類
		TargetSource targetSource = this.advised.targetSource;
		Object target = null;

		try {
			// <2> 如果攔截到下面幾種情況的方法,則需要進行額外處理

			// <2.1> 如果攔截到的方法是 `equals`,且目標類沒有重寫,則呼叫當前類重寫的 `equals` 方法
			if (!this.equalsDefined && AopUtils.isEqualsMethod(method)) {
				// The target does not implement the equals(Object) method itself.
				return equals(args[0]);
			}
			// <2.2> 否則,如果攔截到的方法是 `hashCode`,且目標類沒有重寫,則呼叫當前類重寫的 `hashCode` 方法
			else if (!this.hashCodeDefined && AopUtils.isHashCodeMethod(method)) {
				// The target does not implement the hashCode() method itself.
				return hashCode();
			}
			// <2.3> 否則,如果攔截到的是 DecoratingProxy 中的方法,則通過 AopProxyUtils 工具類計算出目標類的 Class 物件
			else if (method.getDeclaringClass() == DecoratingProxy.class) {
				// There is only getDecoratedClass() declared -> dispatch to proxy config.
				return AopProxyUtils.ultimateTargetClass(this.advised);
			}
			// <2.4> 否則,如果攔截到的是 Advised 中的方法,則通過 AopUtils 工具類呼叫該方法(反射)
			else if (!this.advised.opaque && method.getDeclaringClass().isInterface() &&
					method.getDeclaringClass().isAssignableFrom(Advised.class)) {
				// Service invocations on ProxyConfig with the proxy config...
				return AopUtils.invokeJoinpointUsingReflection(this.advised, method, args);
			}

			Object retVal;

			// <3> 如果 `expose-proxy` 屬性為 `true`,則需要暴露當前代理物件
			if (this.advised.exposeProxy) {
				// Make invocation available if necessary.
				// <3.1> 向 AopContext 中設定代理物件,並記錄 ThreadLocal 之前存放的代理物件
				// 這樣一來,在 Advice 或者被攔截方法中可以通過 AopContext 獲取到這個代理物件
				oldProxy = AopContext.setCurrentProxy(proxy);
				// <3.2> 標記這個代理物件被暴露了
				setProxyContext = true;
			}

			// Get as late as possible to minimize the time we "own" the target,
			// in case it comes from a pool.
			// <4> 獲取目標物件,以及它的 Class 物件
			target = targetSource.getTarget();
			Class<?> targetClass = (target != null ? target.getClass() : null);

			// Get the interception chain for this method.
			// <5> 獲取能夠應用於該方法的所有攔截器(有序)
			// 不同的 AspectJ 根據 @Order 排序
			// 同一個 AspectJ 中的 Advice 排序:AspectJAfterThrowingAdvice > AfterReturningAdviceInterceptor > AspectJAfterAdvice > AspectJAroundAdvice > MethodBeforeAdviceInterceptor
			List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);

			// Check whether we have any advice. If we don't, we can fallback on direct
			// reflective invocation of the target, and avoid creating a MethodInvocation.
			// <6> 如果攔截器鏈為空,則直接執行目標方法
			if (chain.isEmpty()) {
				// We can skip creating a MethodInvocation: just invoke the target directly
				// Note that the final invoker must be an InvokerInterceptor so we know it does
				// nothing but a reflective operation on the target, and no hot swapping or fancy proxying.
				// <6.1> 引數適配處理
				Object[] argsToUse = AopProxyUtils.adaptArgumentsIfNecessary(method, args);
				// <6.2> 執行目標方法(反射),並獲取返回結果
				retVal = AopUtils.invokeJoinpointUsingReflection(target, method, argsToUse);
			}
			// <7> 否則,需要根據攔截器鏈去執行目標方法
			else {
				// We need to create a method invocation...
				// <7.1> 建立一個方法呼叫器,並將前面第 `5` 步獲取到的攔截器鏈傳入其中
				// 該物件就是 Joinpoint 物件
				MethodInvocation invocation = new ReflectiveMethodInvocation(proxy, target, method, args, targetClass, chain);
				// Proceed to the joinpoint through the interceptor chain.
				// <7.2> 執行目標方法,以及所有的 MethodInterceptor 方法攔截器(Advice 通知器),並獲取返回結果
				retVal = invocation.proceed();
			}

			// Massage return value if necessary.
			// <8> 獲取目標方法返回值型別
			Class<?> returnType = method.getReturnType();
			// <9> 如果需要返回代理物件
			if (retVal != null // 返回值不為空
					&& retVal == target // 返回值就是當前目標物件
					&& returnType != Object.class // 返回值型別不是 Object 型別
					&& returnType.isInstance(proxy)  // 返回值型別就是代理物件的型別
					&& !RawTargetAccess.class.isAssignableFrom(method.getDeclaringClass()))
			{
				// Special case: it returned "this" and the return type of the method
				// is type-compatible. Note that we can't help if the target sets
				// a reference to itself in another returned object.
				// 將當前代理物件作為返回結果
				retVal = proxy;
			}
			// <10> 否則,如果返回值型別為原始型別(基本型別,不能為空)且方法的返回型別不是 Void,如果返回值為空則丟擲異常
			else if (retVal == null && returnType != Void.TYPE && returnType.isPrimitive()) {
				throw new AopInvocationException(
						"Null return value from advice does not match primitive return type for: " + method);
			}
			// <11> 返回 `retVal` 返回結果
			return retVal;
		}
		finally {
			// <12> 如果目標物件不為空,且 TargetSource 不是靜態的(表示每次都得返回一個新的目標物件)
			// 那麼需要釋放當前獲取到的目標物件,通常情況下我們的單例 Bean 對應的都是 SingletonTargetSource,不需要釋放
			if (target != null && !targetSource.isStatic()) {
				// Must have come from TargetSource.
				targetSource.releaseTarget(target);
			}
			// <13> 如果暴露了當前代理物件,則需要將之前的代理物件重新設定到 ThreadLocal 中
			if (setProxyContext) {
				// Restore old proxy.
				AopContext.setCurrentProxy(oldProxy);
			}
		}
	}
}

整個的攔截處理過程如下:

  1. 獲取目標類的 TargetSource 物件,用於獲取目標類
  2. 如果攔截到下面幾種情況的方法,則需要進行額外處理
    1. 如果攔截到的方法是 equals,且目標類沒有重寫,則呼叫當前類重寫的 equals 方法
    2. 否則,如果攔截到的方法是 hashCode,且目標類沒有重寫,則呼叫當前類重寫的 hashCode 方法
    3. 否則,如果攔截到的是 DecoratingProxy 中的方法,則通過 AopProxyUtils 工具類計算出目標類的 Class 物件
    4. 否則,如果攔截到的是 Advised 中的方法,則通過 AopUtils 工具類呼叫該方法(反射)
  3. 如果 expose-proxy 屬性為 true,則需要暴露當前代理物件
    1. 向 AopContext 中設定代理物件,並記錄 ThreadLocal 之前存放的代理物件,這樣一來,在 Advice 或者被攔截方法中可以通過 AopContext 獲取到這個代理物件
    2. 標記這個代理物件被暴露了
  4. 獲取目標物件,以及它的 Class 物件
  5. 呼叫當前 AdvisedSupport 的 getInterceptorsAndDynamicInterceptionAdvice(..),獲取能夠應用於該方法的所有攔截器(有序)
    • 不同的 AspectJ 根據 @Order 排序
    • 同一個 AspectJ 中的 Advice 排序:AspectJAfterThrowingAdvice > AfterReturningAdviceInterceptor > AspectJAfterAdvice > AspectJAroundAdvice > MethodBeforeAdviceInterceptor
  6. 如果攔截器鏈為空,則直接執行目標方法
    1. 引數適配處理
    2. 執行目標方法(反射),並獲取返回結果
  7. 否則,需要根據攔截器鏈去執行目標方法
    1. 建立一個 ReflectiveMethodInvocation 方法呼叫器,並將前面第 5 步獲取到的攔截器鏈傳入其中
    2. 執行方法呼叫器,會執行目標方法,以及所有的 MethodInterceptor 方法攔截器(Advice 通知器),並獲取返回結果
  8. 獲取目標方法返回值型別
  9. 如果需要返回代理物件,將當前代理物件作為返回結果
  10. 否則,如果返回值型別為原始型別(基本型別,不能為空)且方法的返回型別不是 Void,如果返回值為空則丟擲異常
  11. 返回 retVal 返回結果
  12. 如果目標物件不為空,且 TargetSource 不是靜態的(表示每次都得返回一個新的目標物件),那麼需要釋放當前獲取到的目標物件,通常情況下我們的單例 Bean 對應的都是 SingletonTargetSource,不需要釋放
  13. 如果暴露了當前代理物件,則需要將之前的代理物件重新設定到 ThreadLocal 中

JDK 動態代理建立的代理物件的攔截處理過程整體邏輯上並不複雜,通過上面的描述大致上可以理解。上面過程複雜的是上面的第 5 步和第 7 步,一個是獲取該方法的攔截器們,一個是執行整個攔截器鏈,接下來我們依次分析

至於上面的第 5 步得到的方法攔截器們的順序為什麼是這個,可以檢視我前面的 《Spring AOP 自動代理(二)篩選合適的通知器》這篇文章

AdvisedSupport

org.springframework.aop.framework.AdvisedSupport,代理物件的配置管理器

獲取能夠應用於方法的攔截器們

getInterceptorsAndDynamicInterceptionAdvice(Method method, @Nullable Class<?> targetClass) 方法,獲取能夠應用於該方法的所有攔截器,如下:

public List<Object> getInterceptorsAndDynamicInterceptionAdvice(Method method, @Nullable Class<?> targetClass) {
    // <1> 建立一個方法快取 Key
    MethodCacheKey cacheKey = new MethodCacheKey(method);
    // <2> 嘗試從快取中獲取
    List<Object> cached = this.methodCache.get(cacheKey);
    // 快取未命中,則進行下一步處理
    if (cached == null) {
        /*
         * <3> 獲取能夠應用於該方法的所有攔截器(有序)
         * 篩選出能夠應用於該方法的所有 Advisor,並獲取對應的 MethodInterceptor,也就是 Advice(如果不是方法攔截器則會包裝成對應的 MethodInterceptor)
         * 因為 Advisor 是排好序的,所以返回的 MethodInterceptor 也是有序的
         *
         * 為什麼 `cached` 使用 `List<Object>` 儲存?
         * 因為有些元素是 MethodInterceptor 和 MethodMatcher 的包裝物件,並不是 MethodInterceptor
         */
        cached = this.advisorChainFactory.getInterceptorsAndDynamicInterceptionAdvice(
                this, method, targetClass);
        // <4> 將該方法對應的攔截器鏈路放入快取
        this.methodCache.put(cacheKey, cached);
    }
    // <5> 返回能夠應用於該方法的所有攔截器(有序)
    return cached;
}

該方法的處理過程如下:

  1. 建立一個方法快取 Key
  2. 嘗試從快取中獲取,快取未命中,則進行下一步處理
  3. 呼叫 DefaultAdvisorChainFactory 的 getInterceptorsAndDynamicInterceptionAdvice(..) 方法,獲取能夠應用於該方法的所有攔截器(有序)
    • 篩選出能夠應用於該方法的所有 Advisor,並獲取對應的 MethodInterceptor,也就是 Advice(如果不是方法攔截器則會包裝成對應的 MethodInterceptor)
    • 因為 Advisor 是排好序的,所以返回的 MethodInterceptor 也是有序的
  4. 將該方法對應的攔截器鏈路放入快取
  5. 返回能夠應用於該方法的所有攔截器(有序)

這個方法的的處理過程只是嘗試去快取中獲取,快取未命中,則通過 DefaultAdvisorChainFactory 獲取能夠應用於該方法的所有攔截器

DefaultAdvisorChainFactory

org.springframework.aop.framework.DefaultAdvisorChainFactory,獲取攔截器鏈路的預設實現

獲取能夠應用於方法的攔截器們

getInterceptorsAndDynamicInterceptionAdvice(Method method, @Nullable Class<?> targetClass) 方法,獲取能夠應用於該方法的所有攔截器,如下:

@Override
public List<Object> getInterceptorsAndDynamicInterceptionAdvice(
        Advised config, Method method, @Nullable Class<?> targetClass) {

    // <1> 獲取 DefaultAdvisorAdapterRegistry 例項物件
    AdvisorAdapterRegistry registry = GlobalAdvisorAdapterRegistry.getInstance();
    // <2> 獲取能夠應用到 `targetClass` 的 Advisor 們
    Advisor[] advisors = config.getAdvisors();
    List<Object> interceptorList = new ArrayList<>(advisors.length);
    Class<?> actualClass = (targetClass != null ? targetClass : method.getDeclaringClass());
    Boolean hasIntroductions = null;

    // <3> 遍歷上一步獲取到的 Advisor 們
    // 篩選出哪些 Advisor 需要處理當前被攔截的 `method`,並獲取對應的 MethodInterceptor(Advice,如果不是方法攔截器則會包裝成對應的 MethodInterceptor)
    for (Advisor advisor : advisors) {
        /*
         * <3.1> 如果是 PointcutAdvisor 型別,則需要對目標物件的型別和被攔截的方法進行匹配
         */
        if (advisor instanceof PointcutAdvisor) {
            // Add it conditionally.
            PointcutAdvisor pointcutAdvisor = (PointcutAdvisor) advisor;
            /*
             * <3.1.1> 判斷這個 PointcutAdvisor 是否匹配目標物件的型別,無法匹配則跳過
             */
            if (config.isPreFiltered() // AdvisedSupport 是否已經過濾過目標物件的型別
                    || pointcutAdvisor.getPointcut().getClassFilter().matches(actualClass)) // 呼叫 Pointcut 的 ClassFilter 對目標物件的型別進行匹配
            {
                // <3.1.2> 獲取 Pointcut 的 MethodMatcher 方法匹配器對該方法進行匹配
                // 參考 AspectJExpressionPointcut,底層藉助於 AspectJ 的處理
                MethodMatcher mm = pointcutAdvisor.getPointcut().getMethodMatcher();
                boolean match;
                if (mm instanceof IntroductionAwareMethodMatcher) {
                    if (hasIntroductions == null) {
                        hasIntroductions = hasMatchingIntroductions(advisors, actualClass);
                    }
                    match = ((IntroductionAwareMethodMatcher) mm).matches(method, actualClass, hasIntroductions);
                }
                else {
                    match = mm.matches(method, actualClass);
                }
                /*
                 * <3.1.3> 如果這個方法匹配成功,則進行下面的處理
                 */
                if (match) {
                    // <3.1.4> 從 Advisor 中獲取 Advice,幷包裝成 MethodInterceptor 攔截器物件(如果不是的話)
                    MethodInterceptor[] interceptors = registry.getInterceptors(advisor);
                    // <3.1.5> 若 MethodMatcher 的 `isRuntime()` 返回 `true`,則表明 MethodMatcher 要在執行時做一些檢測
                    if (mm.isRuntime()) {
                        // Creating a new object instance in the getInterceptors() method
                        // isn't a problem as we normally cache created chains.
                        for (MethodInterceptor interceptor : interceptors) {
                            // <3.1.5.1> 將上面獲取到的 MethodInterceptor 和 MethodMatcher 包裝成一個物件,並新增至 `interceptorList`
                            interceptorList.add(new InterceptorAndDynamicMethodMatcher(interceptor, mm));
                        }
                    }
                    // <3.1.6> 否則,直接將 MethodInterceptor 們新增至 `interceptorList`
                    else {
                        interceptorList.addAll(Arrays.asList(interceptors));
                    }
                }
            }
        }
        /*
         * <3.2> 否則,如果是 IntroductionAdvisor 型別,則需要對目標物件的型別進行匹配
         */
        else if (advisor instanceof IntroductionAdvisor) {
            IntroductionAdvisor ia = (IntroductionAdvisor) advisor;
            /*
             * <3.2.1> 判斷這個 IntroductionAdvisor 是否匹配目標物件的型別,無法匹配則跳過
             */
            if (config.isPreFiltered() // AdvisedSupport 是否已經過濾過目標物件的型別
                    || ia.getClassFilter().matches(actualClass)) // 呼叫 Pointcut 的 ClassFilter 對目標物件的型別進行匹配
            {
                // <3.2.2> 從 Advisor 中獲取 Advice,幷包裝成 MethodInterceptor 攔截器物件(如果不是的話)
                Interceptor[] interceptors = registry.getInterceptors(advisor);
                // <3.2.3> 直接將 MethodInterceptor 們新增至 `interceptorList`
                interceptorList.addAll(Arrays.asList(interceptors));
            }
        }
        /*
         * <3.3> 否則,不需要對目標物件的型別和被攔截的方法進行匹配
         */
        else {
            // <3.3.1> 從 Advisor 中獲取 Advice,幷包裝成 MethodInterceptor 攔截器物件(如果不是的話)
            Interceptor[] interceptors = registry.getInterceptors(advisor);
            // <3.3.2> 直接將 MethodInterceptor 們新增至 `interceptorList`
            interceptorList.addAll(Arrays.asList(interceptors));
        }
    }

    // <4> 返回 `interceptorList` 所有的 MethodInterceptor 攔截器
    // 因為 Advisor 是排好序的,所以這裡的 `interceptorList` 是有序的
    return interceptorList;
}

該方法的處理過程如下:

  1. 獲取 DefaultAdvisorAdapterRegistry 例項物件
  2. 獲取能夠應用到 targetClass 的 Advisor 們
  3. 遍歷上一步獲取到的 Advisor 們,篩選出哪些 Advisor 需要處理當前被攔截的 method,並獲取對應的 MethodInterceptor(Advice,如果不是方法攔截器則會包裝成對應的 MethodInterceptor)
    1. 如果是 PointcutAdvisor 型別,則需要對目標物件的型別和被攔截的方法進行匹配
      1. 判斷這個 PointcutAdvisor 是否匹配目標物件的型別(ClassFilter),無法匹配則跳過
      2. 獲取 Pointcut 的 MethodMatcher 方法匹配器對該方法進行匹配,參考 AspectJExpressionPointcut,底層藉助於 AspectJ 的處理
      3. 如果這個方法匹配成功,則進行下面的處理
      4. 通過 DefaultAdvisorAdapterRegistry 從 Advisor 中獲取 Advice,幷包裝成 MethodInterceptor 攔截器物件(如果不是的話)
      5. 若 MethodMatcher 的 isRuntime() 返回 true,則表明 MethodMatcher 要在執行時做一些檢測
        1. 將上面獲取到的 MethodInterceptor 和 MethodMatcher 包裝成一個物件,並新增至 interceptorList
      6. 否則,直接將 MethodInterceptor 們新增至 interceptorList
    2. 否則,如果是 IntroductionAdvisor 型別,則需要對目標物件的型別進行匹配
      1. 判斷這個 IntroductionAdvisor 是否匹配目標物件的型別(ClassFilter),無法匹配則跳過
      2. 通過 DefaultAdvisorAdapterRegistry 從 Advisor 中獲取 Advice,幷包裝成 MethodInterceptor 攔截器物件(如果不是的話)
      3. 直接將 MethodInterceptor 們新增至 interceptorList
    3. 否則,不需要對目標物件的型別和被攔截的方法進行匹配,直接通過
      1. 通過 DefaultAdvisorAdapterRegistry 從 Advisor 中獲取 Advice,幷包裝成 MethodInterceptor 攔截器物件(如果不是的話)
      2. 直接將 MethodInterceptor 們新增至 interceptorList
  4. 返回 interceptorList 所有的 MethodInterceptor 攔截器,因為 Advisor 是排好序的,所以這裡的 interceptorList 是有序的

這裡做一個小結,整個處理過程並不複雜,當時建立代理物件的時候篩選出了能夠應用於當前 Bean 的所有 Advisor,現在要做的是先從這些 Advisor 中篩選出能夠應用於當前方法的 Advisor,然後通過 DefaultAdvisorAdapterRegistry 獲取 Advisor 對應的 MethodInterceptor 方法攔截器。對於 PointcutAdvisorIntroductionAdvisor 處理稍微有點不同,因為前者多了一個 Pointcut,需要通過它的 MethodMatcher 對方法進行匹配,其他的差不多。

DefaultAdvisorAdapterRegistry

org.springframework.aop.framework.adapter.DefaultAdvisorAdapterRegistry,預設的 Advisor 介面卡註冊中心,主要是對 Advisor 中的 Advice 進行匹配處理

wrap 方法

@Override
public Advisor wrap(Object adviceObject) throws UnknownAdviceTypeException {
    if (adviceObject instanceof Advisor) { // Advisor 型別,直接返回
        return (Advisor) adviceObject;
    }
    if (!(adviceObject instanceof Advice)) { // 非 Advice 介面,丟擲異常
        throw new UnknownAdviceTypeException(adviceObject);
    }
    Advice advice = (Advice) adviceObject;
    if (advice instanceof MethodInterceptor) { // MethodInterceptor 型別,包裝成 DefaultPointcutAdvisor 物件
        // So well-known it doesn't even need an adapter.
        return new DefaultPointcutAdvisor(advice);
    }
    for (AdvisorAdapter adapter : this.adapters) {
        // Check that it is supported.
        // 檢查該 Advice 型別是否支援
        if (adapter.supportsAdvice(advice)) {
            // 包裝成 DefaultPointcutAdvisor 物件 返回
            return new DefaultPointcutAdvisor(advice);
        }
    }
    throw new UnknownAdviceTypeException(advice);
}

將 Advice 包裝成 Advisor 物件

getInterceptors 方法

@Override
public MethodInterceptor[] getInterceptors(Advisor advisor) throws UnknownAdviceTypeException {
    List<MethodInterceptor> interceptors = new ArrayList<>(3);
    // <1> 獲取 Advice 通知器
    Advice advice = advisor.getAdvice();
    /*
     * <2> 若 Advice 是 MethodInterceptor 型別的,直接新增到 `interceptors`即可
     * 例如 AspectJAfterThrowingAdvice、AspectJAfterAdvice、AspectJAroundAdvice
     */
    if (advice instanceof MethodInterceptor) {
        interceptors.add((MethodInterceptor) advice);
    }

    /*
     * <3> 通過 Advisor 介面卡將 Advice 封裝成對應的 MethodInterceptor 物件,並新增至 `interceptors`
     * AspectJAfterReturningAdvice -> AfterReturningAdviceInterceptor
     * AspectJMethodBeforeAdvice -> MethodBeforeAdviceInterceptor
     */
    for (AdvisorAdapter adapter : this.adapters) {
        if (adapter.supportsAdvice(advice)) {
            interceptors.add(adapter.getInterceptor(advisor));
        }
    }
    // <4> 沒有對應的 MethodInterceptor 則丟擲異常
    if (interceptors.isEmpty()) {
        throw new UnknownAdviceTypeException(advisor.getAdvice());
    }
    // <5> 將 `interceptors` 轉換成陣列並返回
    return interceptors.toArray(new MethodInterceptor[0]);
}

獲取 Advisor 中的 MethodInterceptor 方法攔截器:

  1. 獲取 Advice 通知器
  2. 若 Advice 是 MethodInterceptor 型別的,直接新增到 interceptors即可
    • 例如 AspectJAfterThrowingAdvice、AspectJAfterAdvice、AspectJAroundAdvice
  3. 通過 Advisor 介面卡將 Advice 封裝成對應的 MethodInterceptor 物件,並新增至 interceptors
    • AspectJAfterReturningAdvice -> AfterReturningAdviceInterceptor
    • AspectJMethodBeforeAdvice -> MethodBeforeAdviceInterceptor
  4. 沒有對應的 MethodInterceptor 則丟擲異常
  5. interceptors 轉換成陣列並返回

可以看到,在 Spring AOP 的攔截處理中,使用的 Advice 都是 MethodInterceptor 方法攔截器

ReflectiveMethodInvocation

org.springframework.aop.framework.ReflectiveMethodInvocation,代理物件目標方法的呼叫器,包含方法對應的 MethodInterceptor 攔截器鏈

建構函式

public class ReflectiveMethodInvocation implements ProxyMethodInvocation, Cloneable {

	/** 代理物件 */
	protected final Object proxy;
	/** 目標物件 */
	@Nullable
	protected final Object target;
	/** 目標方法 */
	protected final Method method;
	/** 方法入參 */
	protected Object[] arguments;
	/** 目標物件的 Class 物件 */
	@Nullable
	private final Class<?> targetClass;
	/** 自定義屬性 */
	@Nullable
	private Map<String, Object> userAttributes;
	/** 方法的攔截器鏈路 */
	protected final List<?> interceptorsAndDynamicMethodMatchers;
	/** 當前已經執行完的攔截器的位置索引,執行完則執行目標方法 */
	private int currentInterceptorIndex = -1;

	protected ReflectiveMethodInvocation(
			Object proxy, @Nullable Object target, Method method, @Nullable Object[] arguments,
			@Nullable Class<?> targetClass, List<Object> interceptorsAndDynamicMethodMatchers) {
		this.proxy = proxy;
		this.target = target;
		this.targetClass = targetClass;
		// 獲取目標方法,如果是橋接方法則會找到目標方法
		this.method = BridgeMethodResolver.findBridgedMethod(method);
		// 對該方法引數進行適配處理(如果有必要)
		this.arguments = AopProxyUtils.adaptArgumentsIfNecessary(method, arguments);
		this.interceptorsAndDynamicMethodMatchers = interceptorsAndDynamicMethodMatchers;
	}
}

上面的屬性通過上面的註釋進行理解即可

invokeJoinpoint 方法

invokeJoinpoint() 方法,執行目標方法,如下:

@Nullable
protected Object invokeJoinpoint() throws Throwable {
    return AopUtils.invokeJoinpointUsingReflection(this.target, this.method, this.arguments);
}

public static Object invokeJoinpointUsingReflection(@Nullable Object target, Method method, Object[] args)
        throws Throwable {
    try {
        ReflectionUtils.makeAccessible(method);
        return method.invoke(target, args);
    } catch (InvocationTargetException ex) {
        throw ex.getTargetException();
    } catch (IllegalArgumentException ex) {
        throw new AopInvocationException("AOP configuration seems to be invalid: tried calling method [" +
                method + "] on target [" + target + "]", ex);
    } catch (IllegalAccessException ex) {
        throw new AopInvocationException("Could not access method [" + method + "]", ex);
    }
}

基於反射執行目標方法

proceed 方法

proceed() 方法,方法呼叫器的執行,如下:

@Override
@Nullable
public Object proceed() throws Throwable {
    // We start with an index of -1 and increment early.
    // <1> 如果當前已經執行完的攔截器的位置索引就是最後一個,那麼即可執行目標方法
    if (this.currentInterceptorIndex == this.interceptorsAndDynamicMethodMatchers.size() - 1) {
        // 執行目標方法(底層反射機制)
        return invokeJoinpoint();
    }

    // <2> 按順序獲取攔截器
    Object interceptorOrInterceptionAdvice =
            this.interceptorsAndDynamicMethodMatchers.get(++this.currentInterceptorIndex);
    /**
     * <3> 如果是 InterceptorAndDynamicMethodMatcher 型別,表示 MethodMatcher 在真正的執行時需要做一些檢測
     * 參考 {@link DefaultAdvisorChainFactory#getInterceptorsAndDynamicInterceptionAdvice }
     */
    if (interceptorOrInterceptionAdvice instanceof InterceptorAndDynamicMethodMatcher) {
        // Evaluate dynamic method matcher here: static part will already have
        // been evaluated and found to match.
        InterceptorAndDynamicMethodMatcher dm =
                (InterceptorAndDynamicMethodMatcher) interceptorOrInterceptionAdvice;
        Class<?> targetClass = (this.targetClass != null ? this.targetClass : this.method.getDeclaringClass());
        // <3.1> 通過 MethodMatcher 對目標方法進行匹配
        if (dm.methodMatcher.matches(this.method, targetClass, this.arguments)) {
            // 匹配通過,則執行這個攔截器,並傳遞當前物件
            return dm.interceptor.invoke(this);
        }
        // <3.2> 否則,直接跳過這個攔截器
        else {
            // Dynamic matching failed.
            // Skip this interceptor and invoke the next in the chain.
            return proceed();
        }
    }
    else {
        // It's an interceptor, so we just invoke it: The pointcut will have
        // been evaluated statically before this object was constructed.
        // <4> 否則執行這個攔截器,並傳遞當前物件
        return ((MethodInterceptor) interceptorOrInterceptionAdvice).invoke(this);
    }
}

方法呼叫器的執行過程如下:

  1. 如果 currentInterceptorIndex(已經執行完的攔截器的位置索引)就是最後一個,那麼即可執行目標方法,呼叫 invokeJoinpoint() 方法
  2. 按順序獲取攔截器,先將 currentInterceptorIndex 自增 1
  3. 如果是 InterceptorAndDynamicMethodMatcher 型別,表示 MethodMatcher 在真正的執行時需要做一些檢測
    1. 通過 MethodMatcher 對目標方法進行匹配,匹配通過,則執行這個攔截器,並傳遞當前物件,呼叫 MethodInterceptor#invoke(this) 方法
    2. 否則,直接跳過這個攔截器,繼續呼叫當前 proceed() 方法
  4. 否則執行這個攔截器,並傳遞當前物件,呼叫 MethodInterceptor#invoke(this) 方法

上面第 3 步,為什麼可能存在 InterceptorAndDynamicMethodMatcher 物件,返回前面的 DefaultAdvisorChainFactory 可以知曉答案

可以看到整個的方法呼叫過程是根據 MethodInterceptor 的順序一個一個往下執行的,執行完了則執行目標方法。

那麼你是否有疑問,為什麼執行完最後一個攔截器才執行目標方法,後置通知器不是需要等目標方法執行後才進行處理的嗎?其實你進入 Advice 實現的 MethodInterceptor#invoke(MethodInvocation) 方法就知道答案了,可檢視下面這張圖。

方法呼叫器的執行攔截器鏈圖

死磕Spring之AOP篇 - Spring AOP兩種代理物件的攔截處理

到這裡, JDK 動態代理建立的代理物件的攔截處理過程全部分析完了,做一個小的總結:

  1. 在攔截處理某個方法的時候,需要先通過 AdvisedSupport -> DefaultAdvisorChainFactory -> DefaultAdvisorAdapterRegistry 獲取到能夠應用於該方法的 Advisor 們,例項拿到的是它們對應的 MethodInterceptor 攔截器們,他們的順序如上面這張圖所示;

  2. 獲取到了 MethodInterceptor 方法攔截器後,建立一個 ReflectiveMethodInvocation 方法呼叫器,進行方法的攔截處理,依次呼叫每個 MethodInterceptor 的 invoke(..) 方法,在最後執行目標方法;

  3. 在上面這張圖你可以看到不同 MethodInterceptor 的執行順序,不過實際 Advice 的執行邏輯順序是:

    Around 前處理 > Before > Around 後處理 > After > AfterReturning|AfterThrowing

------------------------------------

CGLIB 動態代理

我們先來簡單回顧一下 Spring AOP 中 CGLIB 動態代理建立代理物件的物件:

// CglibAopProxy.java
@Override
public Object getProxy(@Nullable ClassLoader classLoader) {
    // ... 下面只展示部分程式碼
    
    // <5> 建立 CGLIB 的增強類,並進行接下來的配置
    Enhancer enhancer = createEnhancer();
    // <5.1> 設定被代理的類
    enhancer.setSuperclass(proxySuperClass);
    // <5.2> 設定需要代理的介面(可能沒有,不過都會加上 Spring 內部的幾個介面,例如 SpringProxy)
    enhancer.setInterfaces(AopProxyUtils.completeProxiedInterfaces(this.advised));
    // <5.3> 設定命名策略,預設生成的代理物件的名稱中包含 '$$' 和 'BySpringCGLIB'
    enhancer.setNamingPolicy(SpringNamingPolicy.INSTANCE);

    // <5.4> 獲取回撥介面,也就是 MethodInterceptor 方法攔截器
    Callback[] callbacks = getCallbacks(rootClass);
    Class<?>[] types = new Class<?>[callbacks.length];
    for (int x = 0; x < types.length; x++) {
        types[x] = callbacks[x].getClass();
    }
    // <5.5> 設定 Callback 過濾器,用於篩選出方法對應的 Callback 回撥介面
    enhancer.setCallbackFilter(new ProxyCallbackFilter(
            this.advised.getConfigurationOnlyCopy(), this.fixedInterceptorMap, this.fixedInterceptorOffset));
    enhancer.setCallbackTypes(types);

    // Generate the proxy class and create a proxy instance.
    // <6> 建立代理物件,CGLIG 位元組碼替身,建立目標類的子類
    return createProxyClassAndInstance(enhancer, callbacks);
}

可以看到通過 Enhancer 建立代理物件,也就是目標類的子類,設定了一個 Callback 陣列和 CallbackFilter 過濾器,CallbackFilter 用於獲取目標方法對應的 Callback。這些內容都在上一篇《Spring AOP 自動代理(三)建立代理物件》文章中分析過,這裡不再講述,上一篇文章知道,CGLIB 進行 AOP 代理的通用攔截器是 DynamicAdvisedInterceptor 物件,那麼接下來我們來看看這個攔截是怎麼處理的。

DynamicAdvisedInterceptor

org.springframework.aop.framework.CglibAopProxy.DynamicAdvisedInterceptor,CglibAopProxy 的私有靜態內部類

intercept 方法

intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) 方法,CGLIB 動態代理建立代理物件的通用攔截處理方法,如下:

private final AdvisedSupport advised;

@Override
@Nullable
public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
    Object oldProxy = null;
    boolean setProxyContext = false;
    Object target = null;
    // <1> 獲取目標類的 TargetSource 物件,用於獲取目標類
    TargetSource targetSource = this.advised.getTargetSource();
    try {
        // <2> 如果 `expose-proxy` 屬性為 `true`,則需要暴露當前代理物件
        if (this.advised.exposeProxy) {
            // Make invocation available if necessary.
            // <2.1> 向 AopContext 中設定代理物件,並記錄 ThreadLocal 之前存放的代理物件
            // 這樣一來,在 Advice 或者被攔截方法中可以通過 AopContext 獲取到這個代理物件
            oldProxy = AopContext.setCurrentProxy(proxy);
            // <2.2> 標記這個代理物件被暴露了
            setProxyContext = true;
        }
        // Get as late as possible to minimize the time we "own" the target, in case it comes from a pool...
        // <3> 獲取目標物件,以及它的 Class 物件
        target = targetSource.getTarget();
        Class<?> targetClass = (target != null ? target.getClass() : null);
        // <4> 獲取能夠應用於該方法的所有攔截器(有序)
        // 不同的 AspectJ 根據 @Order 排序
        // 同一個 AspectJ 中的 Advice 排序:AspectJAfterThrowingAdvice > AfterReturningAdviceInterceptor > AspectJAfterAdvice > AspectJAroundAdvice > MethodBeforeAdviceInterceptor
        List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);
        Object retVal;
        // <5> 如果攔截器鏈為空,則直接執行目標方法
        if (chain.isEmpty() && Modifier.isPublic(method.getModifiers())) {
            // <5.1> 引數適配處理
            Object[] argsToUse = AopProxyUtils.adaptArgumentsIfNecessary(method, args);
            // <5.2> 執行目標方法(反射),並獲取返回結果
            retVal = methodProxy.invoke(target, argsToUse);
        }
        // <6> 否則,需要根據攔截器鏈去執行目標方法
        else {
            // <6.1> 建立一個方法呼叫器,並將前面獲取到的攔截器鏈傳入其中,該物件就是 Joinpoint 物件
            // <6.2> 執行目標方法,以及所有的 MethodInterceptor 方法攔截器(Advice 通知器),並獲取返回結果
            retVal = new CglibMethodInvocation(proxy, target, method, args, targetClass, chain, methodProxy).proceed();
        }
        // <7> 對最終的返回結果進一步處理(返回結果是否需要為代理物件,返回結果是否不能為空)
        retVal = processReturnType(proxy, target, method, retVal);
        // <8> 返回 `retVal` 返回結果
        return retVal;
    }
    finally {
        // <9> 如果目標物件不為空,且 TargetSource 不是靜態的(表示每次都得返回一個新的目標物件)
        // 那麼需要釋放當前獲取到的目標物件,通常情況下我們的單例 Bean 對應的都是 SingletonTargetSource,不需要釋放
        if (target != null && !targetSource.isStatic()) {
            targetSource.releaseTarget(target);
        }
        // <10> 如果暴露了當前代理物件,則需要將之前的代理物件重新設定到 ThreadLocal 中
        if (setProxyContext) {
            AopContext.setCurrentProxy(oldProxy);
        }
    }
}

整個的攔截處理過程:

  1. 獲取目標類的 TargetSource 物件,用於獲取目標類
  2. 如果 expose-proxy 屬性為 true,則需要暴露當前代理物件
    1. 向 AopContext 中設定代理物件,並記錄 ThreadLocal 之前存放的代理物件
    2. 標記這個代理物件被暴露了
  3. 獲取目標物件,以及它的 Class 物件
  4. 呼叫當前 AdvisedSupport 的 getInterceptorsAndDynamicInterceptionAdvice(..),獲取能夠應用於該方法的所有攔截器(有序)
    • 不同的 AspectJ 根據 @Order 排序
    • 同一個 AspectJ 中的 Advice 排序:AspectJAfterThrowingAdvice > AfterReturningAdviceInterceptor > AspectJAfterAdvice > AspectJAroundAdvice > MethodBeforeAdviceInterceptor
  5. 如果攔截器鏈為空,則直接執行目標方法
    1. 引數適配處理
    2. 執行目標方法(反射),並獲取返回結果
  6. 否則,需要根據攔截器鏈去執行目標方法
    1. 建立一個 CglibMethodInvocation 方法呼叫器,並將前面第 5 步獲取到的攔截器鏈傳入其中
    2. 執行方法呼叫器,會執行目標方法,以及所有的 MethodInterceptor 方法攔截器(Advice 通知器),並獲取返回結果
  7. 對最終的返回結果進一步處理(返回結果是否需要為代理物件,返回結果是否不能為空)
  8. 返回 retVal 返回結果
  9. 如果目標物件不為空,且 TargetSource 不是靜態的(表示每次都得返回一個新的目標物件),那麼需要釋放當前獲取到的目標物件,通常情況下我們的單例 Bean 對應的都是 SingletonTargetSource,不需要釋放
  10. 如果暴露了當前代理物件,則需要將之前的代理物件重新設定到 ThreadLocal 中

CGLIB 動態代理建立代理物件的通用攔截處理過程,和前面講到的 JDK 動態代理建立的代理物件差不多,這裡不再講述。這裡不同的是建立的方法呼叫器是 CglibMethodInvocation 物件,我們一起來看看這個物件

CglibMethodInvocation

org.springframework.aop.framework.CglibAopProxy.CglibMethodInvocation,CglibAopProxy 的私有靜態內部類

private static class CglibMethodInvocation extends ReflectiveMethodInvocation {

    @Nullable
    private final MethodProxy methodProxy;

    public CglibMethodInvocation(Object proxy, @Nullable Object target, Method method,
            Object[] arguments, @Nullable Class<?> targetClass,
            List<Object> interceptorsAndDynamicMethodMatchers, MethodProxy methodProxy) {

        super(proxy, target, method, arguments, targetClass, interceptorsAndDynamicMethodMatchers);

        // Only use method proxy for public methods not derived from java.lang.Object
        // 設定代理方法物件
        this.methodProxy = (Modifier.isPublic(method.getModifiers()) // 方法被 public 修飾
                && method.getDeclaringClass() != Object.class // 不是 Object 中的方法
                && !AopUtils.isEqualsMethod(method) // 不是 `equals` 方法
                && !AopUtils.isHashCodeMethod(method) // 不是 `hashCode` 方法
                && !AopUtils.isToStringMethod(method) // 不是 `toString` 方法
                ? methodProxy : null);
    }
    
    @Override
    protected Object invokeJoinpoint() throws Throwable {
        if (this.methodProxy != null) {
            // 執行代理方法物件(反射)
            return this.methodProxy.invoke(this.target, this.arguments);
        }
        else {
            // 執行目標方法物件(反射)
            return super.invokeJoinpoint();
        }
    }
}

可以看到它繼承了 ReflectiveMethodInvocation 這個類,重寫了 invokeJoinpoint() 執行目標方法的方法,區別在於 CGLIB 使用 MethodProxy 執行目標方法

總結

Spring AOP 有 JDK 動態代理CGLIB 動態代理 兩種建立代理物件的方式,前者通過 JdkDynamicAopProxy 建立代理物件,對應的 InvocationHandler 就是這個 JdkDynamicAopProxy 物件;後者通過 CglibAopPeoxy 建立代理物件,會設定了一個 Callback 陣列和 CallbackFilter 過濾器,CallbackFilter 用於獲取目標方法對應的 Callback,其中進行 AOP 代理的通用攔截器是 DynamicAdvisedInterceptor 方法攔截器。

  • JdkDynamicAopProxy:先獲取到能應用於方法的所有 MethodInterceptor(也就是 Advice),然後通過 ReflectiveMethodInvocation 方法呼叫器進行處理
  • DynamicAdvisedInterceptor:和上者處理邏輯差不多,區別是通過 CglibMethodInvocation 方法呼叫器進行處理,它重寫了 ReflectiveMethodInvocation 執行目標方法的方法

至於 ReflectiveMethodInvocation 方法呼叫器的執行邏輯可以參考上面的講解

到這裡,關於 Spring AOP 自動代理,以及代理物件的攔截處理到這裡已經全部講述完了。其中肯定存在不少的問題,如有疑惑,可在留言區進行留言。

相關文章