spring-aop-4.3.7.RELEASE 在《Spring AOP高級——源碼實現(1)動態代理技術》中介紹了兩種動態代理技術,當然在Spring AOP中代理對象的生成也是運用的這兩種技術。本文將介紹Spring AOP如何通過JDK動態代理的方式創建代理對象。 JDK動態代理以及CGLI ...
spring-aop-4.3.7.RELEASE
在《Spring AOP高級——源碼實現(1)動態代理技術》中介紹了兩種動態代理技術,當然在Spring AOP中代理對象的生成也是運用的這兩種技術。本文將介紹Spring AOP如何通過JDK動態代理的方式創建代理對象。
JDK動態代理以及CGLIB代理這兩種生成代理對象的方式在Spring AOP中分別對應兩個類:JdkDynamicAopProxy和ObjenesisCglibAopProxy,而AopProxy是這兩個類的父介面。
AopProxy介面中定義了兩個基本方法:
1 public interface AopProxy { 2 /** 3 *使用預設的類載入器生成代理對象,預設的類載入器通常是當前線程 4 *的上下文類載入器,可通過Thread#getContextClassLoader()獲得 5 */ 6 Object getProxy(); 7 /** 8 * 使用指定的類載入器創建代理對象,通常用於比較低級別的代理對象 9 * 創建,至於什麼時候用這個暫時先放一放 10 */ 11 Object getProxy(ClassLoader classLoader); 12 }
接著來看它的實現——JdkDynamicAopProxy。
1 //①首先查看JdkDynamicAopProxy類的成員變數 2 final class JdkDynamicAopProxy implements AopProxy, InvocationHandler, Serializable { //JDK動態代理需要實現 InvocationHandler類 3 4 private static final long serialVersionUID = 5531744639992436476L; 5 6 private static final Log logger = LogFactory.getLog(JdkDynamicAopProxy.class); 7 8 /** 代理類的相關配置,這個類繼承自ProxyConfig,都是代理類的相關配置*/ 9 private final AdvisedSupport advised; 10 11 /** 12 * 用於判斷目標對象實現的介面是否定義了equals方法 13 */ 14 private boolean equalsDefined; 15 16 /** 17 * 用於判斷目標對象實現的介面是否定義了hashCode方法 18 */ 19 private boolean hashCodeDefined;
類中有兩個成員變數需要額外註意一下,一個是第14行的equalsDefined變數和第19行的hashCodeDefined變數。
討論它們之前需要首先明確一點,通常情況下,Spring AOP代理對象不會對equals和hashCode方法增強,註意這是在通常情況下,那什麼是“通常情況”,什麼又是“不通常情況”呢?
如果目標對象直接重寫Object對象的equals或hashCode方法,此時Spring AOP則不會對它增強,equalsDefined=false或hashCodeDefined=false;如果目標對象實現的介面定義了equals或hashCode方法,此時Spring AOP則會對它增強,equalsDefined=true或hashCodeDefined=true。所以“通常情況”就是我們並不會在介面定義equals或hashCode方法,“不通常情況”就是在有的特殊情況下在介面定義equals或hashCode方法。再換句話說,如果我們需要Spring AOP增強equals或hashCode方法則必須要在其目標對象的實現介面定義equals或hashCode方法。
1 //②再來看看JdkDynamicAopProxy類的構造方法 2 public JdkDynamicAopProxy(AdvisedSupport config) throws AopConfigException { //只有一個帶AdvisedSupport類型的構造方法,這個類型上面提到過是生成代理類的相關配置,必須不能為空,否則將拋出參數異常的錯誤 3 Assert.notNull(config, "AdvisedSupport must not be null"); 4 if (config.getAdvisors().length == 0 && config.getTargetSource() == AdvisedSupport.EMPTY_TARGET_SOURCE) { //AdvisedSupport配置類中需要定義通知器和目標源。 5 throw new AopConfigException("No advisors and no TargetSource specified"); 6 } 7 this.advised = config; //賦值給成員變數 8 }
構造方法中對於參數的校驗有個比較特殊地方,構造方法不僅需要判斷參數不能為空,而且要判斷參數中的兩個變數——advisors通知器和targetSource目標源。
通知器在《Spring AOP高級——源碼實現(2)Spring AOP中通知器(Advisor)與切麵(Aspect)》中講解過它是一個特殊的切麵,而targetSource目標則是為了獲取target目標對象,這兩個都是實現AOP的重要組成部分必然不能為空。
1 //③接下來是生成代理對象的getProxy方法 2 @Override 3 public Object getProxy() { 4 return getProxy(ClassUtils.getDefaultClassLoader()); //沒有指定ClassLoader則通過Thread.currentThread.getContextClassLoader()獲取當前線程上下文的類載入器。 5 } 6 7 @Override 8 public Object getProxy(ClassLoader classLoader) { 9 if (logger.isDebugEnabled()) { 10 logger.debug("Creating JDK dynamic proxy: target source is " + this.advised.getTargetSource()); 11 } 12 Class<?>[] proxiedInterfaces = AopProxyUtils.completeProxiedInterfaces(this.advised, true); //獲取代理對象需要實現的完整介面 13 findDefinedEqualsAndHashCodeMethods(proxiedInterfaces); //這裡就是上面提到過的判斷的目標對象實現的介面是否定義了equals或hashCode方法,具體原因不再展開 14 return Proxy.newProxyInstance(classLoader, proxiedInterfaces, this); //通過JDK生成代理對象。第一個ClassLoader代表創建類的類載入器,第二個表示需要被代理的目標對象的介面,第三個參數InvocationHandler叫做調用處理器,在這裡它就是對象本身,調用的代理對象方法實際就是調用InvocationHandler介面中的invoke方法。 15 }
第12行調用了AopProxyUtils.completeProxiedInterfaces(this.advised, true)方法,傳入了Spring AOP代理對象配置對象,第二個參數表示是否暴露DecoratingProxy介面,如果設置為true則代理對象會實現DecoratingProxy介面,這個方法是在Spring4.3後新增的方法。來看看這個方法的實現。
1 static Class<?>[] completeProxiedInterfaces(AdvisedSupport advised, boolean decoratingProxy) { 2 Class<?>[] specifiedInterfaces = advised.getProxiedInterfaces(); //獲取目標對象實現的介面 3 …//省略的代碼是通過AdviceSupport沒有獲取到目標對象的實現介面時,則通過直接通過target目標對象來獲取 4 boolean addSpringProxy = !advised.isInterfaceProxied(SpringProxy.class);//是否新增SpringProxy,在AdvisedSupport#isInterfaceProxied方法中會判斷傳入的介面是否已經由目標對象實現。此處傳入SpringProxy.class判斷目標對象是否已經實現該介面,如果沒有實現則在代理對象中需要新增SpringProxy,如果實現了則不必新增。 5 boolean addAdvised = !advised.isOpaque() && !advised.isInterfaceProxied(Advised.class); //是否新增Adviced介面,註意不是Advice通知介面。ProxyConfig#isOpaque方法用於返回由這個配置創建的代理對象是否應該避免被強制轉換為Advised類型。還有一個條件和上面的方法一樣,同理,傳入Advised.class判斷目標對象是否已經實現該介面,如果沒有實現則在代理對象中需要新增Advised,如果實現了則不必新增。 6 boolean addDecoratingProxy = (decoratingProxy && !advised.isInterfaceProxied(DecoratingProxy.class)); //是否新增DecoratingProxy介面,同樣的判斷條件有兩個,第一個參數decoratingProxy,在調用completeProxiedInterfaces方法時傳入的是true,第二個判斷條件和上面一樣判斷被代理的目標對象是否已經實現了DecoratingProxy介面。通常情況下這個介面也會被加入到代理對象中,這是Spring4.3新增的。 7 int nonUserIfcCount = 0; 8 if (addSpringProxy) { 9 nonUserIfcCount++; 10 } 11 if (addAdvised) { 12 nonUserIfcCount++; 13 } 14 if (addDecoratingProxy) { 15 nonUserIfcCount++; 16 } 17 Class<?>[] proxiedInterfaces = new Class<?>[specifiedInterfaces.length + nonUserIfcCount]; //代理類的介面一共是目標對象的介面+上面三個介面SpringProxy、Advised、DecoratingProxy 18 System.arraycopy(specifiedInterfaces, 0, proxiedInterfaces, 0, specifiedInterfaces.length); //將目標對象的介面拷貝,這個方法在《System.arraycopy(src, srcPos, dest, destPos, length) 與 Arrays.copyOf(original, newLength)區別》有介紹 19 //下麵就是講那三個介面加入到specifiedInterfaces數組中並返回 20 int index = specifiedInterfaces.length; 21 if (addSpringProxy) { 22 proxiedInterfaces[index] = SpringProxy.class; 23 index++; 24 } 25 if (addAdvised) { 26 proxiedInterfaces[index] = Advised.class; 27 index++; 28 } 29 if (addDecoratingProxy) { 30 proxiedInterfaces[index] = DecoratingProxy.class; 31 } 32 return proxiedInterfaces; 33 }
生成代理對象後,下一步當然就是調用代理方法,當然這就包括了調用AOP的增強方法以及目標對象的方法。
JDK動態代理生成的代理對象需要實現InvocationHandler介面和invoke方法,這個invoke方法就是JDK代理對象進行攔截的回調入口。 JdkDynamicAopProxy就實現了InvocationHandler介面,這個介面只有一個方法invoke。
public Object invoke(Object proxy, Method method, Object[] args)
這個方法有三個參數:
proxy:指的是我們所代理的那個真實的對象;
method:指的是我們所代理的那個真實對象的某個方法的Method對象;
args:指的是調用那個真實對象方法的參數。
接下來一步一步查看JdkDynamicAopProxy是如何實現invoke方法的。
1 //⑤代理對象的回調方法 2 @Override 3 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { 4 MethodInvocation invocation; 5 Object oldProxy = null; 6 boolean setProxyContext = false; 7 8 TargetSource targetSource = this.advised.targetSource; 9 Class<?> targetClass = null; 10 Object target = null; 11 12 try { 13 if (!this.equalsDefined && AopUtils.isEqualsMethod(method)) { 14 // 這裡就是之前提到過的,“通常情況”Spring AOP不會對equals方法進行攔截增強,所以這裡判斷如果目標對象沒有定義equals方法的話,就會直接調用而不會增強 15 return equals(args[0]); 16 } 17 else if (!this.hashCodeDefined && AopUtils.isHashCodeMethod(method)) { 18 //同上 19 return hashCode(); 20 } 21 else if (method.getDeclaringClass() == DecoratingProxy.class) { 22 // 這裡是上面的疑點,也是Spring4.3新出現的特性 23 return AopProxyUtils.ultimateTargetClass(this.advised); 24 } 25 else if (!this.advised.opaque && method.getDeclaringClass().isInterface() && 26 method.getDeclaringClass().isAssignableFrom(Advised.class)) { 27 //這個地方就有點意思了,Spring AOP不會增強直接實現Advised介面的目標對象,再重覆一次,也就是說如果目標對象實現的Advised介面,則不會對其應用切麵進行方法的增強。 28 return AopUtils.invokeJoinpointUsingReflection(this.advised, method, args); //這個方法是一個對Java通過反射調用方法的封裝 29 } 30 Object retVal; //方法的返回值 31 if (this.advised.exposeProxy) { //是否暴露代理對象,預設false可配置為true,如果暴露就意味著允許線上程內共用代理對象,註意這是線上程內,也就是說同一線程的任意地方都能通過AopContext獲取該代理對象,這應該算是比較高級一點的用法了。 32 oldProxy = AopContext.setCurrentProxy(proxy); 33 setProxyContext = true; 34 } 35 target = targetSource.getTarget(); //通過目標源獲取目標對象 36 if (target != null) { 37 targetClass = target.getClass(); //獲取目標對象Class對象 38 } 39 //...暫略 40 }
上面可以說是對方法調用的一些預處理,有幾個重要的地方:
- 第13-29行,對一些情況的特殊判斷,主要是不對目標對象應用切麵;
- 第31行,判斷是否暴露代理對象,預設false不暴露,如果暴露則表示線上程內的任意位置都能通過AopContext獲取代理對象;
- 第35行,通過目標源獲取目標對象。
下麵可以說是重中之重,也就是具體是如何對目標對象應用切麵對其方法進行增強的。
1 //⑥獲取攔截器鏈,並調用增強方法及目標對象的方法 2 @Override 3 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { 4 //... 5 List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass); //這個方法獲取攔截器鏈。 6 if (chain.isEmpty()) { //攔截器鏈如果為空的話就直接調用目標對象的方法。. 7 Object[] argsToUse = AopProxyUtils.adaptArgumentsIfNecessary(method, args); 8 retVal = AopUtils.invokeJoinpointUsingReflection(target, method, argsToUse); //直接調用目標對象的方法。 9 } 10 else { //通過ReflectiveMethodInvocation.proceed調用攔截器中的方法和目標對象方法 11 invocation = new ReflectiveMethodInvocation(proxy, target, method, args, targetClass, chain); //ReflectiveMethodInvocation對象完成對AOP功能實現的封裝 12 retVal = invocation.proceed(); //獲取返回值 13 } 14 Class<?> returnType = method.getReturnType(); //獲取返回值類型 15 if (retVal != null && retVal == target && returnType != Object.class && returnType.isInstance(proxy) && !RawTargetAccess.class.isAssignableFrom(method.getDeclaringClass())) { 16 // 一些列的判斷條件,如果返回值不為空,且為目標對象的話,就直接將目標對象賦值給retVal 17 retVal = proxy; 18 } 19 else if (retVal == null && returnType != Void.TYPE && returnType.isPrimitive()) { 20 throw new AopInvocationException("Null return value from advice does not match primitive return type for: " + method); //沒有返回值 21 } 22 return retVal; 23 } 24 //...暫略
這段代碼有3個比較關鍵的地方:
- 第5行,獲取攔截器鏈,相當於是獲取潛在的增強方法,只是潛在,後續還有匹配的判斷;
- 第8行,沒有獲取到攔截器鏈,此時相當於直接調用目標對象的方法,AopUtils.invokeJoinpointUsingReflection方法實際是對JDK反射調用方法的一個封裝;
- 第12行,這裡就是進入攔截器鏈中增強方法和目標對象的調用的地方,關鍵。
接下來就是進入ReflectiveMethodInvocation.proceed方法,來探討下Spring AOP是如何對目標對象調用方法進行增強以及調用的。
1 //⑦連接器鏈的調用,ReflectiveMethodInvocation#proceed,這是一個遞歸方法,退出條件就是調用完了攔截鏈中的所有攔截器方法後,再調用目標對象的方法。 2 這個方法的邏輯在於通過攔截器鏈,逐個獲取其中的攔截器,再通過匹配判斷,判斷是否適用,如果適用則取出攔截器中的通知器並通過通知器的invoke方法調用,如果不適用則遞歸調用。 3 @Override 4 public Object proceed() throws Throwable { 5 if (this.currentInterceptorIndex == this.interceptorsAndDynamicMethodMatchers.size() - 1) { //調用完了所有的攔截鏈中攔截器的增強方法,直接調用目標對象的方法並退出 6 return invokeJoinpoint(); //這個方法就是調用AopUtils.invokeJoinpointUsingReflection,上面提到過。 7 } 8 9 Object interceptorOrInterceptionAdvice = 10 this.interceptorsAndDynamicMethodMatchers.get(++this.currentInterceptorIndex); //從攔截器鏈中獲取攔截器 11 if (interceptorOrInterceptionAdvice instanceof InterceptorAndDynamicMethodMatcher) { //這裡進行動態匹配 12 InterceptorAndDynamicMethodMatcher dm = 13 (InterceptorAndDynamicMethodMatcher) interceptorOrInterceptionAdvice; 14 if (dm.methodMatcher.matches(this.method, this.targetClass, this.arguments)) { //這裡如果和定義的切點匹配,那麼這個通知就會得到執行 15 return dm.interceptor.invoke(this); 16 } 17 else { 18 return proceed(); //不適用遞歸繼續獲取攔截器進行匹配、判斷、調用 19 } 20 } 21 else { //這裡判斷出這個攔截器是一個MethodInterceptor則直接調用 22 return ((MethodInterceptor) interceptorOrInterceptionAdvice).invoke(this); //動態匹配失敗則直接調用 23 } 24 }
這裡就是整個調用過程,同樣有一個大的重點:
第11行,這裡是進行動態匹配,什麼是動態匹配呢?我們給出如下的示例,按照xml配置一個通知器而不是切麵。
1 <!--定義通知器bean,需要實現Advice介面--> 2 <bean id="testAdvisor" class="com.springdemo.aop.MyAdvice"/> 3 <!--目標對象--> 4 <bean id="testPoint" class="com.springdemo.aop.TestPoint"/> 5 <!--advisor通知器--> 6 <bean id="testAop" class="org.springframework.aop.framework.ProxyFactoryBean"> 7 <!--目標對象實現的介面--> 8 <property name="proxyInterfaces"> 9 <value>com.springdemo.aop.Test</value> 10 </property> 11 <!--攔截器,也就是上面定義的通知器beanId--> 12 <property name="interceptorNames"> 13 <list> 14 <value>testAdvisor</value> 15 </list> 16 </property> 17 <!--目標對象,也就是上面定義的目標對象beanId--> 18 <property name="targetName"> 19 <value>testPoint</value> 20 </property> 21 </bean>
上面的註釋已經比較清楚了,如果我們按照這樣的方式來實現一個目標對象的方法增強,那麼此時,在調用代理對象的方法時,也就是在執行上面ReflectiveMethodInvocation.proceed方法時是不會進行動態匹配的,因為我們在定義一個advisor通知器也就是上面的Myadvice類的時候是一定會實現Advice介面,如果我們定義的是MethodBeforeAdvice那麼此時就已經確定一定會是前置通知。所以它一定會進入else那個分支去。
如果,我們不是通過定義advisor通知器的方式,而是直接定義一個切麵,那麼,在我們定義切麵這個類是是不需要實現任何介面的,其中的任意方法都可以作為前置或者後置通知,這取決於你的xml配置,例如下麵的配置:
<aop:config> <aop:aspect ref="aspectTest"> <aop:pointcut id="test" expression="execution(* com.demo.TestPoint.test())"/> <aop:before method="doBefore" pointcut-ref="test"/> <aop:after-returning method="doAfter" pointcut-ref="test"/> </aop:aspect> </aop:config>
關於這個地方的理解,建議多寫幾個demo通過打斷點的方式反覆咀嚼。下一篇將會介紹Spring AOP是如何通過CGLib生成代理對象的,本文還有很多很多不足之處,希望有看到的人懂的不懂的都能指出來,非常非常感謝。
這是一個能給程式員加buff的公眾號