Spring動態代理的生成-如何判斷是使用JDK動態代理還是CGlib代理

碼上遇見你發表於2021-10-12
前言

在上一篇文章中講到了Spring是如何獲取對應的Bean的增強,然後本次主要講解一下Spring如何在獲取到增強後建立Spring代理的。

在步入正題之前先給大家看一下Spring建立代理的大致流程圖

image

接下來我們就回到AbstractAutoProxyCreator.class類中的wrapIfNecessary方法。

  • 看原始碼(AbstractAutoProxyCreator.class)
protected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey) {
    // 如果已經處理過
    if (StringUtils.hasLength(beanName) && this.targetSourcedBeans.contains(beanName)) {
      return bean;
    }
    // 無需增強
    if (Boolean.FALSE.equals(this.advisedBeans.get(cacheKey))) {
      return bean;
    }
    // 給定的bean類是否是一個基礎設施類,基礎設施類不應該被代理,或者配置了指定的bean不需要代理
    if (isInfrastructureClass(bean.getClass()) || shouldSkip(bean.getClass(), beanName)) {
      this.advisedBeans.put(cacheKey, Boolean.FALSE);
      return bean;
    }
​
    // 獲取能夠應用到當前 Bean 的所有 Advisor(已根據 @Order 排序)
    // Create proxy if we have advice.
    Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(bean.getClass(), beanName, null);
    // 如果有 Advisor,則進行下面的動態代理建立過程
    if (specificInterceptors != DO_NOT_PROXY) {
      // 如果獲取到了增強則需要針對增強進行代理
      this.advisedBeans.put(cacheKey, Boolean.TRUE);
      // 建立代理 JDK 動態代理或者 CGLIB 動態代理
      Object proxy = createProxy(
          bean.getClass(), beanName, specificInterceptors, new SingletonTargetSource(bean));
      // 將代理物件的 Class 物件(目標類的子類)儲存
      this.proxyTypes.put(cacheKey, proxy.getClass());
      //  返回這個 Bean 物件
      return proxy;
    }
​
    this.advisedBeans.put(cacheKey, Boolean.FALSE);
    return bean;
}

在上一篇Spring原始碼之建立AOP代理之增強器的獲取文章中,主要是圍繞著getAdvicesAndAdvisorsForBean方法展開的,主要是獲取到了所有對應Bean的增強器,並獲取到了此目標Bean所匹配的Advisor

接下來我們著手對接下來的方法createProxy進行分析,

  • 看原始碼(AbstractAutoProxyCreator.class)
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();
    // 複製當前 ProxyConfig 的一些屬性(例如 proxyTargetClass、exposeProxy)
    proxyFactory.copyFrom(this);
​
    // 判斷是否是代理類(也就是是否開啟了CGLIB代理) 預設是false
    if (proxyFactory.isProxyTargetClass()) {
      // Explicit handling of JDK proxy targets (for introduction advice scenarios)
      if (Proxy.isProxyClass(beanClass)) {
        // Must allow for introductions; can't just set interfaces to the proxy's interfaces only.
        for (Class<?> ifc : beanClass.getInterfaces()) {
          proxyFactory.addInterface(ifc);
        }
      }
    }
    else {
      // 如果這個 Bean 配置了進行類代理,則設定為 `proxyTargetClass` 為 `true`
      // No proxyTargetClass flag enforced, let's apply our default checks...
      if (shouldProxyTargetClass(beanClass, beanName)) {
        proxyFactory.setProxyTargetClass(true);
      }
      else {
        // 檢測當前Bean 實現的介面是否包含可代理的介面 ,如果沒有,則將proxyTargetClass 設定為true 表示需要進行CGLIB 提升
        evaluateProxyInterfaces(beanClass, proxyFactory);
      }
    }
​
    // 對入參的 advisors 進一步處理,因為其中還可能存在Advice型別 需要將他們包裝成 DefaultPointcutAdvisor
    // 如果配置了 `interceptorNames` 攔截器,也會新增進來
    Advisor[] advisors = buildAdvisors(beanName, specificInterceptors);
    // 代理工廠新增 Advisor 陣列
    proxyFactory.addAdvisors(advisors);
    // 代理工廠設定 TargetSource 物件
    proxyFactory.setTargetSource(targetSource);
    // 對 ProxyFactory 進行加工處理,抽象方法,目前沒有子類實現
    customizeProxyFactory(proxyFactory);
​
    //用來控制代理工廠被配置之後,是否還允許修改通知。(預設為 false) (即在代理被配置之後,不允許修改代理的配置)。
    proxyFactory.setFrozen(this.freezeProxy);
    // 這個 AdvisedSupport 配置管理器是否已經過濾過目標類(預設為 false)
    if (advisorsPreFiltered()) {
      // 設定 `preFiltered` 為 `true`
      //  這樣 Advisor 們就不會根據 ClassFilter 進行過濾了,而直接通過 MethodMatcher 判斷是否處理被攔截方法
      proxyFactory.setPreFiltered(true);
    }
​
    // 如果 bean 類未在覆蓋類載入器中本地載入,則使用原始 ClassLoader
    // Use original ClassLoader if bean class not locally loaded in overriding class loader
    ClassLoader classLoader = getProxyClassLoader();
    if (classLoader instanceof SmartClassLoader && classLoader != beanClass.getClassLoader()) {
      classLoader = ((SmartClassLoader) classLoader).getOriginalClassLoader();
    }
    // 通過 ProxyFactory 代理工廠建立代理物件
    return proxyFactory.getProxy(classLoader);
}
  • 原始碼分析

    上述程式碼中的ProxyFactory proxyFactory = new ProxyFactory();新建了一個工廠類,並且往後看,明顯的看出對於代理類的建立Spring是委託給了ProxyFactory處理的。

    接下來繼續跟蹤原始碼proxyFactory.getProxy(classLoader);該方法建立了代理物件。


  • 看原始碼(Proxyfactory.java)

