主頁 > 後端開發 > 死磕Spring之AOP篇 - Spring AOP自動代理(三)創建代理物件

死磕Spring之AOP篇 - Spring AOP自動代理(三)創建代理物件

2021-04-22 06:07:01 後端開發

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

Spring 版本:5.1.14.RELEASE

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

了解 AOP 相關術語,可先查看 《Spring AOP 常見面試題) 》 這篇文章

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

在前面的《Spring AOP 自動代理(一)入口》文章中,分析了 Spring AOP 自動代理的入口是 AbstractAutoProxyCreator 物件,其中自動代理的程序主要分為下面兩步:

  1. 篩選出能夠應用于當前 Bean 的 Advisor
  2. 找到了合適 Advisor 則創建一個代理物件, JDK 動態代理或者 CGLIB 動態代理

上一篇《Spring AOP 自動代理(二)篩選合適的通知器》文章分析了上面第 1 步的處理程序,先去決議出當前 IoC 容器所有 Advisor 物件,包括 Advisor 型別的 Bean 和從 @AspectJ 注解的 Bean 決議出來的 Advisor 物件;然后通過通過 ClassFilter 類過濾器和 MethodMatcher 方法匹配器篩選出能夠應用于這個 Bean 的 Advisor 們,最后進行排序;不同的 AspectJ 根據 @Order 排序,同一個 AspectJ 中不同 Advisor 的排序,優先級:AspectJAfterThrowingAdvice > AspectJAfterReturningAdvice > AspectJAfterAdvice > AspectJAroundAdvice > AspectJMethodBeforeAdvice

本文將會分析上面的第 2 的創建程序,如果這個 Bean 有合適的 Advisor,那么為這個 Bean 創建一個代理物件,我們一起來看看是如何創建代理物件的,

回顧

// AbstractAutoProxyCreator.java
protected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey) {
    /*
     * <1> 如果當前 Bean 已經創建過自定義 TargetSource 物件
     * 表示在上面的**實體化前置處理**中已經創建代理物件,那么直接回傳這個物件
     */
    if (StringUtils.hasLength(beanName)
            && this.targetSourcedBeans.contains(beanName))
    {
        return bean;
    }
    // <2> `advisedBeans` 保存了這個 Bean 沒有必要創建代理物件,則直接回傳
    if (Boolean.FALSE.equals(this.advisedBeans.get(cacheKey))) {
        return bean;
    }
    /*
     * <3> 不需要創建代理物件,則直接回傳當前 Bean
     */
    if (isInfrastructureClass(bean.getClass()) // 如果是 Spring 內部的 Bean(Advice、Pointcut、Advisor 或者 AopInfrastructureBean 標記介面)
            || shouldSkip(bean.getClass(), beanName)) // 應該跳過
    {
        // 將這個 Bean 不需要創建代理物件的結果保存起來
        this.advisedBeans.put(cacheKey, Boolean.FALSE);
        return bean;
    }

    // Create proxy if we have advice.
    // <4> 獲取能夠應用到當前 Bean 的所有 Advisor(已根據 @Order 排序)
    Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(bean.getClass(), beanName, null);
    // <5> 如果有 Advisor,則進行下面的動態代理創建程序
    if (specificInterceptors != DO_NOT_PROXY) {
        // <5.1> 將這個 Bean 已創建代理物件的結果保存至 `advisedBeans`
        this.advisedBeans.put(cacheKey, Boolean.TRUE);
        // <5.2> 創建代理物件,JDK 動態代理或者 CGLIB 動態代理
        // 這里傳入的是 SingletonTargetSource 物件,可獲取代理物件的目標物件(當前 Bean)
        Object proxy = createProxy(bean.getClass(), beanName, specificInterceptors, new SingletonTargetSource(bean));
        // <5.3> 將代理物件的 Class 物件(目標類的子類)保存
        this.proxyTypes.put(cacheKey, proxy.getClass());
        // <5.4> 回傳代理物件
        return proxy;
    }

    // <6> 否則,將這個 Bean 不需要創建代理物件的結果保存起來
    this.advisedBeans.put(cacheKey, Boolean.FALSE);
    // <7> 回傳這個 Bean 物件
    return bean;
}

在創建代理物件的程序中,上面方法的第 4 步嘗試獲取能夠應用于當前 Bean 的 Advisor,該程序在上一篇文章中進行分析過,如果有 Advisor,則呼叫 createProxy(..) 方法創建代理物件

注意,這里傳入的 TargetSource 是一個 SingletonTargetSource 物件,可獲取目標物件,如下:

public class SingletonTargetSource implements TargetSource, Serializable {
	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;
	}
}

創建代理物件的流程

  1. 創建一個 ProxyFactory 代理工廠物件,設定需要創建的代理類的配置資訊,例如 Advisor 陣列和 TargetSource 目標類來源

  2. 借助 DefaultAopProxyFactory 選擇 JdkDynamicAopProxy(JDK 動態代理)還是 ObjenesisCglibAopProxy(CGLIB 動態代理)

    • proxy-target-classfalse 時,優先使用 JDK 動態代理,如果目標類沒有實作可代理的介面,那么還是使用 CGLIB 動態代理

    • 如果為 true,優先使用 CGLIB 動態代理,如果目標類本身是一個介面,那么還是使用 JDK 動態代理

  3. 通過 JdkDynamicAopProxy 或者 ObjenesisCglibAopProxy 創建一個代理物件

    • JdkDynamicAopProxy 本身是一個 InvocationHandler 實作類,通過 JDK 的 Proxy.newProxyInstance(..) 創建代理物件
    • ObjenesisCglibAopProxy 借助 CGLIB 的 Enhancer 創建代理物件,會設定 Callback 陣列和 CallbackFilter 篩選器(選擇合適 Callback 處理對應的方法),整個程序相比于 JDK 動態代理更復雜點,主要的實作在 DynamicAdvisedInterceptor 方法攔截器中

AbstractAutoProxyCreator

org.springframework.aop.framework.autoproxy.AbstractAutoProxyCreator:AOP 自動代理的抽象類,完成主要的邏輯實作,提供一些骨架方法交由子類完成

1. createProxy 方法

createProxy(..) 方法,為目標物件創建一個代理物件,如下:

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

    if (this.beanFactory instanceof ConfigurableListableBeanFactory) {
        // 為目標 Bean 的 BeanDefinition 物件設定一個屬性
        // org.springframework.aop.framework.autoproxy.AutoProxyUtils.originalTargetClass -> 目標 Bean 的 Class 物件
        AutoProxyUtils.exposeTargetClass((ConfigurableListableBeanFactory) this.beanFactory, beanName, beanClass);
    }

    // <1> 創建一個代理工廠
    ProxyFactory proxyFactory = new ProxyFactory();
    // <2> 復制當前 ProxyConfig 的一些屬性(例如 proxyTargetClass、exposeProxy)
    proxyFactory.copyFrom(this);

    /**
     * <3> 判斷是否類代理,也就是是否開啟 CGLIB 代理
     * 默認配置下為 `false`,參考 {@link org.springframework.context.annotation.EnableAspectJAutoProxy}
     */
    if (!proxyFactory.isProxyTargetClass()) {
        /*
         * <3.1> 如果這個 Bean 配置了進行類代理,則設定為 `proxyTargetClass` 為 `true`
         */
        if (shouldProxyTargetClass(beanClass, beanName)) {
            proxyFactory.setProxyTargetClass(true);
        }
        else {
            /*
             * <3.2> 檢測當前 Bean 實作的介面是否包含可代理的介面
             * 如沒有實作,則將 `proxyTargetClass` 設為 `true`,表示需要進行 CGLIB 提升
             */
            evaluateProxyInterfaces(beanClass, proxyFactory);
        }
    }

    /*
     * <4> 對入參的 Advisor 進一步處理,因為其中可能還存在 Advice 型別,需要將他們包裝成 DefaultPointcutAdvisor 物件
     * 如果配置了 `interceptorNames` 攔截器,也會添加進來
     */
    Advisor[] advisors = buildAdvisors(beanName, specificInterceptors);
    // <5> 代理工廠添加 Advisor 陣列
    proxyFactory.addAdvisors(advisors);
    // <6> 代理工廠設定 TargetSource 物件
    proxyFactory.setTargetSource(targetSource);
    // <7> 對 ProxyFactory 進行加工處理,抽象方法,目前沒有子類實作
    customizeProxyFactory(proxyFactory);

    proxyFactory.setFrozen(this.freezeProxy);
    // <8> 是否這個 AdvisedSupport 配置管理器已經過濾過目標類(默認為 false)
    if (advisorsPreFiltered()) {
        // 設定 `preFiltered` 為 `true`
        // 這樣 Advisor 們就不會根據 ClassFilter 進行過濾了,而直接通過 MethodMatcher 判斷是否處理被攔截方法
        proxyFactory.setPreFiltered(true);
    }

    // <9> 通過 ProxyFactory 代理工廠創建代理物件
    return proxyFactory.getProxy(getProxyClassLoader());
}

