Spring AOP源碼解析-創建代理對象

一.簡介

在上一篇文章中,分析了Spring是如何爲目標bean篩選合適的通知器。現在通知器選好了,接下來就是通過代理的方式將通知器(Advisor)所持有的所有通知(Advice)織入到bean的某些方法前後,這篇文章就是分析創建代理的過程。

二.背景知識

2.1 proxy-target-class

Spring AOP部分使用JDK動態代理或者CGLIB來爲目標對象創建代理(建議儘量使用JDK動態代理),如果被代理的目標對象實現了至少一個接口,則會使用JDK動態代理。所有該目標類型實現的接口都將被代理。若該目標對象沒有實現任何接口,則會創建一個CGLIB代理。如果你希望強制使用CGLIB代理(如果希望代理目標對象的所有方法,而不只是實現自接口的方法)那也可以。但是需要考慮以下兩個問題:

  • 無法通知(advice)Final方法,因爲它們不能被覆寫。
  • 你需要將CGLIB二進制發行包放在classpath下。

JDK本身就提供了動態代理,強制使用CGLIB代理需要將<aop:config>的proxy-target-class屬性設爲true:

<aop:config proxy-target-class="true">
    <aop:aspect id="xxx" ref="xxxx">
        <!-- 省略 -->
    </aop:aspect>
</aop:config>

當使用CGLIB代理和@Aspect自動代理支持,可以按照以下方式設置:

<aop:aspectj-autoproxy proxy-target-class="true"/>

如上,默認情況下 proxy-target-class 屬性爲 false。當目標 bean 實現了接口時,Spring 會基於 JDK 動態代理爲目標 bean 創建代理對象。若未實現任何接口,Spring 則會通過 CGLIB 創建代理。而當 proxy-target-class 屬性設爲 true 時,則會強制 Spring 通過 CGLIB 的方式創建代理對象,即使目標 bean 實現了接口。

JDK動態代理和CGLIB代理的區別:

  • JDK動態代理:其代理對象必須是某個接口的實現,它是通過在運行期間創建一個接口的實現類來完成對目標對象的代理。
  • CGLIB代理:其實現原理類似於JDK動態代理,只是它在運行期間生成的代理對象是針對目標類擴展的子類。CGLIB是高效的代碼生成包,底層是依靠ASM(開源的Java字節碼編輯類庫)操作字節碼實現的,性能比JDK強。

2.2 AopProxy接口

爲目標 bean 創建代理對象前,需要先創建 AopProxy 對象,然後再調用該對象的 getProxy 方法創建實際的代理類。我們先來看看 AopProxy 這個接口的定義,如下:

public interface AopProxy {

    /** 創建代理對象 */
    Object getProxy();
    
    Object getProxy(ClassLoader classLoader);
}

在 Spring 中,有兩個類實現了 AopProxy,如下:

2.3 創建代理的入口處


public abstract class AbstractAutoProxyCreator extends ProxyProcessorSupport
        implements SmartInstantiationAwareBeanPostProcessor, BeanFactoryAware {
    
    @Override
    /** bean 初始化後置處理方法 */
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        if (bean != null) {
            Object cacheKey = getCacheKey(bean.getClass(), beanName);
            if (!this.earlyProxyReferences.contains(cacheKey)) {
                // 如果需要,爲 bean 生成代理對象
                return wrapIfNecessary(bean, beanName, cacheKey);
            }
        }
        return bean;
    }
    
    protected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey) {
        if (beanName != null && this.targetSourcedBeans.contains(beanName)) {
            return bean;
        }
        if (Boolean.FALSE.equals(this.advisedBeans.get(cacheKey))) {
            return bean;
        }
 
        /*
         * 如果是基礎設施類(Pointcut、Advice、Advisor 等接口的實現類),或是應該跳過的類,
         * 則不應該生成代理,此時直接返回 bean
         */ 
        if (isInfrastructureClass(bean.getClass()) || shouldSkip(bean.getClass(), beanName)) {
            // 將 <cacheKey, FALSE> 鍵值對放入緩存中,供上面的 if 分支使用
            this.advisedBeans.put(cacheKey, Boolean.FALSE);
            return bean;
        }
 
        // 爲目標 bean 查找合適的通知器
        Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(bean.getClass(), beanName, null);
        /*
         * 若 specificInterceptors != null,即 specificInterceptors != DO_NOT_PROXY,
         * 則爲 bean 生成代理對象,否則直接返回 bean
         */ 
        if (specificInterceptors != DO_NOT_PROXY) {
            this.advisedBeans.put(cacheKey, Boolean.TRUE);
            // 創建代理
            Object proxy = createProxy(
                    bean.getClass(), beanName, specificInterceptors, new SingletonTargetSource(bean));
            this.proxyTypes.put(cacheKey, proxy.getClass());
            /*
             * 返回代理對象,此時 IOC 容器輸入 bean,得到 proxy。此時,
             * beanName 對應的 bean 是代理對象,而非原始的 bean
             */ 
            return proxy;
        }
 
        this.advisedBeans.put(cacheKey, Boolean.FALSE);
        // specificInterceptors = null,直接返回 bean
        return bean;
    }
}