public Object getProxy(@Nullable ClassLoader classLoader) {
    // 先建立一個 AOP 代理類(JdkDynamicAopProxy 或者 ObjenesisCglibAopProxy) 其實現是在DefaultAopProxyFactory中
    // 根據 AOP 代理為目標 Bean 建立一個代理物件,並返回
    return createAopProxy().getProxy(classLoader);
}
  • 原始碼分析

    通過上述註釋可以感覺到終於要來到了主題,到底是如何決定使用哪種代理方式的。首先我們看到getProxy方法中的createAopProxy方法,它的預設實現其實是在DefaultAopProxyFactory類中。這中間它經過了ProxyCreatorSupport類的createAopProxy方法。

  • 看原始碼(ProxyCreatorSupport.java)

protected final synchronized AopProxy createAopProxy() {
    if (!this.active) {
      activate();
    }
    // 先獲取 AOP 代理工廠,預設為 DefaultAopProxyFactory,只有這個實現
    // 然後通過它根據建立當前 AdvisedSupport 配置管理器建立一個 AOP 代理(JdkDynamicAopProxy 或者 ObjenesisCglibAopProxy)
    return getAopProxyFactory().createAopProxy(this);
}

緊接著我們直接來到具體實現createAopProxy方法的實現類DefaultAopProxyFactory類中。

  • 看原始碼(DefaultAopProxyFactory.java)
@Override
public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException {
    // 判斷是否滿足下面條件的
    /*
     * config.isOptimize() 需要優化,預設為 `false`詳細來說就是:用來控制通過CGLIB建立的代理是否使用激進的優化策略
     *       除非完全瞭解AOP代理如何處理優化,否則不推薦使用者使用這個設定,目前這個屬性僅用於CGLIB 代理,對於JDK動態代理(預設代理)無效
     * config.isProxyTargetClass() 使用類代理,也就是使用 CGLIB 動態代理 預設為 `false`
     *      設定方式:<aop:aspectj-autoproxy proxy-target-class="true"/>
     * hasNoUserSuppliedProxyInterfaces(config) // 是否存在代理介面
     */
    if (!NativeDetector.inNativeImage() &&
        (config.isOptimize() || config.isProxyTargetClass() || hasNoUserSuppliedProxyInterfaces(config))) {
      Class<?> targetClass = config.getTargetClass();
      if (targetClass == null) {
        throw new AopConfigException("TargetSource cannot determine target class: " +
            "Either an interface or a target is required for proxy creation.");
      }
      // 如果目標類是一個介面或者是 java.lang.reflect.Proxy 的子類 則還是使用 JDK 動態代理,建立一個 JdkDynamicAopProxy 物件,
      // 傳入 AdvisedSupport 配置管理器,並返回
      if (targetClass.isInterface() || Proxy.isProxyClass(targetClass)) {
        return new JdkDynamicAopProxy(config);
      }
      // 使用 CGLIB 動態代理,建立一個  ObjenesisCglibAopProxy 物件,傳入 AdvisedSupport 配置管理器,並返回
      return new ObjenesisCglibAopProxy(config);
    }
    else {
      // 使用 JDK 動態代理,建立一個 JdkDynamicAopProxy 物件,傳入 AdvisedSupport 配置管理器,並返回
      return new JdkDynamicAopProxy(config);
    }
}
  • 原始碼分析

    在這個DefaultAopProxyFactory類中可以明顯的看到,這裡根據OptimizeProxyTargetClasshasNoUserSuppliedProxyInterfaces三個屬性進行的決斷,看究竟使用哪種動態代理。

  • optimize 需要優化,預設為 false詳細來說就是:用來控制通過CGLIB建立的代理是否使用激進的優化策略;除非完全瞭解AOP代理如何處理優化,否則不推薦使用者使用這個設定,目前這個屬性僅用於CGLIB 代理,對於JDK動態代理(預設代理)無效

  • ProxyTargetClass使用類代理,也就是使用 CGLIB 動態代理 預設為 false

    設定方式:<aop:aspectj-autoproxy proxy-target-class="true"/>

  • hasNoUserSuppliedProxyInterfaces(config) // 是否存在代理介面

JDK與Cglib的說明
  • 如果目標物件實現了介面,預設情況下會採用JDK的動態代理實現AOP
  • 如果目標物件實現了介面,可以強制使用CGLIB實現AOP。
  • 如果目標物件沒有實現了介面,必須採用CGLIB庫,Spring會自動在JDK動態代理 和CGLIB之間轉換
如何強制使用CGLIB實現AOP?
  1. 新增 CGLIB 庫,Spring_HOME/cglib/*.jar
  2. Spring 配置檔案中加人<aop:aspectj-autoproxy proxy-target-class="true"/>。
JDK動態代理和CGLIB位元組碼生成的區別?
  • JDK動態代理只能對實現了介面的類生成代理,而不能針對類。
  • GLIB是針對類實現代理,主要是對指定的類生成一個子類,覆蓋其中的方法,因為是繼承,所以該類或方法最好不要宣告成final。

好了到這裡就講完了Spring是如何決定使用哪種動態代理的方式的。

想要獲取更多精彩內容請微信搜尋【碼上遇見你】

相關文章