該方法的處理程序如下:

  1. 創建一個 ProxyFactory 代理工廠 proxyFactory
  2. 復制當前物件的一些屬性給 proxyFactory(例如 proxyTargetClass、exposeProxy),當前 AbstractAutoProxyCreator 物件繼承了 ProxyConfig
  3. 判斷是否類代理,也就是是否開啟 CGLIB 代理,默認配置下為 false,如果沒有的話,進行下面處理
    1. 如果這個 Bean 配置了進行類代理,則設定為 proxyTargetClasstrue
    2. 否則,檢測當前 Bean 實作的介面是否包含可代理的介面,如沒有實作,則將 proxyTargetClass 設為 true,表示需要進行 CGLIB 提升
  4. 呼叫 buildAdvisors(..) 方法,對入參的 Advisor 陣列進一步處理,會將不是 Advisor 型別的物件包裝成 DefaultPointcutAdvisor 物件
  5. proxyFactory 代理工廠添加 Advisor 陣列
  6. proxyFactory 代理工廠設定 TargetSource 物件,用戶獲取目標物件
  7. proxyFactory 進行加工處理,抽象方法,目前沒有子類實作
  8. 是否這個 AdvisedSupport 配置管理器已經過濾過目標類(默認為 false
    1. 是的話設定 preFilteredtrue,這樣 Advisor 們就不會根據 ClassFilter 進行過濾了,而直接通過 MethodMatcher 判斷是否處理被攔截方法
  9. 呼叫 proxyFactory 代理工廠的 getProxy(@Nullable ClassLoader classLoader) 方法創建代理物件

這個程序不復雜,容易理解,其中第 4 步會再次對 Advisor 陣列進一步處理,在第 9 步根據 proxyFactory 創建代理物件

buildAdvisors 方法

buildAdvisors(@Nullable String beanName, @Nullable Object[] specificInterceptors) 方法,對 Advisor 陣列進一步處理,如下:

protected Advisor[] buildAdvisors(@Nullable String beanName, @Nullable Object[] specificInterceptors) {
    // Handle prototypes correctly...
    // <1> 將配置的 `interceptorNames` 轉換成 Advisor 型別(默認沒有)
    Advisor[] commonInterceptors = resolveInterceptorNames();

    // <2> 將 commonInterceptors 與 specificInterceptors 放入一個集合
    List<Object> allInterceptors = new ArrayList<>();
    if (specificInterceptors != null) {
        allInterceptors.addAll(Arrays.asList(specificInterceptors));
        if (commonInterceptors.length > 0) {
            // 是否添加至最前面(默認為 true)
            if (this.applyCommonInterceptorsFirst) {
                allInterceptors.addAll(0, Arrays.asList(commonInterceptors));
            }
            else {
                allInterceptors.addAll(Arrays.asList(commonInterceptors));
            }
        }
    }
    Advisor[] advisors = new Advisor[allInterceptors.size()];
    /*
     * <3> 遍歷 `specificInterceptors` 陣列
     */
    for (int i = 0; i < allInterceptors.size(); i++) {
        // <3.1> 將不是 Advisor 型別的 Advice 或者 MethodInterceptor 包裝成 DefaultPointcutAdvisor 物件
        advisors[i] = this.advisorAdapterRegistry.wrap(allInterceptors.get(i));
    }
    // <4> 回傳構建好的 Advisor 陣列
    return advisors;
}

該方法的處理程序如下:

  1. 將配置的 interceptorNames 轉換成 Advisor 型別(默認沒有),得到 commonInterceptors 陣列,如下:

    private Advisor[] resolveInterceptorNames() {
        BeanFactory bf = this.beanFactory;
        ConfigurableBeanFactory cbf = (bf instanceof ConfigurableBeanFactory ? (ConfigurableBeanFactory) bf : null);
        List<Advisor> advisors = new ArrayList<>();
        for (String beanName : this.interceptorNames) {
            if (cbf == null || !cbf.isCurrentlyInCreation(beanName)) {
                Assert.state(bf != null, "BeanFactory required for resolving interceptor names");
                Object next = bf.getBean(beanName);
                advisors.add(this.advisorAdapterRegistry.wrap(next));
            }
        }
        return advisors.toArray(new Advisor[0]);
    }
    

    決議 interceptorNames 中對應的 Bean,并包裝成 Advisor 型別(如果需要的話)

  2. 將上一步獲取到的 commonInterceptors 陣列放入入參中的 specificInterceptors 陣列中

  3. 遍歷 specificInterceptors 陣列

    1. 將不是 Advisor 型別的 Advice 或者 MethodInterceptor 包裝成 DefaultPointcutAdvisor 物件

      // DefaultAdvisorAdapterRegistry.java
      @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);
      }
      
  4. 回傳構建好的 Advisor 陣列

能夠應用于這個 Bean 的 Advisor 們已經準備好了,那么接下來我們來看看 ProxyFactory 是如何創建代理物件的

2. ProxyFactory

org.springframework.aop.framework.ProxyFactory,代理工廠,如下:

public Object getProxy(@Nullable ClassLoader classLoader) {
    // <1> 先創建一個 AOP 代理類(JdkDynamicAopProxy 或者 ObjenesisCglibAopProxy)
    // <2> 根據 AOP 代理為目標 Bean 創建一個代理物件,并回傳
    return createAopProxy().getProxy(classLoader);
}

程序分為兩步:

  1. 呼叫 createAopProxy() 方法,創建一個 AOP 代理類(JdkDynamicAopProxy 或者 ObjenesisCglibAopProxy),如下:

    protected final synchronized AopProxy createAopProxy() {
        if (!this.active) {
            activate();
        }
        // <1> 先獲取 AOP 代理工廠,默認為 DefaultAopProxyFactory,只有這個實作
        // <2> 然后通過它根據創建當前 AdvisedSupport 配置管理器創建一個 AOP 代理(JdkDynamicAopProxy 或者 ObjenesisCglibAopProxy)
        return getAopProxyFactory().createAopProxy(this);
    }
    
  2. 然后呼叫 AOP 代理類的 getProxy(@Nullable ClassLoader classLoader) 方法獲取代理物件

可以看到選擇 JDK 動態代理還是選擇 CGLIB 動態代理在 DefaultAopProxyFactory 中可以找到答案

3. DefaultAopProxyFactory

org.springframework.aop.framework.DefaultAopProxyFactory,默認的 AOP 代理工廠,如下:

public class DefaultAopProxyFactory implements AopProxyFactory, Serializable {

	@Override
	public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException {
		/*
		 * <1> 判斷是否滿足下面三個條件的其中一個
		 */
		if (config.isOptimize() // 需要優化,默認為 `false`
				|| config.isProxyTargetClass() // 使用類代理,也就是使用 CGLIB 動態代理
				|| hasNoUserSuppliedProxyInterfaces(config) // 目標類沒有實作介面
		) {
			// <1.1> 獲取目標類
			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.");
			}
			/*
			 * <1.2> 如果目標類是一個介面或者是 java.lang.reflect.Proxy 的子類
			 * 則還是使用 JDK 動態代理,創建一個 JdkDynamicAopProxy 物件,傳入 AdvisedSupport 配置管理器,并回傳
			 */
			if (targetClass.isInterface() || Proxy.isProxyClass(targetClass)) {
				return new JdkDynamicAopProxy(config);
			}
			// <1.3> 使用 CGLIB 動態代理,創建一個  ObjenesisCglibAopProxy 物件,傳入 AdvisedSupport 配置管理器,并回傳
			return new ObjenesisCglibAopProxy(config);
		}
		// <2> 否則
		else {
			// 使用 JDK 動態代理,創建一個 JdkDynamicAopProxy 物件,傳入 AdvisedSupport 配置管理器,并回傳
			return new JdkDynamicAopProxy(config);
		}
	}

	private boolean hasNoUserSuppliedProxyInterfaces(AdvisedSupport config) {
		Class<?>[] ifcs = config.getProxiedInterfaces();
		return (ifcs.length == 0 || (ifcs.length == 1 && SpringProxy.class.isAssignableFrom(ifcs[0])));
	}

}