三.動態代理

3.1 基於JDK的動態代理

基於 JDK 的動態代理主要是通過 JDK 提供的代理創建類 Proxy 爲目標對象創建代理,下面我們來看一下 Proxy 中創建代理的方法聲明。如下:

public static Object newProxyInstance(ClassLoader loader,
                                          Class<?>[] interfaces,
                                          InvocationHandler h)

參數列表解釋如下:

  • loader:類加載器
  • interfaces:目標類所實現的接口列表
  • h:用於封裝代理邏輯

JDK動態代理對目標類是有一定的要求的,即要求目標類必須實現了接口,JDK動態代理只能爲實現了接口的目標類生成代理對象。至於 InvocationHandler,是一個接口類型,在自定義的InvocationHandler中需要重寫三個函數(這三個函數後面會解析到用途):

  • 構造函數,將代理的對象傳入。
  • invoke方法,此方法中實現了AOP增強的所有邏輯
  • getProxy方法,獲得代理對象

下面來演示下JDK動態代理的使用方式,這個場景就是在之前寫過的計算器方法上面加日誌:

(1)創建接口


 
public interface Calculator {
	
	public int add(int i,int j);
	public int sub(int i,int j);
	public int mul(int i,int j);
	public int div(int i,int j);
 

}

(2)創建接口實現類


import com.test.inter.Calculator;
 
public class MyMathCalculator implements Calculator{
 
	@Override
	public int add(int i, int j) {
		System.out.println("【add】方法開始了");
		int result=i+j;
		return result;
	}
 
	@Override
	public int sub(int i, int j) {
		System.out.println("【sub】方法開始了");
		int result=i-j;
		return result;
	}
 
	@Override
	public int mul(int i, int j) {
		System.out.println("【mul】方法開始了");
		int result=i*j;
		return result;
	}
 
	@Override
	public int div(int i, int j) {
		System.out.println("【div】方法開始了");
		int result=i/j;
		return result;
	}
	
	
 
}

(3)創建一個類來實現目標方法

import java.lang.reflect.Method;
import java.util.Arrays;
 
public class LogUtils {
	//方法開始前
	public static void logStart(Method method,Object...arg2){
		System.out.println("【"+method.getName()+"】方法開始執行,用的參數列表【"+Arrays.asList(arg2)+"】");
	}
	
    //方法正常返回
	public static void logReturn(Method method,Object result){
		System.out.println("【"+method.getName()+"】方法開始執行,計算結果是"+result);
	}
	
     //方法拋出異常
	public static void logException(Method method,Exception e){
		System.out.println("【"+method.getName()+"】方法執行出現異常了,異常信息是"+e.getCause()+"這個異常已經通知了測試小組");
	}
	
    //方法結束
	public static void logEnd(Method method){
		System.out.println("【"+method.getName()+"】方法最終結束了");
 
	}
	
}

(4)編寫一個類,爲傳入的參數對象創建一個動態代理對象(這一步也是寫成創建自定義InvocationHandler,用於對接口提供的方法進行增強。然後在測試的時候先實例化目標對象和InvocationHandler,根據目標對象生成代理對象,最後調用代理對象的方法。):

package com.test.proxy;
 
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Arrays;
 
import com.test.inter.Calculator;
import com.test.utils.LogUtils;
 
