Spring 源碼學習筆記10——Spring AOP

Spring 源碼學習筆記10——Spring AOP

參考書籍《Spring技術內幕》Spring AOP的實現章節
書有點老,但是裡面一些概念還是總結比較到位
源碼基於Spring-aop 5.3.22 可能和舊版本有所差異但是大體邏輯一致

一丶AOP概述

AOP為Aspect Oriented Programming的縮寫,意為:面向切面編程,通過預編譯方式和運行期間動態代理實現程式功能的統一維護的一種技術。 是一種新的模組化機制,用來描述分散在對象,類,或函數中的橫切關注點,分離關注點使解決特定領域問題的程式碼從業務邏輯中獨立出來,業務邏輯程式碼中不在含義針對特定領域的程式碼調用,業務邏輯同特定領域問題的關係通過切面封裝,維護,這樣原本分散在整個應用程式中的變動可以很好地管理起來。

用人話說就是,通過切面完成特定邏輯(事務,入參出參日誌等)
可以和業務邏輯(crud)抽離開,便於維護

1. Advice 通知

定義在連接點做什麼,為切面增強提供植入介面。描述Spring AOP圍繞方法調而注入的切面行為

2.Pointcut切入點

切點決定Advice通知應該作用在哪個連接點,也就是通過Poincut來定義需要增強的方法集合,這些集合可以按照一定規則來完成,這種情況下,Pointcut意味著標識方法(比如事務切面定義了事務註解方法上生效)切入點是一些列織入邏輯程式碼的連接點集合

3.Advisor通知器

整合Advice 和 Pointcut,定義應該使用哪個通知器並在哪個關注點使用它。

二丶aop 重要介面和編程體驗

我們先拋棄Spring框架,利用Spring aop中存在的工具實現aop增強。

1.基於Advice

image-20220824154409542

Advice介面的實現有AfterAdvice後置通知,Beforeadvice前置通知,MethodInterceptor方法攔截器可以看做是環繞通知。

/**
 * 服務類
 */
public static class Service{
    public void doSomething(){
        System.out.println("service doSomething");
    }
}

/***
 * 自定義的advice 環繞通知
 */
public static class MyAdvice implements MethodInterceptor {

    @Override
    public Object invoke(MethodInvocation invocation) throws Throwable {
        System.out.println("my advice before");
        //service 方法執行
        Object res = invocation.proceed();
        System.out.println("my advice after");
        return res;
    }
}

public static void main(String[] args) {
    //代理工程 用於生成代理對象
    ProxyFactory proxyFactory = new ProxyFactory();
    //目標對象 
    Service service = new Service();
    
    //設置需要代理的對象
    proxyFactory.setTarget(service);
    proxyFactory.addAdvice(new MyAdvice());
    
    //生成代理對象
    Service proxyService = (Service) proxyFactory.getProxy();
    //代理對象執行
    proxyService.doSomething();
}

上述程式碼執行結果

image-20220824155430057

基於Advice,ProxyFactory成功實現了Aop代理增強

2.基於Advisor

image-20220824162331408

public static void advisorBased(){
    //代理工程 用於生成代理對象
    ProxyFactory proxyFactory = new ProxyFactory();
    //目標對象
    Service service = new Service();
    
    //advisor
    DefaultPointcutAdvisor advisor = new DefaultPointcutAdvisor();
    //根據名稱匹配方法的pointcut
    NameMatchMethodPointcut pointcut = new NameMatchMethodPointcut();
    //指定只有doSomething 才增強
    pointcut.setMappedName("doSomething");
    advisor.setPointcut(pointcut);
    advisor.setAdvice(new MyAdvice());
    
    
    //設置需要代理的對象
    proxyFactory.setTarget(service);
    proxyFactory.addAdvisor(advisor);
    //生成代理對象
    Service proxyService = (Service) proxyFactory.getProxy();
    //代理對象執行
    proxyService.doSomething();
    System.out.println();
    proxyService.sayHello();
}

image-20220824161712037

最後我們發現只有名稱匹配的方法才生效。

Advisor介面具備方法Advice getAdvice()來獲取通知。PointcutAdvisor實現了Advisor並且新增方法Pointcut getPointcut()來獲取切入點的定義。Pointcut介面定義了兩個方法ClassFilter getClassFilter(),MethodMatcher getMethodMatcher()分別是對類和方法的篩選,來決定Advise是不是應該作用於當前類。

三丶ProxyFactory 和 ProxyFactoryBean

1.ProxyFactory

1.1類圖

image-20220824163045792

TargetSource 用於獲取 AOP 調用的當前「目標」

image-20220824163652106

getTargetClass可以獲取被代理對象的類型,getTarget可以獲取被代理對象,HotSwappableTargetSource中的swap方法可以替換掉代理對象,Spring aop常用的是SingletonTargetSource它持有了原始的被代理對象。

1.2 proxyFactory是如何創建代理對象的。

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

image-20220824164249060

1.2.1 DefaultAopProxyFactory # createAopProxy

image-20220824164734093