創建 AOP 代理類的程序如下:

  1. 判斷是否滿足下面三個條件的其中一個,則進行下面的處理

    需要優化,默認為 false

    使用類代理,也就是使用 CGLIB 動態代理,在前面的 AbstractAutoProxyCreator#createProxy(..) 方法中有提到過;

    目標類沒有實作介面;

    1. 獲取目標類
    2. 如果目標類是一個介面或者是 java.lang.reflect.Proxy 的子類,則還是使用JDK 動態代理,創建一個 JdkDynamicAopProxy 物件
    3. 否則,使用 CGLIB 動態代理,創建一個 ObjenesisCglibAopProxy 物件
  2. 否則,使用JDK 動態代理,創建一個 JdkDynamicAopProxy 物件

我們可以看到JDK 動態代理對應 JdkDynamicAopProxy 物件,CGLIB 動態代理對應 ObjenesisCglibAopProxy(繼承 CglibAopProxy)物件,在創建這兩個物件的時候都傳入了一個引數,就是 AdvisedSupport 配置管理器物件,

回到前面的 ProxyFactory#createAopProxy() 方法中,這個 AdvisedSupport 物件就是這個 ProxyFactory 物件,它繼承了 AdvisedSupport

再回到 AbstractAutoProxyCreator#createProxy(..) 方法中,這個 ProxyFactory 物件是在這創建的,包含了 TargetSource 目標類來源和能夠應用于當前目標物件的所有 Advisor

所以,我們得到的JdkDynamicAopProxy或者ObjenesisCglibAopProxy都包含了創建 AOP 代理物件的所有配置資訊,可以創建代理物件了,

通過上面的這個方法,我們還可以得出一個結論,關于 proxy-target-class 配置的含義是什么?

首先這個配置表示是否進行類代理,也就是 CGLIB 動態代理,默認是 false

當為 false 時,優先使用 JDK 動態代理,如果目標類沒有實作可代理的介面,那么還是使用 CGLIB 動態代理

如果為 true,優先使用 CGLIB 動態代理,如果目標類本身是一個介面,那么還是使用 JDK 動態代理

到這里,我們知道了使用哪種方式創建代理物件,那么接下里我們一起來看看兩種方式創建代理物件的程序,

4. JdkDynamicAopProxy

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

建構式

final class JdkDynamicAopProxy implements AopProxy, InvocationHandler, Serializable {
	/** use serialVersionUID from Spring 1.2 for interoperability. */
	private static final long serialVersionUID = 5531744639992436476L;

	/** We use a static Log to avoid serialization issues. */
	private static final Log logger = LogFactory.getLog(JdkDynamicAopProxy.class);

	/** 代理物件的配置資訊,例如保存了 TargetSource 目標類來源、能夠應用于目標類的所有 Advisor */
	private final AdvisedSupport advised;

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

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

	public JdkDynamicAopProxy(AdvisedSupport config) throws AopConfigException {
		Assert.notNull(config, "AdvisedSupport must not be null");
		if (config.getAdvisors().length == 0 // 沒有 Advisor,表示沒有任何動作
				&& config.getTargetSource() == AdvisedSupport.EMPTY_TARGET_SOURCE) // 沒有來源
		{
			throw new AopConfigException("No advisors and no TargetSource specified");
		}
		this.advised = config;
	}
}

4.1 getProxy 方法

getProxy() 方法,獲取一個 JDK 動態代理物件,如下:

@Override
public Object getProxy() { return getProxy(ClassUtils.getDefaultClassLoader()); }

@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);
}

獲取代理物件的程序如下:

  1. 獲取需要代理的介面(目標類實作的介面,會加上 Spring 內部的幾個介面,例如 SpringProxy)

  2. 判斷目標類是否重寫了 equals 或者 hashCode 方法,沒有重寫在攔截到這兩個方法的時候,會呼叫當前類的實作

    private void findDefinedEqualsAndHashCodeMethods(Class<?>[] proxiedInterfaces) {
        for (Class<?> proxiedInterface : proxiedInterfaces) {
            Method[] methods = proxiedInterface.getDeclaredMethods();
            for (Method method : methods) {
                if (AopUtils.isEqualsMethod(method)) {
                    this.equalsDefined = true;
                }
                if (AopUtils.isHashCodeMethod(method)) {
                    this.hashCodeDefined = true;
                }
                if (this.equalsDefined && this.hashCodeDefined) {
                    return;
                }
            }
        }
    }
    
  3. 呼叫 JDK 的 Proxy#newProxyInstance(..) 方法創建代理物件,入參的 InvocationHandler 實作了就是當前物件

對于上面 JDK 創建代理物件的程序是不是很熟悉,不清楚的小伙伴可以查看我前面 《初識 JDK、CGLIB 兩種動態代理》 這篇文章

AopProxyUtils

org.springframework.aop.framework.AopProxyUtils 工具類,獲取 JDK 創建代理物件時需要實作哪些介面,如下:

static Class<?>[] completeProxiedInterfaces(AdvisedSupport advised, boolean decoratingProxy) {
    // <1> 獲取需要代理的介面(目標類實作的介面),放入 `specifiedInterfaces` 中
    Class<?>[] specifiedInterfaces = advised.getProxiedInterfaces();
    if (specifiedInterfaces.length == 0) {
        // No user-specified interfaces: check whether target class is an interface.
        Class<?> targetClass = advised.getTargetClass();
        if (targetClass != null) {
            if (targetClass.isInterface()) {
                advised.setInterfaces(targetClass);
            }
            else if (Proxy.isProxyClass(targetClass)) {
                advised.setInterfaces(targetClass.getInterfaces());
            }
            specifiedInterfaces = advised.getProxiedInterfaces();
        }
    }
    // <2> 判斷目標類實作的介面是否存在 SpringProxy|Advised|DecoratingProxy 介面,不存在需要添加一個
    boolean addSpringProxy = !advised.isInterfaceProxied(SpringProxy.class);
    boolean addAdvised = !advised.isOpaque() && !advised.isInterfaceProxied(Advised.class);
    boolean addDecoratingProxy = (decoratingProxy && !advised.isInterfaceProxied(DecoratingProxy.class));
    int nonUserIfcCount = 0;
    if (addSpringProxy) {
        nonUserIfcCount++;
    }
    if (addAdvised) {
        nonUserIfcCount++;
    }
    if (addDecoratingProxy) {
        nonUserIfcCount++;
    }
    Class<?>[] proxiedInterfaces = new Class<?>[specifiedInterfaces.length + nonUserIfcCount];
    System.arraycopy(specifiedInterfaces, 0, proxiedInterfaces, 0, specifiedInterfaces.length);
    int index = specifiedInterfaces.length;
    // <3> 如果目標類實作的介面沒有 SpringProxy,則添加一個
    // 這樣創建的代理物件就會實作這個介面,就能知道這個代理物件是否由 Spring 創建
    if (addSpringProxy) {
        proxiedInterfaces[index] = SpringProxy.class;
        index++;
    }
    // 添加一個 Advised 介面
    if (addAdvised) {
        proxiedInterfaces[index] = Advised.class;
        index++;
    }
    // 添加一個 DecoratingProxy 介面
    if (addDecoratingProxy) {
        proxiedInterfaces[index] = DecoratingProxy.class;
    }
    // <4> 回傳需要代理的介面
    return proxiedInterfaces;
}

該方法的處理程序如下:

  1. 獲取需要代理的介面(目標類實作的介面),放入 specifiedInterfaces
  2. 判斷目標類實作的介面是否存在 SpringProxy|Advised|DecoratingProxy 介面,不存在需要添加一個
  3. 通常情況下,上面三個介面都會添加到 specifiedInterfaces 的后面
  4. 回傳 specifiedInterfaces 陣列

通過上面這個方法得到的結論:在 Spring 內部通過 JDK 創建的代理物件會實作額外的介面,包括 SpringProxy 標記介面,可區分代理物件是否為 Spring 創建的

4 CglibAopProxy