/*
 * 生成代理的類
 * JDK 默認的動態代理,如果目標對象沒有實現任何接口,是無法爲他創建代理對象的。
 */
public class caculatorProxy {
	
	/*
	 * 爲傳入的參數對象創建一個動態代理對象
	 * Calculator calculator:被代理的對象
	 */
	public static Calculator getProxy(final Calculator calculator)
	{
		//方法執行器,幫目標對象執行目標方法
		InvocationHandler h=new InvocationHandler() {
			
			/*
			 * (non-Javadoc)
			 * @see java.lang.reflect.InvocationHandler#invoke(java.lang.Object, java.lang.reflect.Method, java.lang.Object[])
			 * Object proxy:代理對象,給JDK使用,任何時候都不要動這個對象
			 * Method method:當前將要執行的目標對象的方法
			 * Object[] arg2:這個方法調用時外界傳入的參數值
			 */
			@Override
			public Object invoke(Object proxy, Method method, Object[] arg2)
					throws Throwable {
				//利用反射執行目標方法
				//目標方法執行後的返回值
				System.out.println("這是動態代理將要幫你執行方法...");
				
				Object result=null;
				try {
					LogUtils.logStart(method, arg2);
					result = method.invoke(calculator, arg2);
				    LogUtils.logReturn(method, result);
				} catch (Exception e) {
					LogUtils.logException(method, e);
				}
				finally{
					LogUtils.logEnd(method);
				}
				//返回值必須返回出去外界才能拿到真正執行後的返回值
				return result;
			}
		};
		Class<?>[] interfaces=calculator.getClass().getInterfaces();
		ClassLoader loader=calculator.getClass().getClassLoader();
		//Proxy爲目標對象創建代理對象
		Object proxy=Proxy.newProxyInstance(loader, interfaces, h);
		return (Calculator) proxy; 
		
	}
 
}

(5)測試

package com.test.test;
 
import static org.junit.Assert.*;
 
import org.junit.Test;
 
import com.test.impl.MyMathCalculator;
import com.test.inter.Calculator;
import com.test.proxy.caculatorProxy;
 
public class AOPTest {
 
	@Test
	public void test() {
		Calculator calculatro=new MyMathCalculator();
		//代理對象和被代理對象唯一能產生的關聯就是實現了同一個接口
		Calculator prox=caculatorProxy.getProxy(calculatro);
		prox.add(2, 1);
	}
 
}

(6)測試結果:

3.2 基於CGLIB的動態代理

CGLIB是一個java字節碼的生成工具,它動態生成一個被代理類的子類,子類重寫被代理的類的所有不是final的方法。在子類中採用方法攔截的技術攔截所有父類方法的調用,順勢織入橫切邏輯。

下來看一下CGLIB動態代理的使用方式:

(1)目標類:

public class Dog{
    
    final public void run(String name) {
        System.out.println("狗"+name+"----run");
    }
    
    public void eat() {
        System.out.println("狗----eat");
    }
}

(2)方法攔截器:

import java.lang.reflect.Method;

import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

public class MyMethodInterceptor implements MethodInterceptor{

    @Override
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
        System.out.println("這裏是對目標類進行增強!!!");
        //注意這裏的方法調用,不是用反射哦!!!
        Object object = proxy.invokeSuper(obj, args);
        return object;
    }  
}

(3)測試類:

import net.sf.cglib.core.DebuggingClassWriter;
import net.sf.cglib.proxy.Enhancer;

public class CgLibProxy {
    public static void main(String[] args) {
        //在指定目錄下生成動態代理類,我們可以反編譯看一下里面到底是一些什麼東西
        System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "D:\\java\\java_workapace");
        
        //創建Enhancer對象,類似於JDK動態代理的Proxy類,下一步就是設置幾個參數
        Enhancer enhancer = new Enhancer();
        //設置目標類的字節碼文件
        enhancer.setSuperclass(Dog.class);
        //設置回調函數
        enhancer.setCallback(new MyMethodInterceptor());
        
        //這裏的creat方法就是正式創建代理類
        Dog proxyDog = (Dog)enhancer.create();
        //調用代理類的eat方法
        proxyDog.eat();       
    }
}

四.源碼分析

4.1 createProxy方法(入口處調用的方法)