這裡生成的AopProxy 才是負責生成代理對象的,其中spring內置了兩種策略——JDK動態代理和CGLIB動態代理。

只有設置了需要代理目標類,或者說沒有指定代理的介面,且代理目標類不是介面,不是lambda,不是已經被JDK動態代理後的類,那麼才會使用CGLIB進行動態代理。

1.2.2 AopPorxy

image-20220824165158029

其中JdkDynamicAopProxy,還實現了InvocationHandler。

  • Jdk動態代理

  • 生成代理對象

    Proxy.newProxyInstance(classLoader, this.proxiedInterfaces, this),這裡的this便是自己。

  • invoke

首先是對`equals`,`hashCode`的處理,目標對象聲明了讓目標對象執行,反之調用`JdkDynamicAopProxy`對應的方法

其次是如果配置中設置了暴露代理對象,那麼將其放入到`AopContext`中的`ThreadLocal`中

```java
if (this.advised.exposeProxy) {
   // Make invocation available if necessary.
   oldProxy = AopContext.setCurrentProxy(proxy);
   setProxyContext = true;
}
```

然後獲取當前對象的攔截器鏈,如果攔截器鏈為空 那麼直接反射調用目標對象的方法。如果存在攔截器鏈那麼new 一個`ReflectiveMethodInvocation`利用反射依次執行。Spring只支援方法攔截器`MethodInterceptor`進行代理增強,對於Advise都會適配成`MethodInterceptor`,Spring採用適配器模式,具體的適配器如下