org.springframework.aop.framework.CglibAopProxy,CGLIB 動態代理類,可創建代理物件

CGLIB 動態代理比 JDK 動態代理可要復雜得多~

建構式

class CglibAopProxy implements AopProxy, Serializable {

	// Constants for CGLIB callback array indices
	// 因為 CGLIB 設定的 Callback 是一個陣列,下面定義了陣列中固定幾個攔截器的位置
	// 進行 AOP 代理的通用攔截器
	private static final int AOP_PROXY = 0;
	// 執行目標方法的攔截器
	private static final int INVOKE_TARGET = 1;
	// 空的 Callback 物件,對于 finalize() 方法,不需要進行任何處理
	private static final int NO_OVERRIDE = 2;
	// 目標物件調度器,用于獲取目標物件
	private static final int DISPATCH_TARGET = 3;
	// 配置管理器的調度器,會回傳一個 AdvisedSupport 物件
	private static final int DISPATCH_ADVISED = 4;
	// 處理 `equals(Object)` 方法的攔截器
	private static final int INVOKE_EQUALS = 5;
	// 處理 `hashCode()` 方法的攔截器
	private static final int INVOKE_HASHCODE = 6;

	/** Logger available to subclasses; static to optimize serialization. */
	protected static final Log logger = LogFactory.getLog(CglibAopProxy.class);

	/** Keeps track of the Classes that we have validated for final methods. */
	private static final Map<Class<?>, Boolean> validatedClasses = new WeakHashMap<>();

	/** 代理物件的配置資訊,例如保存了 TargetSource 目標類來源、能夠應用于目標類的所有 Advisor */
	protected final AdvisedSupport advised;

	/** 創建代理物件的構造方法入參 */
	@Nullable
	protected Object[] constructorArgs;

	/** 創建代理物件的構造方法的入參型別 */
	@Nullable
	protected Class<?>[] constructorArgTypes;

	/** Dispatcher used for methods on Advised. 配置管理器的調度器,會回傳一個 AdvisedSupport 物件 */
	private final transient AdvisedDispatcher advisedDispatcher;

	/**
	 * 快取方法的呼叫器陣列索引
	 * key:方法名稱
	 * value:方法對應的方法呼叫器在 Callback 陣列中的位置
	 */
	private transient Map<String, Integer> fixedInterceptorMap = Collections.emptyMap();

	// 方法呼叫器在 Callback 陣列中的偏移量
	private transient int fixedInterceptorOffset;

	public CglibAopProxy(AdvisedSupport config) throws AopConfigException {
		Assert.notNull(config, "AdvisedSupport must not be null");
		if (config.getAdvisors().length == 0 // 沒有 Advisor,表示沒有任何動作
				&& config.getTargetSource() == AdvisedSupport.EMPTY_TARGET_SOURCE) // 沒有來源
		{
			throw new AopConfigException("No advisors and no TargetSource specified");
		}
		this.advised = config;
		this.advisedDispatcher = new AdvisedDispatcher(this.advised);
	}
}

上面的每一個屬性都有注釋,都非常重要,在 Spring 的 CGLIB 動態代理創建代理物件時,會設定一個 Callback 陣列,上面定義了陣列中固定幾個攔截器的位置

陣列中前面的幾個 Callback 如下:

  • 0:進行 AOP 代理的通用攔截器【重點關注】
  • 1:執行目標方法的攔截器
  • 2:空的 Callback 物件,例如 finalize() 方法,不需要進行任何處理
  • 3:目標物件調度器,用于獲取目標物件
  • 4:配置管理器的調度器,會回傳一個 AdvisedSupport 物件
  • 5:處理 equals(Object) 方法的攔截器
  • 6:處理 hashCode() 方法的攔截器

4.1 getProxy 方法

getProxy() 方法,獲取一個 CGLIB 動態代理物件,如下:

@Override
public Object getProxy() { return getProxy(null); }

@Override
public Object getProxy(@Nullable ClassLoader classLoader) {
    try {
        // <1> 獲取目標類
        Class<?> rootClass = this.advised.getTargetClass();
        Assert.state(rootClass != null, "Target class must be available for creating a CGLIB proxy");

        // <2> 將目標類作為被代理的類
        Class<?> proxySuperClass = rootClass;
        // <3> 如果目標類已經被 CGLIB 提升(名稱包含 `$$` 符號)
        if (ClassUtils.isCglibProxyClass(rootClass)) {
            // <3.1> 獲取目標類的父類作為被代理的類
            proxySuperClass = rootClass.getSuperclass();
            // <3.2> 獲取目標類實作的介面,并添加至當前 AdvisedSupport 配置管理器中
            // 例如 `@Configuration` 注解的 Bean 會被 CGLIB 提升,實作了 EnhancedConfiguration 介面
            Class<?>[] additionalInterfaces = rootClass.getInterfaces();
            for (Class<?> additionalInterface : additionalInterfaces) {
                this.advised.addInterface(additionalInterface);
            }
        }

        // Validate the class, writing log messages as necessary.
        // <4> 進行校驗,僅列印日志,例如 final 修飾的方法不能被 CGLIB 提升
        validateClassIfNecessary(proxySuperClass, classLoader);

        // Configure CGLIB Enhancer...
        // <5> 創建 CGLIB 的增強類,并進行接下來的配置
        Enhancer enhancer = createEnhancer();
        if (classLoader != null) {
            enhancer.setClassLoader(classLoader);
            // `useCache` 默認為 `true`,可通過 `cglib.useCache` 系統引數指定
            if (classLoader instanceof SmartClassLoader &&
                    ((SmartClassLoader) classLoader).isClassReloadable(proxySuperClass)) {
                enhancer.setUseCache(false);
            }
        }
        // <5.1> 設定被代理的類
        enhancer.setSuperclass(proxySuperClass);
        // <5.2> 設定需要代理的介面(可能沒有,不過都會加上 Spring 內部的幾個介面,例如 SpringProxy)
        enhancer.setInterfaces(AopProxyUtils.completeProxiedInterfaces(this.advised));
        // <5.3> 設定命名策略,默認生成的代理物件的名稱中包含 '$$' 和 'BySpringCGLIB'
        enhancer.setNamingPolicy(SpringNamingPolicy.INSTANCE);
        enhancer.setStrategy(new ClassLoaderAwareUndeclaredThrowableStrategy(classLoader));

        // <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();
        }
        // fixedInterceptorMap only populated at this point, after getCallbacks call above
        // <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);
    }
    catch (CodeGenerationException | IllegalArgumentException ex) {
        // 拋出 AopConfigException 例外
    }
    catch (Throwable ex) {
        // 拋出 AopConfigException 例外
    }
}

該方法的處理程序如下:

  1. 獲取目標類 rootClass
  2. 將目標類作為被代理的類 proxySuperClass
  3. 如果目標類已經被 CGLIB 提升(名稱包含 $$ 符號)
    1. 獲取目標類的父類作為被代理的類
    2. 獲取目標類實作的介面,并添加至當前 AdvisedSupport 配置管理器中,例如 @Configuration 注解的 Bean 會被 CGLIB 提升,實作了 EnhancedConfiguration 介面
  4. 進行校驗,僅列印日志,例如 final 修飾的方法不能被 CGLIB 提升
  5. 創建 CGLIB 的增強類 Enhancer 物件,并進行接下來的配置
    1. 設定被代理的類為 proxySuperClass
    2. 設定需要代理的介面(例如 SpringProxy),在前面講述 JDK 動態代理創建代理物件時已經講過 AopProxyUtils 這個工具類,不同的是 CGLIB 不會添加 DecoratingProxy 這個介面
    3. 設定命名策略,默認生成的代理物件的名稱中包含 $$BySpringCGLIB
    4. 呼叫 getCallbacks(Class<?>) 方法,獲取回呼陣列 Callback[] callbacks,也就是 MethodInterceptor 方法攔截器
    5. 設定 Callback 過濾器為 ProxyCallbackFilter,用于篩選出方法使用 callbacks 中的哪個 Callback
  6. 呼叫 createProxyClassAndInstance(Enhancer enhancer, Callback[] callbacks) 方法,創建代理物件,并回傳

CGLIB 創建代理物件的程序整體上并不復雜,其中第 5.45.56 步我們接下來逐步分析

4.2 getCallbacks 方法

getCallbacks(Class<?> rootClass) 方法,獲取代理物件的 Callback 回呼陣列,如下:

private Callback[] getCallbacks(Class<?> rootClass) throws Exception {
    // Parameters used for optimization choices...
    // <1> 獲取代理物件的三個配置資訊
    // `exposeProxy` 是否暴露代理物件,通常為 `false`
    boolean exposeProxy = this.advised.isExposeProxy();
    // `isFrozen` 配置管理器是否被凍結,通常為 `false`
    boolean isFrozen = this.advised.isFrozen();
    // `isStatic` 是否是靜態的目標物件,也就是說目標物件是否每次都需要創建,通常為 `true`
    boolean isStatic = this.advised.getTargetSource().isStatic();

    // Choose an "aop" interceptor (used for AOP calls).
    // <2> 【重點】創建目標物件的攔截器,MethodInterceptor 方法攔截器,會對目標物件的方法進行攔截處理,之前篩選出來的 Advisor 會在這個里面被呼叫
    Callback aopInterceptor = new DynamicAdvisedInterceptor(this.advised);

    // Choose a "straight to target" interceptor. (used for calls that are
    // unadvised but can return this). May be required to expose the proxy.
    // <3> 創建目標物件的執行器,用于執行目標方法
    Callback targetInterceptor;
    // <3.1> 如果需要暴露當前代理物件,則通過 AopContext 進行暴露,放入 ThreadLocal 中,其他的和下面都相同
    if (exposeProxy) {
        targetInterceptor = (isStatic ?
                new StaticUnadvisedExposedInterceptor(this.advised.getTargetSource().getTarget()) :
                new DynamicUnadvisedExposedInterceptor(this.advised.getTargetSource()));
    }
    // <3.2> 否則,不暴露
    else {
        targetInterceptor = (isStatic ?
                // <3.2.1> 用于執行方法代理物件,并對最終的回傳結果進一步處理(回傳結果是否需要為代理物件,回傳結果是否不能為空)
                new StaticUnadvisedInterceptor(this.advised.getTargetSource().getTarget()) :
                // <3.2.2> 和上者的區別就是,每次攔截都會重新獲取目標物件,結束后釋放該目標物件
                new DynamicUnadvisedInterceptor(this.advised.getTargetSource()));
    }

    // Choose a "direct to target" dispatcher (used for
    // unadvised calls to static targets that cannot return this).
    // <4> 目標物件調度器,用于獲取目標物件
    Callback targetDispatcher = (isStatic ?
            // <4.1> 目標物件的調度器,用于獲取當前目標物件
            new StaticDispatcher(this.advised.getTargetSource().getTarget()) :
            // <4.2> 目標物件的調度器,空的 Callback,不做任何處理
            new SerializableNoOp());

    // <5> 生成主要的幾個回呼介面,也就是上面創建的幾個 Callback,放入 `mainCallbacks` 陣列
    Callback[] mainCallbacks = new Callback[] {
            // 0:進行 AOP 代理的通用攔截器
            aopInterceptor,  // for normal advice
            // 1:執行目標方法的攔截器
            targetInterceptor,  // invoke target without considering advice, if optimized
            // 2:空的 Callback 物件,例如 `finalize()` 方法,不需要進行任何處理
            new SerializableNoOp(),  // no override for methods mapped to this
            // 3:目標物件調度器,用于獲取目標物件
            targetDispatcher,
            // 4:配置管理器的調度器,會回傳一個 AdvisedSupport 物件
            // 因為代理物件會實作 Advised 介面,攔截到其里面的方法時,需要呼叫當前 AdvisedSupport 的方法
            this.advisedDispatcher,
            // 5:處理 `equals(Object)` 方法的攔截器
            new EqualsInterceptor(this.advised),
            // 6:處理 `hashCode()` 方法的攔截器
            new HashCodeInterceptor(this.advised)
    };

    Callback[] callbacks;

    // If the target is a static one and the advice chain is frozen,
    // then we can make some optimizations by sending the AOP calls
    // direct to the target using the fixed chain for that method.
    /*
     * <6> 如果目標物件不需要每次都創建,且當前 AdvisedSupport 配置管理器被凍結了,性能優化,可暫時忽略
     * 那么在創建代理物件的時候,就可以先將目標物件的每個方法對應的方法呼叫器決議出來,該程序有點性能損耗,這樣在代理物件執行方法的時候性能有所提升
     * 不過由于這里會決議出許多方法呼叫器,會占有一定的記憶體,以空間換時間
     */
    if (isStatic && isFrozen) {
        Method[] methods = rootClass.getMethods();
        Callback[] fixedCallbacks = new Callback[methods.length];
        this.fixedInterceptorMap = new HashMap<>(methods.length);

        // TODO: small memory optimization here (can skip creation for methods with no advice)
        /*
         * <6.1> 遍歷目標物件所有的方法
         */
        for (int x = 0; x < methods.length; x++) {
            Method method = methods[x];
            // <6.1.1> 獲取能夠應用于該方法的所有攔截器(有序)
            // 不同的 AspectJ 根據 @Order 排序
            // 同一個 AspectJ 中的 Advice 排序:AspectJAfterThrowingAdvice > AfterReturningAdviceInterceptor > AspectJAfterAdvice > AspectJAroundAdvice > MethodBeforeAdviceInterceptor
            List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, rootClass);
            // <6.1.2> 創建一個方法呼叫器
            fixedCallbacks[x] = new FixedChainStaticTargetInterceptor(
                    chain, this.advised.getTargetSource().getTarget(), this.advised.getTargetClass());
            // <6.1.3> 將每個方法的對應的呼叫器的位置索引快取起來
            this.fixedInterceptorMap.put(methods.toString(), x);
        }

        // Now copy both the callbacks from mainCallbacks
        // and fixedCallbacks into the callbacks array.
        callbacks = new Callback[mainCallbacks.length + fixedCallbacks.length];
        // <6.2> 將 `mainCallbacks` 復制到 `callbacks` 陣列
        System.arraycopy(mainCallbacks, 0, callbacks, 0, mainCallbacks.length);
        // <6.3> 將 `fixedCallbacks` 復制到 `callbacks` 陣列(拼接在后面)
        System.arraycopy(fixedCallbacks, 0, callbacks, mainCallbacks.length, fixedCallbacks.length);
        // <6.4> 記錄一個已生成的方法呼叫器的偏移量
        this.fixedInterceptorOffset = mainCallbacks.length;
    }
    // <7> 否則,不進行決議,取上面的幾個主要的 Callback
    else {
        callbacks = mainCallbacks;
    }
    // <8> 回傳這個目標物件對應的 Callback 陣列
    return callbacks;
}

該方法的處理程序如下:

  1. 是獲取代理物件的三個配置資訊

    • exposeProxy 是否暴露代理物件,通常為 false
    • isFrozen 配置管理器是否被凍結,通常為 false
    • isStatic 是否是靜態的目標物件,也就是說目標物件是否每次都需要創建,通常為 true
  2. 【重點】創建目標物件的攔截器,DynamicAdvisedInterceptor 方法攔截器,會對目標物件的方法進行攔截處理,代理物件的處理邏輯都在這里面完成

  3. 創建目標物件的執行器,用于執行目標方法

    1. 如果需要暴露當前代理物件,則通過 AopContext 進行暴露,放入 ThreadLocal 中,其他的和下面都相同
    2. 否則,不暴露
      1. isStatic 為 true,創建 StaticUnadvisedInterceptor 物件,用于執行方法代理物件,并對最終的回傳結果進一步處理(回傳結果是否需要為代理物件,回傳結果是否不能為空)
      2. 否則,創建 DynamicUnadvisedInterceptor 物件,和上者的區別就是,每次攔截都會重新獲取目標物件,結束后釋放該目標物件
  4. 目標物件調度器,用于獲取目標物件

    1. isStatic 為 true,創建 StaticDispatcher 物件,目標物件的調度器,用于獲取當前目標物件
    2. 否則,創建 SerializableNoOp 物件,空的 Callback,不做任何處理
  5. 生成主要的幾個回呼介面,也就是上面創建的幾個 Callback,放入 mainCallbacks 陣列,可以回到上面的建構式看看哦??

    0:進行 AOP 代理的通用攔截器

    1:執行目標方法的攔截器

    2:空的 Callback 物件,例如 finalize() 方法,不需要進行任何處理

    3:目標物件調度器,用于獲取目標物件

    4:配置管理器的調度器,會回傳一個 AdvisedSupport 物件,因為代理物件會實作 Advised 介面,攔截到其里面的方法時,需要呼叫當前 AdvisedSupport 的方法

    5:處理 equals(Object) 方法的攔截器

    6:處理 hashCode() 方法的攔截器

  6. 如果目標物件不需要每次都創建,且當前 AdvisedSupport 配置管理器被凍結了(默認不會),這一步做了一個性能優化,具體查看上面的代碼

    在創建代理物件的時候,就可以先將目標物件的每個方法對應的方法呼叫器決議出來,該程序有點性能損耗,這樣在代理物件執行方法的時候性能有所提升;不過由于這里會決議出許多方法呼叫器,會占有一定的記憶體,以空間換時間

  7. 否則,不進行決議,取上面 mainCallbacks

  8. 回傳這個目標物件對應的 Callback 陣列