Spring在爲目標方法創建代理的過程中,要會根據bean是否實現接口,以及一些其他配置來決定使用AopProxy何種實現類爲目標bean創建代理對象,下來看一下代理創建的過程,如下:

	protected Object createProxy(
			Class<?> beanClass, String beanName, Object[] specificInterceptors, TargetSource targetSource) {
		//在bean對應的BeanDefinition中添加原Class對象屬性(保存bean原本的Class)
		if (this.beanFactory instanceof ConfigurableListableBeanFactory) {
			AutoProxyUtils.exposeTargetClass((ConfigurableListableBeanFactory) this.beanFactory, beanName, beanClass);
		}
 
		ProxyFactory proxyFactory = new ProxyFactory();
		//獲取當前類中配置的屬性
		proxyFactory.copyFrom(this);
		
		//檢查proxyTargetClass屬性
		if (!proxyFactory.isProxyTargetClass()) {
			//檢查beanDefinitioin中是否包含preserveTargetClass屬性,且屬性爲true
			//設置是否使用CGLib進行代理
			if (shouldProxyTargetClass(beanClass, beanName)) {
				proxyFactory.setProxyTargetClass(true);
			}
			else {
				//篩選代理接口並添加到proxyFactory
				evaluateProxyInterfaces(beanClass, proxyFactory);
			}
		}
		
		//獲取增強器(包括前面篩選出來的增強器,以及通過setInterceptorNames中添加的通用增強器,默認爲空)
		Advisor[] advisors = buildAdvisors(beanName, specificInterceptors);
		for (Advisor advisor : advisors) {
			//將所有增強器添加到proxyFactory
			proxyFactory.addAdvisor(advisor);
		}
		
		//設置需要代理的bean對象信息
		proxyFactory.setTargetSource(targetSource);
		//模版方法,由子類定製化代理
		customizeProxyFactory(proxyFactory);
		//用來控制代理工程被配置後,是否還允許修改代理的配置,默認爲false
		proxyFactory.setFrozen(this.freezeProxy);
		if (advisorsPreFiltered()) {
			proxyFactory.setPreFiltered(true);
		}
 
		//創建代理對象
		return proxyFactory.getProxy(getProxyClassLoader());

}

這個方法的工作流程爲:

  1. 在BeanDefinition中保存bean原Class對象,因爲創建代理後,bean的class會被修改(Spring4中新加入的帶你,Spring3中不包含)
  2. 創建ProxyFactory
  3. 設置屬性
  4. 過濾目標bean的接口,並添加到ProxyFactory
  5. 獲取增強器實例,添加到ProxyFactory中
  6. 創建代理

4.1.1 過濾接口-evaluateProxyInterfaces方法

過濾接口中主要功能是,幫助判斷是否使用JDK的動態代理來創建代理。因爲JDK動態代理的條件是bean實現了接口,所以Spring會將目標bean實現的接口過濾後添加到ProxyFactory中,方便判斷是否使用JDK動態代理,下面是evaluateProxyInterfaces實現:
 

	protected void evaluateProxyInterfaces(Class<?> beanClass, ProxyFactory proxyFactory) {
		//獲取所有實現的接口
		Class<?>[] targetInterfaces = ClassUtils.getAllInterfacesForClass(beanClass, getProxyClassLoader());
		boolean hasReasonableProxyInterface = false;
		for (Class<?> ifc : targetInterfaces) {
			//不是Spring內部回調用的接口(會排除掉InitializingBean、DisposableBean、Aware接口) && 不是語言內部接口 && 接口定義了一個以上的方法
			if (!isConfigurationCallbackInterface(ifc) && !isInternalLanguageInterface(ifc) &&
					ifc.getMethods().length > 0) {
				hasReasonableProxyInterface = true;
				break;
			}
		}
		//如果滿足上面三個條件,纔會將接口添加到proxyFactory
		if (hasReasonableProxyInterface) {
			// Must allow for introductions; can't just set interfaces to the target's interfaces only.
			for (Class<?> ifc : targetInterfaces) {
				proxyFactory.addInterface(ifc);
			}
		}
		//條件不滿足,缺少合適的接口,無法使用JDK動態代理,使用CGLib
		else {
			proxyFactory.setProxyTargetClass(true);
		}
	}