![image-20220824172451573](//img2022.cnblogs.com/blog/2605549/202208/2605549-20220824172515386-796996823.png)

```java
public interface AdvisorAdapter {
   //支援什麼Advise被適配
   boolean supportsAdvice(Advice advice);

   //適配
   MethodInterceptor getInterceptor(Advisor advisor);
}
```

自然是Spring遍歷每一個Advise責任鏈模式依次找到`AdvisorAdapter`然後調用適配方法得到一個`MethodInterceptor`,下面是適配成的`MethodInterceptor`。

![image-20220824172904504](//img2022.cnblogs.com/blog/2605549/202208/2605549-20220824172910944-1903834494.png)
  • CGLIB動態代理

    • 設置CallBack

      首先new 一個Enhancer設置父類為被代理對象的類型,這裡會把講Aop的邏輯轉變為一個DynamicAdvisedInterceptor,equals和hashCode方法也有對應的callBack

      image-20220824174533939

      注意這裡的MethodInterceptor是org.springframework.cglib.proxy.MethodInterceptor,其中的intercept 方法的邏輯和JDK動態代理的invoke類似,都是鏈式調用。

2.ProxyFactoryBean

image-20220824175124978

ProxyFactoryBean創建代理對象的邏輯和ProxyFactory類似,但是ProxyFactoryBean是一個FactoryBean,我們可以利用這一點在bean初始化的時候生成一個代理對象

image-20220824175752977

image-20220824180011325

創建原型對象類似,但是不會判斷之前是否創建過,直接無腦創建即可

四丶Spring AOP 和IOC是如何結合起來的

通常我們在使用Spring Aop的時候會在啟動類上加一個@EnableAspectJAutoProxy註解,這個註解@Import(AspectJAutoProxyRegistrar.class)導入了AspectJAutoProxyRegistrar,這個類實現了ImportBeanDefinitionRegistrar,Spring容器會調用其registerBeanDefinitions方法為我們注入BeanDefinition,後續會實例化一個AnnotationAwareAspectJAutoProxyCreator類型的bean,它是Spring IOC和AOP結合的關鍵

1.AnnotationAwareAspectJAutoProxyCreator類結構圖

image-20220824180746690

這其中最為關鍵的必然是AnnotationAwareAspectJAutoProxyCreator是一個BeanPostProcessor,從而在Spring 回調postProcessAfterInitialization對bean進行代理的增強,並且它實現了SmartInstantiationAwareBeanPostProcessor Spring容器創建bean的時候如果出現了循環依賴那麼會調用到getEarlyBeanReference,在這個方法裡面同樣也會進行aop的增強

  • AbstractAutoProxyCreator 實現了SmartInstantiationAwareBeanPostProcessor是一個bean後置處理器,使用 AOP 代理包裝每個符合條件的 bean,在調用 bean 本身之前委託給指定的攔截器,AOP代理髮生的地方。

  • AbstractAdvisorAutoProxyCreator

    為了每一個Bean找到合適的Advisor並且進行,如果Advisor標註了@Order或者說實現了Ordered介面那麼會進行排序。

  • AspectJAwareAdvisorAutoProxyCreator

    AbstractAdvisorAutoProxyCreator子類,對一個切面中的多個Advisor進行優先順序排序

  • AnnotationAwareAspectJAutoProxyCreator

    AspectJAwareAdvisorAutoProxyCreator的子類,會將容器中標註了@AspectJ註解的類解析成Advisor(整合Advice 和 Pointcut,定義應該使用哪個通知器並在哪個關注點使用它)

2.AbstractAutoProxyCreator是如何進行Aop增強的

image-20220824181252842

image-20220824181353002

進行AOP增強的地方其實還有postProcessBeforeInstantiation如果我們自己配置了TargetSourceCreator並且可以為當前bean生成,那麼才可能發生aop,這裡一般不會進行任何操作。

2.1 wrapIfNecessary

image-20220824182002813

其中shouldSkip被AspectJAwareAdvisorAutoProxyCreator重寫,如果Advisor是AspectJPointcutAdvisor並且切面名稱和bean名稱相同那麼會跳過,這應該是我們標註@Aspect的時候需要保證這個類會被Spring加入到容器,所有需要加@Componet所以會過濾掉

2.1.1 如何找到所有合適的advice 和advisor

image-20220821162528516

  • findCandidateAdvisors方法會找到容器中所以的Advisor類型的bean,AnnotationAwareAspectJAutoProxyCreator進行了重寫,它還會把所以標註了@Aspect註解的bean中的增強邏輯封裝成Advisor

  • findAdvisorsThatCanApply這個方法內部邏輯基本上就是調用PointcutAdvisor獲取類過濾器,方法匹配器進行匹配。

  • sortAdvisors 這裡默認是通過@Order註解,或者Ordered介面進行排序,但是AspectJAwareAdvisorAutoProxyCreator進行了重寫,因為它需要對同一個標註@Aspect切面裡面的前置後置等進行排序

2.1.2 創建代理對象

protected Object createProxy(Class<?> beanClass, @Nullable String beanName,
      @Nullable Object[] specificInterceptors, TargetSource targetSource) {

   if (this.beanFactory instanceof ConfigurableListableBeanFactory) {
      AutoProxyUtils.exposeTargetClass((ConfigurableListableBeanFactory) this.beanFactory, beanName, beanClass);
   }

   ProxyFactory proxyFactory = new ProxyFactory();
   proxyFactory.copyFrom(this);
   //這裡的ProxyTargetClass 來自上面的copyFrom 取決於EnableAspectJAutoProxy註解的proxyTargetClass
    //proxyTargetClass 表示是否使用基於CGLIB子類的代理
   if (!proxyFactory.isProxyTargetClass()) {
       //shouldProxyTargetClass 方法就是去BeanFactory中看當前bean的BeanDefinition中是否存在AutoProxy.PRESERVE_TARGET_CLASS_ATTRIBUTE=trued的attribute,當我們手動注入bean的時候可以使用這個強制讓當前bean使用CGLIB增強
      if (shouldProxyTargetClass(beanClass, beanName)) {
         proxyFactory.setProxyTargetClass(true);
      }
      else {
         //獲取當前類中非Spring回調(InitializingBean,DisposableBean,Aware)類型的介面,且如果介面的方法大於0,那麼會把介面類型加入到proxyFactory中,否則設置ProxyTargetClass(沒有介面那麼沒辦法使用JDK動態代理)
         evaluateProxyInterfaces(beanClass, proxyFactory);
      }
   }
   
   //主要是把上面找到的advise 適配成Advisor。調用的是advisorAdapterRegistry的wrap方法
   Advisor[] advisors = buildAdvisors(beanName, specificInterceptors);    
   proxyFactory.addAdvisors(advisors);
   //這裡的targetSource是SingletonTargetSource
   proxyFactory.setTargetSource(targetSource);
   //留給子類擴展的方法
   customizeProxyFactory(proxyFactory);
    
   proxyFactory.setFrozen(this.freezeProxy);
   if (advisorsPreFiltered()) {
      proxyFactory.setPreFiltered(true);
   }
	//生成代理對象
   return proxyFactory.getProxy(getProxyClassLoader());
}

這裡其實和我們上面的Aop編程體驗中基於Advisor類似,最後都是AopProxy創建代理對象

2.2 AnnotationAwareAspectJAutoProxyCreator

上面我們講了其父類AbstractAutoProxyCreator的大體邏輯,AnnotationAwareAspectJAutoProxyCreator會將@Aspect註解類解析成Advisor,下面我們重點看下AnnotationAwareAspectJAutoProxyCreator是怎麼將@Aspect註解類解析成Advisor的

image-20220824190521871

這裡依賴了BeanFactoryAspectJAdvisorsBuilder,它會遍歷所有bean,並調用isAspect方法

image-20220824190659633

然後調用ReflectiveAspectJAdvisorFactory的getAdvisors方法將其適配成多個Advisor,會遍歷每一個沒有標註@Pointcut的方法,然後獲取@Around, @Before, @After, @AfterReturning, @AfterThrowing(如果沒有那麼直接返回)然後獲取value中的內容包裝成AspectJExpressionPointcut(AspectJ表達式pointcut),然後包裝成InstantiationModelAwarePointcutAdvisorImpl在這個類中會把對應註解的方法封裝成對應的AbstractAspectJAdvice的子類

image-20220824191958027

調用對應方法依舊採用反射,其子類在合適的實際進行調用。

Tags: