Spring Aop實作對目标對象的代理,主要有兩種方式:Jdk代理和Cglib代理。這兩種代理的差別在于,Jdk代理與目标類都會實作同一個接口,并且在代理類中會調用目标類中被代理的方法,調用者實際調用的則是代理類的方法,通過這種方式我們就可以在代理類中織入切面邏輯;Jdk代理存在的問題在于目标類被代理的方法必須實作某個接口,Cglib代理則是為了解決這個問題而存在的,其實作代理的方式是通過為目标類動态生成一個子類,通過在子類中織入相應邏輯來達到織入代理邏輯的目的。
關于Jdk代理和Cglib代理,其優缺點主要在于:
- Jdk代理生成的代理類隻有一個,因而其編譯速度是非常快的;而由于被代理的目标類是動态傳入代理類中的,Jdk代理的執行效率相對來說低一點,這也是Jdk代理被稱為動态代理的原因;
- Cglib代理需要為每個目标類生成相應的子類,因而在實際運作過程中,其可能會生成非常多的子類,過多的子類始終不是太好的,因為這影響了虛拟機編譯類的效率;但由于在調用過程中,代理類的方法是已經靜态編譯生成了的,因而Cglib代理的執行效率相對來說高一些。
本文主要講解Spring Aop是如何通過Cglib代理實作将切面邏輯織入目标類的。
1. AopProxy織入對象生成
前面我們講過,Spring Aop織入切面邏輯的入口方法是AbstractAutoProxyCreator.createProxy()方法,如下是該方法的源碼:
protected Object createProxy(Class<?> beanClass, @Nullable String beanName,
@Nullable Object[] specificInterceptors, TargetSource targetSource) {
// 如果目前beanFactory實作了ConfigurableListableBeanFactory接口,則将需要被代理的
// 對象暴露出來
if (this.beanFactory instanceof ConfigurableListableBeanFactory) {
AutoProxyUtils.exposeTargetClass((ConfigurableListableBeanFactory)
this.beanFactory, beanName, beanClass);
}
// 建立代理工廠
ProxyFactory proxyFactory = new ProxyFactory();
// 複制proxyTargetClass,exposeProxy等屬性
proxyFactory.copyFrom(this);
// 如果目前設定了不使用Cglib代理目标類,則判斷目标類是否設定了preserveTargetClass屬性,
// 如果設定了,則還是強制使用Cglib代理目标類;如果沒有設定,則判斷目标類是否實作了相關接口,
// 沒有設定,則還是使用Cglib代理。需要注意的是Spring預設使用的是Jdk代理來織入切面邏輯。
if (!proxyFactory.isProxyTargetClass()) {
// 判斷目标類是否設定了preserveTargetClass屬性
if (shouldProxyTargetClass(beanClass, beanName)) {
proxyFactory.setProxyTargetClass(true);
} else {
// 判斷目标類是否實作了相關接口
evaluateProxyInterfaces(beanClass, proxyFactory);
}
}
// 将需要織入的切面邏輯都轉換為Advisor對象
Advisor[] advisors = buildAdvisors(beanName, specificInterceptors);
proxyFactory.addAdvisors(advisors);
proxyFactory.setTargetSource(targetSource);
// 提供的hook方法,供子類實作以實作對代理工廠的定制
customizeProxyFactory(proxyFactory);
proxyFactory.setFrozen(this.freezeProxy);
// 目前判斷邏輯預設傳回false,子類可進行重寫,對于AnnotationAwareAspectJAutoProxyCreator,
// 其重寫了該方法傳回true,因為其已經對擷取到的Advisor進行了過濾,後面不需要在對目标類進行重新
// 比對了
if (advisorsPreFiltered()) {
proxyFactory.setPreFiltered(true);
}
// 生成代理類
return proxyFactory.getProxy(getProxyClassLoader());
}
可以看到,在生成代理類之前,主要做了兩件事:①判斷使用Jdk代理還是Cglib代理;②設定相關的屬性。這裡我們繼續看最後的ProxyFactory.getProxy()方法:
public Object getProxy(@Nullable ClassLoader classLoader) {
// 首先擷取AopProxy對象,其主要有兩個實作:JdkDynamicAopProxy和ObjenesisCglibAopProxy,
// 分别用于Jdk和Cglib代理類的生成,其getProxy()方法則用于擷取具體的代理對象
return createAopProxy().getProxy(classLoader);
}
上面的createAopProxy()方法可以了解為一個工廠方法,傳回值是一個AopProxy類型的對象,其内部根據具體的條件生成相應的子類對象,即JdkDynamicAopProxy和ObjenesisCglibAopProxy。後面則通過調用AopProxy.getProxy()方法擷取代理過的對象。如下是createAopProxy()方法的實作邏輯:
public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException {
// 判斷目前類是否需要進行運作時優化,或者是指定了使用Cglib代理的方式,再或者是目标類沒有使用者提供的
// 相關接口,則使用Cglib代理實作代理邏輯的織入
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.");
}
// 如果被代理的類是一個接口,或者被代理的類是使用Jdk代理生成的類,此時還是使用Jdk代理
if (targetClass.isInterface() || Proxy.isProxyClass(targetClass)) {
return new JdkDynamicAopProxy(config);
}
// 傳回Cglib代理織入類對象
return new ObjenesisCglibAopProxy(config);
} else {
// 傳回Jdk代理織入類對象
return new JdkDynamicAopProxy(config);
}
}
這裡可以看到,本文需要講解的Cglib代理邏輯的織入就在ObjenesisCglibAopProxy.getProxy()方法中。
2. 代理邏輯的織入
關于代理邏輯的織入,其實作主體還是通過Enhancer來實作,即通過需要織入的Advisor清單,生成Callback對象,并将其設定到Enhancer對象中,最後通過Enhancer生成目标對象。如下是AopProxy.getProxy()方法的源碼:
public Object getProxy(@Nullable ClassLoader classLoader) {
if (logger.isDebugEnabled()) {
logger.debug("Creating CGLIB proxy: target source is "
+ this.advised.getTargetSource());
}
try {
Class<?> rootClass = this.advised.getTargetClass();
Assert.state(rootClass != null,
"Target class must be available for creating a CGLIB proxy");
// 判斷目前類是否是已經通過Cglib代理生成的類,如果是的,則擷取其原始父類,
// 并将其接口設定到需要代理的接口中
Class<?> proxySuperClass = rootClass;
if (ClassUtils.isCglibProxyClass(rootClass)) {
// 擷取父類
proxySuperClass = rootClass.getSuperclass();
// 擷取父類實作的接口,并将其設定到需要代理的接口中
Class<?>[] additionalInterfaces = rootClass.getInterfaces();
for (Class<?> additionalInterface : additionalInterfaces) {
this.advised.addInterface(additionalInterface);
}
}
// 對目标類進行檢查,主要檢查點有三個:
// 1. 目标方法不能使用final修飾;
// 2. 目标方法不能是private類型的;
// 3. 目标方法不能是包通路權限的;
// 這三個點滿足任何一個,目前方法就不能被代理,此時該方法就會被略過
validateClassIfNecessary(proxySuperClass, classLoader);
// 建立Enhancer對象,并且設定ClassLoader
Enhancer enhancer = createEnhancer();
if (classLoader != null) {
enhancer.setClassLoader(classLoader);
if (classLoader instanceof SmartClassLoader &&
((SmartClassLoader) classLoader).isClassReloadable(proxySuperClass)) {
enhancer.setUseCache(false);
}
}
enhancer.setSuperclass(proxySuperClass);
// 這裡AopProxyUtils.completeProxiedInterfaces()方法的主要目的是為要生成的代理類
// 增加SpringProxy,Advised,DecoratingProxy三個需要實作的接口。這裡三個接口的作用如下:
// 1. SpringProxy:是一個空接口,用于标記目前生成的代理類是Spring生成的代理類;
// 2. Advised:Spring生成代理類所使用的屬性都儲存在該接口中,
// 包括Advisor,Advice和其他相關屬性;
// 3. DecoratingProxy:該接口用于擷取目前代理對象所代理的目标對象的Class類型。
enhancer.setInterfaces(AopProxyUtils.completeProxiedInterfaces(this.advised));
enhancer.setNamingPolicy(SpringNamingPolicy.INSTANCE);
enhancer.setStrategy(new
ClassLoaderAwareUndeclaredThrowableStrategy(classLoader));
// 擷取目前需要織入到代理類中的邏輯
Callback[] callbacks = getCallbacks(rootClass);
Class<?>[] types = new Class<?>[callbacks.length];
for (int x = 0; x < types.length; x++) {
types[x] = callbacks[x].getClass();
}
// 設定代理類中各個方法将要使用的切面邏輯,這裡ProxyCallbackFilter.accept()方法傳回
// 的整型值正好一一對應上面Callback數組中各個切面邏輯的下标,也就是說這裡的CallbackFilter
// 的作用正好指定了代理類中各個方法将要使用Callback數組中的哪個或哪幾個切面邏輯
enhancer.setCallbackFilter(new ProxyCallbackFilter(
this.advised.getConfigurationOnlyCopy(), this.fixedInterceptorMap,
this.fixedInterceptorOffset));
enhancer.setCallbackTypes(types);
// 生成代理對象
return createProxyClassAndInstance(enhancer, callbacks);
} catch (CodeGenerationException | IllegalArgumentException ex) {
throw new AopConfigException("Could not generate CGLIB subclass of class ["
+ this.advised.getTargetClass() + "]: Common causes of this problem "
+ "include using a final class or a non-visible class", ex);
} catch (Throwable ex) {
throw new AopConfigException("Unexpected AOP exception", ex);
}
}
可以看到,這裡的AopProxy.getProxy()方法就是生成代理對象的主幹邏輯。上面的邏輯中主要有兩個部分需要重點講解:①如果擷取Callback數組;②CallbackFilter的作用。關于第一點,我們後面會進行重點講解,至于第二點,這裡我們需要了解的就是CallbackFilter.accept()方法接收一個Method類型的參數,該參數也即目前要生成的代理邏輯的方法,這裡的accept()方法将傳回目标目前要織入代理邏輯的方法所需要使用的切面邏輯,也即Callback對象在Callback數組中的下标。關于CallbackFilter的使用原理,讀者可以閱讀
實戰CGLib系列之proxy篇(二):回調過濾CallbackFilter這篇文章。下面我們繼續閱讀getCallbacks()的源碼:
private Callback[] getCallbacks(Class<?> rootClass) throws Exception {
boolean exposeProxy = this.advised.isExposeProxy();
boolean isFrozen = this.advised.isFrozen();
boolean isStatic = this.advised.getTargetSource().isStatic();
// 使用者自定義的代理邏輯的主要織入類
Callback aopInterceptor = new DynamicAdvisedInterceptor(this.advised);
Callback targetInterceptor;
// 判斷如果要暴露代理對象,如果是,則使用AopContext設定将代理對象設定到ThreadLocal中
// 使用者則可以通過AopContext擷取目标對象
if (exposeProxy) {
// 判斷被代理的對象是否是靜态的,如果是靜态的,則将目标對象緩存起來,每次都使用該對象即可,
// 如果目标對象是動态的,則在DynamicUnadvisedExposedInterceptor中每次都生成一個新的
// 目标對象,以織入後面的代理邏輯
targetInterceptor = isStatic ?
new StaticUnadvisedExposedInterceptor(
this.advised.getTargetSource().getTarget()) :
new DynamicUnadvisedExposedInterceptor(this.advised.getTargetSource());
} else {
// 下面兩個類與上面兩個的唯一差別就在于是否使用AopContext暴露生成的代理對象
targetInterceptor = isStatic ?
new StaticUnadvisedInterceptor(this.advised.getTargetSource().getTarget()) :
new DynamicUnadvisedInterceptor(this.advised.getTargetSource());
}
// 目前Callback用于一般的不用背代理的方法,這些方法
Callback targetDispatcher = isStatic ?
new StaticDispatcher(this.advised.getTargetSource().getTarget())
: new SerializableNoOp();
// 将擷取到的callback組裝為一個數組
Callback[] mainCallbacks = new Callback[] {
aopInterceptor, // 使用者自己定義的攔截器
targetInterceptor, // 根據條件是否暴露代理對象的攔截器
new SerializableNoOp(), // 不做任何操作的攔截器
targetDispatcher, this.advisedDispatcher, // 用于存儲Advised對象的分發器
new EqualsInterceptor(this.advised), // 針對equals方法調用的攔截器
new HashCodeInterceptor(this.advised) // 針對hashcode方法調用的攔截器
};
Callback[] callbacks;
// 如果目标對象是靜态的,也即可以緩存的,并且切面邏輯的調用鍊是固定的,
// 則對目标對象和整個調用鍊進行緩存
if (isStatic && isFrozen) {
Method[] methods = rootClass.getMethods();
Callback[] fixedCallbacks = new Callback[methods.length];
this.fixedInterceptorMap = new HashMap<>(methods.length);
for (int x = 0; x < methods.length; x++) {
// 擷取目标對象的切面邏輯
List<Object> chain =
this.advised.getInterceptorsAndDynamicInterceptionAdvice(
methods[x], rootClass);
fixedCallbacks[x] = new FixedChainStaticTargetInterceptor(
chain, this.advised.getTargetSource().getTarget(),
this.advised.getTargetClass());
// 對調用鍊進行緩存
this.fixedInterceptorMap.put(methods[x].toString(), x);
}
// 将生成的靜态調用鍊存入Callback數組中
callbacks = new Callback[mainCallbacks.length + fixedCallbacks.length];
System.arraycopy(mainCallbacks, 0, callbacks, 0, mainCallbacks.length);
System.arraycopy(fixedCallbacks, 0, callbacks, mainCallbacks.length,
fixedCallbacks.length);
// 這裡fixedInterceptorOffset記錄了目前靜态的調用鍊的切面邏輯的起始位置,
// 這裡記錄的用處在于後面使用CallbackFilter的時候,如果發現是靜态的調用鍊,
// 則直接通過該參數擷取相應的調用鍊,而直接略過了前面的動态調用鍊
this.fixedInterceptorOffset = mainCallbacks.length;
} else {
callbacks = mainCallbacks;
}
return callbacks;
}
這裡的getCallbacks()方法主要做了三件事:①擷取目标對象的動态調用鍊;②判斷是否設定了exposeProxy屬性,如果設定了,則生成一個可以暴露代理對象的Callback對象,否則生成一個不做任何處理直接調用目标對象的Callback對象;③判斷目标對象是否是靜态的,并且目前的切面邏輯是否是固定的,如果是,則将目标對象和調用鍊進行緩存,以便後續直接調用。這裡需要說明的一個點在于第三點,因為在判斷目标對象為靜态對象,并且調用鍊是固定的時候,會将目标對象和調用鍊進行緩存,并且封裝到指定的Callback對象中。這裡讀者可能會疑問為什麼動态調用鍊和靜态調用鍊都進行了緩存,這和前面講解的CallbackFilter是息息相關的,因為上述代碼最後使用fixedInterceptorOffset記錄了目前靜态調用鍊在數組中存儲的位置,我們前面也講了,Enhancer可以通過CallbackFilter傳回的整數值來動态的指定從目前對象Callback數組中的第幾個環繞邏輯開始織入,這裡就會使用到fixedInterceptorOffset。從上述代碼中可以看出,使用者自定義的調用鍊是在DynamicAdvisedInterceptor中生成的(關于靜态調用鍊的生成實際上是同樣的邏輯,隻不過靜态調用鍊會被緩存),這裡我們看看DynamicAdvisedInterceptor的實作源碼:
public Object intercept(Object proxy, Method method, Object[] args,
MethodProxy methodProxy) throws Throwable {
Object oldProxy = null;
boolean setProxyContext = false;
Object target = null;
// 通過TargetSource擷取目标對象
TargetSource targetSource = this.advised.getTargetSource();
try {
// 判斷如果需要暴露代理對象,則将目前代理對象設定到ThreadLocal中
if (this.advised.exposeProxy) {
oldProxy = AopContext.setCurrentProxy(proxy);
setProxyContext = true;
}
target = targetSource.getTarget();
Class<?> targetClass = (target != null ? target.getClass() : null);
// 擷取目标對象切面邏輯的環繞鍊
List<Object> chain = this.advised
.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);
Object retVal;
if (chain.isEmpty() && Modifier.isPublic(method.getModifiers())) {
// 對參數進行處理,以使其與目标方法的參數類型一緻,尤其對于數組類型,
// 會單獨處理其資料類型與實際類型一緻
Object[] argsToUse = AopProxyUtils.adaptArgumentsIfNecessary(method, args);
// 因為沒有切面邏輯需要織入,這裡直接調用目标方法
retVal = methodProxy.invoke(target, argsToUse);
} else {
// 通過生成的調用鍊,對目标方法進行環繞調用
retVal = new CglibMethodInvocation(proxy, target, method,
args, targetClass, chain, methodProxy).proceed();
}
// 對傳回值進行處理,如果傳回值就是目前目标對象,那麼将代理生成的代理對象傳回;
// 如果傳回值為空,并且傳回值類型是非void的基本資料類型,則抛出異常;
// 如果上述兩個條件都不符合,則直接将生成的傳回值傳回
retVal = processReturnType(proxy, target, method, retVal);
return retVal;
} finally {
// 如果目标對象不是靜态的,則調用TargetSource.releaseTarget()方法釋放目标對象
if (target != null && !targetSource.isStatic()) {
targetSource.releaseTarget(target);
}
// 将代理對象設定為前面(外層邏輯)調用設定的對象,以防止暴露出來的代理對象不一緻
if (setProxyContext) {
AopContext.setCurrentProxy(oldProxy);
}
}
}
這裡intercept()方法裡主要邏輯有兩點:①為目标對象生成切面邏輯調用鍊;②通過切面邏輯對目标對象進行環繞,并且進行調用。關于這兩點,我們都會進行講解,這裡我們首先看看Cglib是如何生成調用鍊的,如下是getInterceptorsAndDynamicInterceptionAdvice()方法最終調用的源碼,中間略過了部分比較簡單的調用:
public List<Object> getInterceptorsAndDynamicInterceptionAdvice(
Advised config, Method method, @Nullable Class<?> targetClass) {
List<Object> interceptorList = new ArrayList<>(config.getAdvisors().length);
Class<?> actualClass = (targetClass != null ?
targetClass : method.getDeclaringClass());
// 判斷切面邏輯中是否有IntroductionAdvisor類型的Advisor
boolean hasIntroductions = hasMatchingIntroductions(config, actualClass);
AdvisorAdapterRegistry registry = GlobalAdvisorAdapterRegistry.getInstance();
for (Advisor advisor : config.getAdvisors()) {
if (advisor instanceof PointcutAdvisor) {
PointcutAdvisor pointcutAdvisor = (PointcutAdvisor) advisor;
// 這裡判斷切面邏輯的調用鍊是否提前進行過過濾,如果進行過,則不再進行目标方法的比對,
// 如果沒有,則再進行一次比對。這裡我們使用的AnnotationAwareAspectJAutoProxyCreator
// 在生成切面邏輯的時候就已經進行了過濾,因而這裡傳回的是true,本文最開始也對這裡進行了講解
if (config.isPreFiltered() || pointcutAdvisor.getPointcut()
.getClassFilter().matches(actualClass)) {
// 将Advisor對象轉換為MethodInterceptor數組
MethodInterceptor[] interceptors = registry.getInterceptors(advisor);
MethodMatcher mm = pointcutAdvisor.getPointcut().getMethodMatcher();
// 這裡進行比對的時候,首先會檢查是否為IntroductionAwareMethodMatcher類型的
// Matcher,如果是,則調用其定義的matches()方法進行比對,如果不是,則直接調用
// 目前切面的matches()方法進行比對。這裡由于前面進行比對時可能存在部分在靜态比對時
// 無法确認的方法比對結果,因而這裡調用是必要的,而對于能夠确認的比對邏輯,這裡調用
// 也是非常迅速的,因為前面已經對比對結果進行了緩存
if (MethodMatchers.matches(mm, method, actualClass, hasIntroductions)) {
// 判斷如果是動态比對,則使用InterceptorAndDynamicMethodMatcher對其進行封裝
if (mm.isRuntime()) {
for (MethodInterceptor interceptor : interceptors) {
interceptorList.add(
new InterceptorAndDynamicMethodMatcher(interceptor, mm));
}
} else {
// 如果是靜态比對,則直接将調用鍊傳回
interceptorList.addAll(Arrays.asList(interceptors));
}
}
}
} else if (advisor instanceof IntroductionAdvisor) {
IntroductionAdvisor ia = (IntroductionAdvisor) advisor;
// 判斷如果為IntroductionAdvisor類型的Advisor,則将調用鍊封裝為Interceptor數組
if (config.isPreFiltered() || ia.getClassFilter().matches(actualClass)) {
Interceptor[] interceptors = registry.getInterceptors(advisor);
interceptorList.addAll(Arrays.asList(interceptors));
}
} else {
// 這裡是提供的使用自定義的轉換器對Advisor進行轉換的邏輯,因為getInterceptors()方法中
// 會使用相應的Adapter對目标Advisor進行比對,如果能比對上,通過其getInterceptor()方法
// 将自定義的Advice轉換為MethodInterceptor對象
Interceptor[] interceptors = registry.getInterceptors(advisor);
interceptorList.addAll(Arrays.asList(interceptors));
}
}
return interceptorList;
}
這裡擷取調用鍊的邏輯其實比較簡單,其最終的目的就是将Advisor數組一個一個的封裝為Interceptor對象。在進行Advisor封裝的時候,這裡分為了三種類型:
- 如果目标切面邏輯是一般的切面邏輯,即PointcutAdvisor,則會在運作時對目标方法進行動态比對,因為前面可能存在還不能确認的是否應該應用切面邏輯的方法;
- 如果切面邏輯是IntroductionAdvisor的,則将其封裝為Interceptor類型的數組;
- 如果以上兩個都不是,說明切面邏輯可能是使用者自定義的切面邏輯,這裡就通過注冊的AdvisorAdapter進行比對,如果某個Adapter能夠支援目前Advisor的轉換,則調用其getInterceptor()方法将Advisor轉換為MethodInterceptor傳回。
下面我們看看Cglib是如何通過生成的切面調用鍊将目标對象進行環繞的。前面我們講了,将切面邏輯進行織入的邏輯在CglibMethodInvocation中,實際上其調用邏輯在其proceed()方法中,這裡我們直接看該方法的源碼:
public Object proceed() throws Throwable {
// 這裡currentInterceptorIndex記錄了目前調用鍊中正在調用的Intercepor的下标,該數值初始為-1
if (this.currentInterceptorIndex ==
this.interceptorsAndDynamicMethodMatchers.size() - 1) {
// 如果調用鍊為空,則直接調用目标方法
return invokeJoinpoint();
}
// 擷取下一個需要織入的Interceptor邏輯
Object interceptorOrInterceptionAdvice =
this.interceptorsAndDynamicMethodMatchers.get(++this.currentInterceptorIndex);
if (interceptorOrInterceptionAdvice instanceof InterceptorAndDynamicMethodMatcher) {
InterceptorAndDynamicMethodMatcher dm =
(InterceptorAndDynamicMethodMatcher) interceptorOrInterceptionAdvice;
// 對動态的方法進行比對,如果比對成功,才進行調用,否則直接進行下一個Interceptor的調用
if (dm.methodMatcher.matches(this.method, this.targetClass, this.arguments)) {
return dm.interceptor.invoke(this);
} else {
return proceed();
}
} else {
// 如果不需要進行動态比對,則直接進行下一步的調用
return ((MethodInterceptor) interceptorOrInterceptionAdvice).invoke(this);
}
}
這裡proceed()方法的邏輯比較簡單,其使用一個索引記錄了目前正在調用的Interceptor在調用鍊中的位置,并且依次對調用鍊進行調用,進而實作将切面邏輯織入目标對象的目的。這裡最終對目标對象的調用的邏輯在invokeJoinpoint()方法中。
3. 小結
本文首先講解Spring是如何通過配置的參數來選擇使用哪種代理方式的,然後重點講解了Spring Aop是如何使用Cglib代理實作代理邏輯的織入的。
本文來自雲栖社群合作夥伴“開源中國”
本文作者:王練
原文連結