獲取代理物件的 Callback 程序稍微有點復雜,因為代理物件處理不同的方法使用到的 Callback 也是不同的,不過你要知道的是上面第 2 步,第一個 Callback 就是 DynamicAdvisedInterceptor 物件,該物件用于進行 AOP 代理的通用攔截器,目標類的方法在這個攔截器中進行處理,這個物件和 JDK 動態代理創建的代理物件的實作差不多,所以都放入下一篇文章進行分析??

4.3 ProxyCallbackFilter

CglibAopProxy 的私有內部靜態類,用于篩選方法出對應的 Callback 物件

private static class ProxyCallbackFilter implements CallbackFilter {

    private final AdvisedSupport advised;

    private final Map<String, Integer> fixedInterceptorMap;

    private final int fixedInterceptorOffset;

    public ProxyCallbackFilter(
            AdvisedSupport advised, Map<String, Integer> fixedInterceptorMap, int fixedInterceptorOffset) {
        this.advised = advised;
        this.fixedInterceptorMap = fixedInterceptorMap;
        this.fixedInterceptorOffset = fixedInterceptorOffset;
    }

    /**
     * 根據 Method 回傳我們需要的 Callback 在陣列中的位置
     */
    @Override
    public int accept(Method method) {
        // <1> 如果該方法是 `finalize()`
        if (AopUtils.isFinalizeMethod(method)) {
            // 回傳一個空 Callback 物件,不對該方法做任何處理
            return NO_OVERRIDE;
        }
        // <2> 如果該方法是 Advised 中的方法,也就是需要 AdvisedSupport 來執行
        if (!this.advised.isOpaque() && method.getDeclaringClass().isInterface() &&
                method.getDeclaringClass().isAssignableFrom(Advised.class)) {
            // 回傳一個配置管理器的調度器,會回傳一個 AdvisedSupport 物件去執行這個方法
            return DISPATCH_ADVISED;
        }
        // We must always proxy equals, to direct calls to this.
        // <3> 如果該方法是 `equals(Object)` 方法
        if (AopUtils.isEqualsMethod(method)) {
            // 回傳處理 `equals(Object)` 方法的攔截器去執行該方法
            return INVOKE_EQUALS;
        }
        // We must always calculate hashCode based on the proxy.
        // <4> 如果該方法是 `hashCode()` 方法
        if (AopUtils.isHashCodeMethod(method)) {
            // 回傳處理 `hashCode()` 方法的攔截器去執行該方法
            return INVOKE_HASHCODE;
        }
        // <5> 獲取目標類 Class 物件
        Class<?> targetClass = this.advised.getTargetClass();
        // Proxy is not yet available, but that shouldn't matter.
        // <6> 獲取能夠應用于該方法的所有攔截器,僅判斷是否存在 Advice
        List<?> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);
        boolean haveAdvice = !chain.isEmpty();
        boolean exposeProxy = this.advised.isExposeProxy();
        boolean isStatic = this.advised.getTargetSource().isStatic();
        boolean isFrozen = this.advised.isFrozen();
        // <7> 如果有 Advice 或者配置沒有被凍結,通常情況都會進入這里
        if (haveAdvice || !isFrozen) {
            // If exposing the proxy, then AOP_PROXY must be used.
            // <7.1> 如果需要暴露這個代理物件,默認為 `false`
            if (exposeProxy) {
                // 回傳處理 AOP 代理的通用攔截器去執行該方法
                return AOP_PROXY;
            }
            String key = method.toString();
            // Check to see if we have fixed interceptor to serve this method.
            // Else use the AOP_PROXY.
            // <7.2> 如果目標物件是單例的,且配置被凍結,且存在對應的方法呼叫器
            if (isStatic && isFrozen && this.fixedInterceptorMap.containsKey(key)) {
                // We know that we are optimizing so we can use the FixedStaticChainInterceptors.
                // 回傳已經存在的方法呼叫器
                int index = this.fixedInterceptorMap.get(key);
                return (index + this.fixedInterceptorOffset);
            }
            // <7.3> 否則(通常會走到這一步)
            else {
                // 回傳處理 AOP 代理的通用攔截器去執行該方法
                return AOP_PROXY;
            }
        }
        // <8> 否則
        else {
            // <8.1> 如果需要暴露代理物件,或者不是單例模式,默認這兩種情況都不滿足
            if (exposeProxy || !isStatic) {
                // 則回傳執行目標方法的攔截器
                return INVOKE_TARGET;
            }
            Class<?> returnType = method.getReturnType();
            // <8.2> 如果該方法需要回傳的就是目標類,暫時不清楚這種情況
            if (targetClass != null && returnType.isAssignableFrom(targetClass)) {
                // 那么回傳執行目標方法的攔截器,回傳當前物件
                return INVOKE_TARGET;
            }
            // <8.3> 否則,回傳目標物件調度器,用于獲取目標物件,讓目標物件自己去執行這個方法
            else {
                return DISPATCH_TARGET;
            }
        }
    }
}

我們直接看到實作的 accept(Method method) 方法的處理邏輯:

  1. 如果該方法是 finalize(),回傳一個空 Callback 物件,不對該方法做任何處理
  2. 如果該方法是 Advised 中的方法,也就是需要 AdvisedSupport 來執行,回傳一個配置管理器的調度器,會回傳一個 AdvisedSupport 物件去執行這個方法
  3. 如果該方法是 equals(Object) 方法,回傳處理 equals(Object) 方法的攔截器去執行該方法
  4. 如果該方法是 hashCode() 方法,回傳處理 hashCode() 方法的攔截器去執行該方法
  5. 獲取目標類 Class 物件
  6. 獲取能夠應用于該方法的所有攔截器,僅判斷是否存在 Advice
  7. 如果有 Advice 或者配置沒有被凍結,通常情況都會進入這里
    1. 如果需要暴露這個代理物件,默認為 false,回傳處理 AOP 代理的通用攔截器去執行該方法
    2. 如果目標物件是單例的,且配置被凍結,且存在對應的方法呼叫器,回傳已經存在的方法呼叫器
    3. 否則(通常會走到這一步),回傳進行 AOP 代理的通用攔截器去執行該方法
  8. 否則
    1. 如果需要暴露代理物件,或者不是單例模式,默認這兩種情況都不滿足,回傳執行目標方法的攔截器
    2. 如果該方法需要回傳的就是目標類,暫時不清楚這種情況,那么回傳執行目標方法的攔截器,回傳當前物件
    3. 否則,回傳目標物件調度器,用于獲取目標物件,讓目標物件自己去執行這個方法

上面的處理程序基本上都根據 Method 進行判斷,然后回傳一個 int 數值,這個值代表去取 Callback 陣列中的那個 Callback,回看上面講到的幾個方法你就全部理解了,

我們來看到上面的第 7.3 步,回傳的是一個 0,表示使用第一個 Callback 進行處理,對應的就是 DynamicAdvisedInterceptor 物件,這個物件和 JDK 動態代理創建的代理物件的實作差不多,所以都放入下一篇文章進行分析??

4.4 createProxyClassAndInstance 方法

createProxyClassAndInstance(Enhancer enhancer, Callback[] callbacks) 方法,創建代理物件,如下:

protected Object createProxyClassAndInstance(Enhancer enhancer, Callback[] callbacks) {
    // 設定使用構造器期間不進行攔截
    enhancer.setInterceptDuringConstruction(false);
    // 設定 Callback 陣列
    enhancer.setCallbacks(callbacks);
    // 創建一個代理物件(目標類的子類)
    return (this.constructorArgs != null && this.constructorArgTypes != null ?
            // 使用指定的構造方法
            enhancer.create(this.constructorArgTypes, this.constructorArgs) :
            // 使用默認的構造方法
            enhancer.create());
}

這個方法在 ObjenesisCglibAopProxy 子類中被重寫了,Spring 內部也是使用 ObjenesisCglibAopProxy 這個類,因為 CGLIB 創建的子類考慮到通過構造器實體化代理物件,可能會存在構造器不合適的情況,所以 Spring 采用 Objenesis 實體化物件,不通過構造器創建實體物件

4.4 ObjenesisCglibAopProxy

org.springframework.aop.framework.ObjenesisCglibAopProxy,繼承 CglibAopProxy 類,重寫 createProxyClassAndInstance(..) 方法

class ObjenesisCglibAopProxy extends CglibAopProxy {

	/**
	 * 創建一個 Objenesis 物件
	 * [**Objenesis**](http://objenesis.org/) 是一個小型 Java 庫,目的是為一些特殊的 Class 物件實體化一個物件
	 * 應用場景:
	 * 1. 序列化,遠程呼叫和持久化 - 物件需要實體化并存盤為到一個特殊的狀態,而沒有呼叫代碼
	 * 2. 代理,AOP 庫和 Mock 物件 - 類可以被子類繼承而子類不用擔心父類的構造器
	 * 3. 容器框架 - 物件可以以非標準的方式被動態實體化
	 */
	private static final SpringObjenesis objenesis = new SpringObjenesis();

	public ObjenesisCglibAopProxy(AdvisedSupport config) {
		super(config);
	}

	@Override
	protected Object createProxyClassAndInstance(Enhancer enhancer, Callback[] callbacks) {
		// <1> 先創建代理物件的 Class 物件(目標類的子類)
		Class<?> proxyClass = enhancer.createClass();
		Object proxyInstance = null;

		// <2> 是否使用 Objenesis 來實體化代理物件,默認會
		// 可通過 在 `spring.properties` 檔案中添加 `spring.objenesis.ignore=false` 來禁止
		if (objenesis.isWorthTrying()) {
			try {
				// <2.1> 通過 Objenesis 實體化代理物件(非標準方式,不使用構造方法進行實體化)
				proxyInstance = objenesis.newInstance(proxyClass, enhancer.getUseCache());
			}
			catch (Throwable ex) {
				logger.debug("Unable to instantiate proxy using Objenesis, " +
						"falling back to regular proxy construction", ex);
			}
		}

		// <3> 如果借助 Objenesis 實體化代理物件失敗
		if (proxyInstance == null) {
			// Regular instantiation via default constructor...
			try {
				// <3.1> 選擇構造器,指定了引數則使用對應的構造器,否則使用默認構造器
				Constructor<?> ctor = (this.constructorArgs != null ?
						proxyClass.getDeclaredConstructor(this.constructorArgTypes) :
						proxyClass.getDeclaredConstructor());
				ReflectionUtils.makeAccessible(ctor);
				// <3.2> 通過構造器實體化代理物件(反射)
				proxyInstance = (this.constructorArgs != null ?
						ctor.newInstance(this.constructorArgs) : ctor.newInstance());
			}
			catch (Throwable ex) {
				throw new AopConfigException("Unable to instantiate proxy using Objenesis, " +
						"and regular proxy instantiation via default constructor fails as well", ex);
			}
		}

		// <4> 設定 Callback 陣列
		((Factory) proxyInstance).setCallbacks(callbacks);
		// <5> 回傳代理物件
		return proxyInstance;
	}
}

首先我們來看到 SpringObjenesis 這個物件,它實作了 Objenesis 介面,Objenesis 是一個小型 Java 庫,目的是為一些特殊的 Class 物件實體化一個物件,應用場景:

  • 序列化,遠程呼叫和持久化 - 物件需要實體化并存盤為到一個特殊的狀態,而沒有呼叫代碼
  • 代理,AOP 庫和 Mock 物件 - 類可以被子類繼承而子類不用擔心父類的構造器
  • 容器框架 - 物件可以以非標準的方式被動態實體化

創建代理物件的程序如下:

  1. 先通過 Enhancer 創建代理物件的 Class 物件(目標類的子類)
  2. 是否使用 Objenesis 來實體化代理物件,默認會
    1. 通過 Objenesis 實體化代理物件(非標準方式,不使用構造方法進行實體化)
  3. 如果借助 Objenesis 實體化代理物件失敗
    1. 選擇構造器,指定了引數則使用對應的構造器,否則使用默認構造器
    2. 通過構造器實體化代理物件(反射)
  4. 設定 Callback 陣列
  5. 回傳代理物件

總結

在前面的《Spring AOP 自動代理(一)入口》文章中,分析了 Spring AOP 自動代理的入口是 AbstractAutoProxyCreator 物件,其中自動代理的程序主要分為下面兩步:

  1. 篩選出能夠應用于當前 Bean 的 Advisor
  2. 找到了合適 Advisor 則創建一個代理物件, JDK 動態代理或者 CGLIB 動態代理

上一篇《Spring AOP 自動代理(二)篩選合適的通知器》文章分析了上面第 1 步的處理程序,本文是接著前面兩篇文章分析上面第 2 個程序,Spring 是如何創建代理物件的,大致流程如下:

  1. 創建一個 ProxyFactory 代理工廠物件,設定需要創建的代理類的配置資訊,例如 Advisor 陣列和 TargetSource 目標類來源

  2. 借助 DefaultAopProxyFactory 選擇 JdkDynamicAopProxy(JDK 動態代理)還是 ObjenesisCglibAopProxy(CGLIB 動態代理)

    • proxy-target-classfalse 時,優先使用 JDK 動態代理,如果目標類沒有實作可代理的介面,那么還是使用 CGLIB 動態代理

    • 如果為 true,優先使用 CGLIB 動態代理,如果目標類本身是一個介面,那么還是使用 JDK 動態代理

  3. 通過 JdkDynamicAopProxy 或者 ObjenesisCglibAopProxy 創建一個代理物件

    • JdkDynamicAopProxy 本身是一個 InvocationHandler 實作類,通過 JDK 的 Proxy.newProxyInstance(..) 創建代理物件
    • ObjenesisCglibAopProxy 借助 CGLIB 的 Enhancer 創建代理物件,會設定 Callback 陣列和 CallbackFilter 篩選器(選擇合適 Callback 處理對應的方法),整個程序相比于 JDK 動態代理更復雜點,主要的實作在 DynamicAdvisedInterceptor 方法攔截器中

其中 CGLIB 實體化代理物件的程序使用到了 Objenesis,它是一個小型 Java 庫,目的是為一些特殊的 Class 物件實體化一個物件,因為 CGLIB 創建的子類考慮到通過構造器實體化代理物件,可能會存在構造器不合適的情況,所以 Spring 采用 Objenesis 實體化物件,不通過構造器創建實體物件,

好了,本篇文章就到這里了,Spring AOP 中整個自動代理程序到這里也就結束了,對于 JDK 動態代理和 CGLIB 動態代理創建的代理物件,它們的具體實作(或者說是方法攔截處理)在這里并沒有體現出來,前者的實作在 JdkDynamicAopProxy 中,因為它實作了 InvocationHandler 介面,后者的實作則在 DynamicAdvisedInterceptor 方法攔截器中,這些內容將在下篇文章進行分析,

轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/278735.html

標籤:Java

上一篇:王炸!!IDEA 2021.1 推出語音、視頻功能,邊寫代碼邊聊天,我真的服了…