4.2 創建代理

public Object getProxy(ClassLoader classLoader) {
    // 先創建 AopProxy 實現類對象,然後再調用 getProxy 爲目標 bean 創建代理對象
    return createAopProxy().getProxy(classLoader);
}

getProxy 這裏有兩個方法調用,一個是調用 createAopProxy 創建 AopProxy 實現類對象,然後再調用 AopProxy 實現類對象中的 getProxy 創建代理對象。這裏我們先來看一下創建 AopProxy 實現類對象的過程,如下:

	protected final synchronized AopProxy createAopProxy() {
		//active會在在第一次創建代理後,設爲true
		if (!this.active) {
			//設置active爲true,並通知監聽器(如果沒有配置,爲空)
			activate();
		}
		//getAopProxyFactory會返回aopProxyFactory變量,默認實現爲DefaultAopProxyFactory
		return getAopProxyFactory().createAopProxy(this);
	}protected final synchronized AopProxy createAopProxy() {
    if (!this.active) {
        activate();
    }
    return getAopProxyFactory().createAopProxy(this);
}

public class DefaultAopProxyFactory implements AopProxyFactory, Serializable {

    @Override
    public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException {
        /*
         * 下面的三個條件簡單分析一下:
         *
         *   條件1:config.isOptimize() - 是否需要優化,
         *   條件2:config.isProxyTargetClass() - 檢測 proxyTargetClass 的值,
         *         前面的代碼會設置這個值
         *   條件3:hasNoUserSuppliedProxyInterfaces(config) 
         *         - 目標 bean 是否實現了接口
         */
        if (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.");
            }
            if (targetClass.isInterface() || Proxy.isProxyClass(targetClass)) {
                return new JdkDynamicAopProxy(config);
            }
            // 創建 CGLIB 代理,ObjenesisCglibAopProxy 繼承自 CglibAopProxy
            return new ObjenesisCglibAopProxy(config);
        }
        else {
            // 創建 JDK 動態代理
            return new JdkDynamicAopProxy(config);
        }
    }
}

Spring生成代理對象的方式有兩種,JDK動態代理和CGLib,分別生成JdkDynamicAopProxy和ObjenesisCglibAopProxy,從上面代碼可以看出Spring的判斷條件:

  1. optimize:用來控制通過CGLib創建的代理是否使用激進的優化策略(該僅對CGLib有效)
  2. proxyTargetClass:當屬性爲true,使用CGLib,目標類本身被代理而不是目標類的接口,設置方式:<aop:aspectj-autoproxy proxy-target-class="true">。
  3. hasNoUserSuppliedProxyInterfaces:是否存在代理接口(也就是前面過濾接口一節中,添加進去的接口)

下面是對JDK與CGLIB方式的總結:

  • 如果是目標對象實現了接口,默認情況下會採用JDK動態代理實現AOP
  • 如果目標對象實現了接口,可以強制使用CGLIB實現AOP
  • 如果目標對象沒有實現了接口,必須採用CGLIB庫,Spring會自動在JDK動態代理和CGLIB之間轉換。

4.3 獲取代理

如上,DefaultAopProxyFactory 根據一些條件決定生成什麼類型的 AopProxy 實現類對象。生成好 AopProxy 實現類對象後,下面就要爲目標 bean 創建代理對象了。

4.3.1 JdkDynamicAopProxy.getProxy

這裏以 JdkDynamicAopProxy 爲例,我們來看一下,該類的 getProxy 方法的邏輯是怎樣的。如下:

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

public Object getProxy(ClassLoader classLoader) {
    if (logger.isDebugEnabled()) {
        logger.debug("Creating JDK dynamic proxy: target source is " + this.advised.getTargetSource());
    }
    //獲取代理接口
		Class<?>[] proxiedInterfaces = AopProxyUtils.completeProxiedInterfaces(this.advised, true);
		//檢查是否在接口中定義了equals或hashCode方法
		findDefinedEqualsAndHashCodeMethods(proxiedInterfaces);
		//創建代理對象
		return Proxy.newProxyInstance(classLoader, proxiedInterfaces, this);
}

會發現 JdkDynamicAopProxy 最終調用 Proxy.newProxyInstance 方法創建代理對象。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章