【Spring】AOP實現原理

shanml發表於2023-10-30

註冊AOP代理建立器

在平時開發過程中,如果想開啟AOP,一般會使用@EnableAspectJAutoProxy註解,這樣在啟動時,它會向Spring容器註冊一個代理建立器用於建立代理物件,AOP使用的是AnnotationAwareAspectJAutoProxyCreator,它實現了SmartInstantiationAwareBeanPostProcessor,從名字中可以看出這是一個Bean後置處理器BeanPostProcessor,BeanPostProcessor是Spring提供的一個擴充套件點,裡面提供了兩個方法,分別為postProcessBeforeInitialization(初始化之前)和postProcessAfterInitialization(初始化之後),可以在Bean初始化前後,進行一些操作(比如為Bean設定屬性值)。

關於後置處理器的使用可參考:【Spring】BeanPostProcessor後置處理器

  • Advisor:對切面的封裝,使用了@AspectJ註解的類會被Spring封裝成Advisor。

AOP的實現主要在代理建立器的postProcessAfterInitialization方法中:

  • postProcessAfterInitialization:在bean初始化之後執行的方法,這時候bean已經例項化完畢,這裡會呼叫wrapIfNecessary方法判斷是否有必要為該Bean生成AOP代理物件,如果不需要建立AOP代理物件直接返回即可,反之會獲取Advisors,然後建立AOP的代理物件,替換掉原來生成的Bean
// AnnotationAwareAspectJAutoProxyCreator的父類AbstractAutoProxyCreator中實現
public abstract class AbstractAutoProxyCreator extends ProxyProcessorSupport
        implements SmartInstantiationAwareBeanPostProcessor, BeanFactoryAware {

    /**
     * 在bean初始化之後執行的方法
     */
    @Override
    public Object postProcessAfterInitialization(@Nullable Object bean, String beanName) {
        if (bean != null) {
            // 構建快取Key
            Object cacheKey = getCacheKey(bean.getClass(), beanName);
            if (this.earlyProxyReferences.remove(cacheKey) != bean) {
                // 是否有必要建立AOP代理物件
                return wrapIfNecessary(bean, beanName, cacheKey);
            }
        }
        return bean;
    }
}

AOP代理條件判斷

Spring需要知道有哪些類需要進行AOP代理、哪些需要跳過AOP代理,比如我們使用@Aspect標註的切面類,只是一個普通Bean,不需要進行AOP代理,而我們的目標類,就需要進行代理,所以這一步會進行判斷。

處於以下情況之一是不需要進行AOP代理的,會跳過:

  1. 如果是Advice、Pointcut、Advisor、AopInfrastructureBean類本身及其子類則跳過建立,這些是Spring的基礎類,不需要進行AOP代理;
  2. 如果有Aspect註解且不是透過Ajc編譯的類,這個就是用@Aspect標註的切面類,也不需要AOP進行代理;
  3. 這個判斷條件的作用同上,只不過個判斷主要用於在XML中透過 aop:aspect標籤形式來配置切面的情況,Spring會生成一個對應的AspectJPointcutAdvisor,切面本身對應的那個Java類是不需要進代理的,所以新增了一個判斷,跳過切面本身對應的那個Java類,在使用註解和使用aop:aspect標籤時實現不一樣,所以這裡又加了一個條件判斷;

獲取Advisor

對於上述情況的bean會跳過,剩下的Bean需要先獲取所有的Advisors,從中找出適用於當前Bean的Advisor,如果查詢到表示當前Bean需要進行AOP代理,依舊返回原來的Bean物件即可。

Spring會把使用了@AspectJ註解定義的切面包裝成Advisor,判斷是否有與當前bean匹配的Advisor,判斷方式如下:

  1. 根據切點Pointcut的getClassFilter方法對類進行匹配,判斷當前Bean的class是否匹配;

  2. 根據切點Pointcut獲取MethodMatcher方法匹配器,透過MethodMatcher對當前Bean中的每一個方法進行匹配,也就是使用配置的切點表示式對方法進行匹配;

經過這一步處理,如果匹配到了該Bean的Advisor,說明當前Bean需要進行AOP代理,會返回適用於當前Bean的Advisor集合,接下來會為該Bean建立AOP代理物件。

建立AOP代理物件

建立代理物件

前置知識:JDK動態代理,可參考 【Java】JDK動態代理實現原理

Spring提供了兩種方式建立代理物件,分別是JDK動態代理和Cglib,使用JDK動態代理需要被代理物件實現介面,否則使用Cglib實現。

以JDK動態代理為例,建立代理物件的過程在JdkDynamicAopProxy中,它實現了InvocationHandler,在透過JDK的動態代理建立物件的時候,需要這個InvocationHandler,透過Proxy的newProxyInstance即可建立AOP代理物件:

final class JdkDynamicAopProxy implements AopProxy, InvocationHandler, Serializable {

    @Override
    public Object getProxy(@Nullable ClassLoader classLoader) {
        if (logger.isTraceEnabled()) {
            logger.trace("Creating JDK dynamic proxy: " + this.advised.getTargetSource());
        }
        Class<?>[] proxiedInterfaces = AopProxyUtils.completeProxiedInterfaces(this.advised, true);
        findDefinedEqualsAndHashCodeMethods(proxiedInterfaces);
        // 生成代理物件,proxiedInterfaces為目標代理類,this為InvocationHandler也就是當前的JdkDynamicAopProxy
        return Proxy.newProxyInstance(classLoader, proxiedInterfaces, this);
    }
}

執行目標方法

在建立了AOP代理物件之後,會使用這個代理物件替換掉原來容器中的Bean,開發過程中拿到的Bean就是這個AOP代理物件了,當執行目標方法時,首先會進入到代理物件的invoke方法(JdkDynamicAopProxy中的invoke方法,InvocationHandler中定義了invoke方法,JdkDynamicAopProxy實現了InvocationHandler介面所以會實現這個方法):

對於AOP,在invoke方法中,會先獲取目標方法的所有攔截器,Spring會將適用於當前方法的Advisor轉為方法攔截器,然後使用責任鏈模式,對攔截器進行一個個的呼叫,當然如果當前方法沒有對應的攔截器需要執行,直接透過反射執行目標方法即可。

因為攔截器可以有多個,所以執行攔截器鏈方法是一個遞迴呼叫的過程(在ReflectiveMethodInvocation中實現),它使用了一個變數currentInterceptorIndex記錄了當前攔截器的下標:

  1. 判斷currentInterceptorIndex是否與攔截器鏈的大小一致,如果一致說明已經走到了最後一個攔截器,攔截器走完就可以執行目標方法了,此時會透過反射執行目方法;
  2. 如果攔截器鏈未走完,會對currentInterceptorIndex加1,獲取下一個攔截器,繼續執行;

舉個例子
比如定義了一個切面,裡面設定了兩個通知,分別是前置通知和環繞通知,要將這個切面作用到某個目標方法,在方法執行前後進行一些操作,Spring會將切面及通知封裝為攔截器,在執行目標方法時,攔截器鏈中就會有兩個攔截器,首先執行第一個攔截器,它是一個前置通知,執行完前置通知的方法後,會向後推進進入下一個攔截器:

執行到第二個攔截器,它是一個環繞通知,首先執行環繞通知中的前置操作(環繞通知中目標方法執行之前),執行完畢之後,該方法不會結束,會繼續進入攔截器鏈的處理邏輯,等待目標方法執行之後再繼續執行後置操作(環繞通知中目標方法執行之後的操作):

此時已經是攔截器鏈中最後一個,所以此時可以執行目標方法,執行完目標方法,攔截器鏈的邏輯已經執行完畢,所以對於第二個攔截器來說,會回到環繞通知中的處理邏輯,開始執行目標方法執行之後的後置操作。

總結
(1)在開啟AOP的時候,它會向容器中註冊一個AOP的代理物件建立器,它是一個後置處理器,在Spring容器中每個Bean例項化之後,初始化前後會進入到後置處理器對應的方法中,AOP建立代理物件並將原來的Bean替換就是在後置處理器的postProcessAfterInitialization方法中進行的。

(2)在建立AOP代理之前會先判斷是否需要為當前Bean建立代理物件,因為並不是所有的Bean都需要進行建立,只有切面中設定要攔截的那些方法所在的Bean才需要建立AOP代理物件,所以一些Spring基礎類、使用@Aspect標註的切面本身等Bean都會跳過。

(3)經過上述步驟後,會或獲取所有的Advisor,Spring會將建立的切面包裝成Advisor,所以可以理解為獲取定義的所有切面,從中找出是否有匹配當前Bean的切面,如果有表示需要為Bean建立AOP代理,之後就會根據Bean的資訊,比如是否實現了介面,來決定使用JDK動態代理還是Cglib建立代理物件,建立代理物件之後會替換掉原來的Bean,將這個代理物件返回。

(4)代理物件建立完畢之後,執行目標方法時,會進入到代理物件的業務邏輯中,在這裡會獲取匹配當前目標方法的所有Advisor(比如前置通知、後置通知等)將其轉換成一個攔截器鏈,然後執行攔截器鏈,執行每個攔截器鏈的時候會執行對應通知中的方法,當攔截器鏈執行完畢之後,會透過反射執行真正的目標方法。

相關文章