下一篇:1.1Python起源

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 【C++】Microsoft C++、C 和匯編程式檔案

    ......

    uj5u.com 2020-09-10 00:57:23 more
  • 例外宣告

    相比于斷言適用于排除邏輯上不可能存在的狀態,例外通常是用于邏輯上可能發生的錯誤。 例外宣告 Item 1:當函式不可能拋出例外或不能接受拋出例外時,使用noexcept 理由 如果不打算拋出例外的話,程式就會認為無法處理這種錯誤,并且應當盡早終止,如此可以有效地阻止例外的傳播與擴散。 示例 //不可 ......

    uj5u.com 2020-09-10 00:57:27 more
  • Codeforces 1400E Clear the Multiset(貪心 + 分治)

    鏈接:https://codeforces.com/problemset/problem/1400/E 來源:Codeforces 思路:給你一個陣列,現在你可以進行兩種操作,操作1:將一段沒有 0 的區間進行減一的操作,操作2:將 i 位置上的元素歸零。最終問:將這個陣列的全部元素歸零后操作的最少 ......

    uj5u.com 2020-09-10 00:57:30 more
  • UVA11610 【Reverse Prime】

    本人看到此題沒有翻譯,就附帶了一個自己的翻譯版本 思考 這一題,它的第一個要求是找出所有 $7$ 位反向質數及其質因數的個數。 我們應該需要質數篩篩選1~$10^{7}$的所有數,這里就不慢慢介紹了。但是,重讀題,我們突然發現反向質數都是 $7$ 位,而將它反過來后的數字卻是 $6$ 位數,這就說明 ......

    uj5u.com 2020-09-10 00:57:36 more
  • 統計區間素數數量

    1 #pragma GCC optimize(2) 2 #include <bits/stdc++.h> 3 using namespace std; 4 bool isprime[1000000010]; 5 vector<int> prime; 6 inline int getlist(int ......

    uj5u.com 2020-09-10 00:57:47 more
  • C/C++編程筆記:C++中的 const 變數詳解,教你正確認識const用法

    1、C中的const 1、區域const變數存放在堆疊區中,會分配記憶體(也就是說可以通過地址間接修改變數的值)。測驗代碼如下: 運行結果: 2、全域const變數存放在只讀資料段(不能通過地址修改,會發生寫入錯誤), 默認為外部聯編,可以給其他源檔案使用(需要用extern關鍵字修飾) 運行結果: ......

    uj5u.com 2020-09-10 00:58:04 more
  • 【C++犯錯記錄】VS2019 MFC添加資源不懂如何修改資源宏ID

    1. 首先在資源視圖中,添加資源 2. 點擊新添加的資源,復制自動生成的ID 3. 在解決方案資源管理器中找到Resource.h檔案,編輯,使用整個專案搜索和替換的方式快速替換 宏宣告 4. Ctrl+Shift+F 全域搜索,點擊查找全部,然后逐個替換 5. 為什么使用搜索替換而不使用屬性視窗直 ......

    uj5u.com 2020-09-10 00:59:11 more
  • 【C++犯錯記錄】VS2019 MFC不懂的批量添加資源

    1. 打開資源頭檔案Resource.h,在其中預先定義好宏 ID(不清楚其實ID值應該設定多少,可以先新建一個相同的資源項,再在這個資源的ID值的基礎上遞增即可) 2. 在資源視圖中選中專案資源,按F7編輯資源檔案,按 ID 型別 相對路徑的形式添加 資源。(別忘了先把檔案拷貝到專案中的res檔案 ......

    uj5u.com 2020-09-10 01:00:19 more
  • C/C++編程筆記:關于C++的參考型別,專供新手入門使用

    今天要講的是C++中我最喜歡的一個用法——參考,也叫別名。 參考就是給一個變數名取一個變數名,方便我們間接地使用這個變數。我們可以給一個變數創建N個參考,這N + 1個變數共享了同一塊記憶體區域。(參考型別的變數會占用記憶體空間,占用的記憶體空間的大小和指標型別的大小是相同的。雖然參考是一個物件的別名,但 ......

    uj5u.com 2020-09-10 01:00:22 more
  • 【C/C++編程筆記】從頭開始學習C ++:初學者完整指南

    眾所周知,C ++的學習曲線陡峭,但是花時間學習這種語言將為您的職業帶來奇跡,并使您與其他開發人員區分開。您會更輕松地學習新語言,形成真正的解決問題的技能,并在編程的基礎上打下堅實的基礎。 C ++將幫助您養成良好的編程習慣(即清晰一致的編碼風格,在撰寫代碼時注釋代碼,并限制類內部的可見性),并且由 ......

    uj5u.com 2020-09-10 01:00:41 more
最新发布
  • Rust中的智能指標:Box<T> Rc<T> Arc<T> Cell<T> RefCell<T> Weak

    Rust中的智能指標是什么 智能指標(smart pointers)是一類資料結構,是擁有資料所有權和額外功能的指標。是指標的進一步發展 指標(pointer)是一個包含記憶體地址的變數的通用概念。這個地址參考,或 ” 指向”(points at)一些其 他資料 。參考以 & 符號為標志并借用了他們所 ......

    uj5u.com 2023-04-20 07:24:10 more
  • Java的值傳遞和參考傳遞

    值傳遞不會改變本身,參考傳遞(如果傳遞的值需要實體化到堆里)如果發生修改了會改變本身。 1.基本資料型別都是值傳遞 package com.example.basic; public class Test { public static void main(String[] args) { int ......

    uj5u.com 2023-04-20 07:24:04 more
  • [2]SpinalHDL教程——Scala簡單入門

    第一個 Scala 程式 shell里面輸入 $ scala scala> 1 + 1 res0: Int = 2 scala> println("Hello World!") Hello World! 檔案形式 object HelloWorld { /* 這是我的第一個 Scala 程式 * 以 ......

    uj5u.com 2023-04-20 07:23:58 more
  • 理解函式指標和回呼函式

    理解 函式指標 指向函式的指標。比如: 理解函式指標的偽代碼 void (*p)(int type, char *data); // 定義一個函式指標p void func(int type, char *data); // 宣告一個函式func p = func; // 將指標p指向函式func ......

    uj5u.com 2023-04-20 07:23:52 more
  • Django筆記二十五之資料庫函式之日期函式

    本文首發于公眾號:Hunter后端 原文鏈接:Django筆記二十五之資料庫函式之日期函式 日期函式主要介紹兩個大類,Extract() 和 Trunc() Extract() 函式作用是提取日期,比如我們可以提取一個日期欄位的年份,月份,日等資料 Trunc() 的作用則是截取,比如 2022-0 ......

    uj5u.com 2023-04-20 07:23:45 more
  • 一天吃透JVM面試八股文

    什么是JVM? JVM,全稱Java Virtual Machine(Java虛擬機),是通過在實際的計算機上仿真模擬各種計算機功能來實作的。由一套位元組碼指令集、一組暫存器、一個堆疊、一個垃圾回收堆和一個存盤方法域等組成。JVM屏蔽了與作業系統平臺相關的資訊,使得Java程式只需要生成在Java虛擬機 ......

    uj5u.com 2023-04-20 07:23:31 more
  • 使用Java接入小程式訂閱訊息!

    更新完微信服務號的模板訊息之后,我又趕緊把微信小程式的訂閱訊息給實作了!之前我一直以為微信小程式也是要企業才能申請,沒想到小程式個人就能申請。 訊息推送平臺🔥推送下發【郵件】【短信】【微信服務號】【微信小程式】【企業微信】【釘釘】等訊息型別。 https://gitee.com/zhongfuch ......

    uj5u.com 2023-04-20 07:22:59 more
  • java -- 緩沖流、轉換流、序列化流

    緩沖流 緩沖流, 也叫高效流, 按照資料型別分類: 位元組緩沖流:BufferedInputStream,BufferedOutputStream 字符緩沖流:BufferedReader,BufferedWriter 緩沖流的基本原理,是在創建流物件時,會創建一個內置的默認大小的緩沖區陣列,通過緩沖 ......

    uj5u.com 2023-04-20 07:22:49 more
  • Java-SpringBoot-Range請求頭設定實作視頻分段傳輸

    老實說,人太懶了,現在基本都不喜歡寫筆記了,但是網上有關Range請求頭的文章都太水了 下面是抄的一段StackOverflow的代碼...自己大修改過的,寫的注釋挺全的,應該直接看得懂,就不解釋了 寫的不好...只是希望能給視頻網站開發的新手一點點幫助吧. 業務場景:視頻分段傳輸、視頻多段傳輸(理 ......

    uj5u.com 2023-04-20 07:22:42 more
  • Windows 10開發教程_編程入門自學教程_菜鳥教程-免費教程分享

    教程簡介 Windows 10開發入門教程 - 從簡單的步驟了解Windows 10開發,從基本到高級概念,包括簡介,UWP,第一個應用程式,商店,XAML控制元件,資料系結,XAML性能,自適應設計,自適應UI,自適應代碼,檔案管理,SQLite資料庫,應用程式到應用程式通信,應用程式本地化,應用程式 ......

    uj5u.com 2023-04-20 07:22:35 more