前言
往期文章:
- Spring IoC - Spring IoC 的設計
- Spring IoC - IoC 容器初始化 源碼解析
- Spring IoC - 依賴注入 源碼解析
- 向您生動地講解Spring AOP 源碼(1)
- 向您生動地講解Spring AOP 源碼(2)
在上一章向您生動地講解Spring AOP 源碼(2)中,作者介紹了【如何獲取對應 Bean 適配的Advisors 鏈】。
在本章中,作者會向您介紹,Spring AOP 是如何解析我們配置的Aspect,並將advice織入的?
在本章的附錄部分,還會介紹如何保存 JDK動態代理和 Cglib生成的類文件。
閒話不多說,讓我們直接開始。
創建代理類
上一章結束之後,Spring AOP的核心邏輯已經走了一半了,獲取了目標類所適用的增強器列表,下面開始分析獲取代理的過程。
未免讀者閱讀不連貫,我們再貼一下向您生動地講解Spring AOP 源碼(1)中我們最後講解的一段源碼,由此繼續往下講述。
源碼位置:AbstractAutoProxyCreator#wrapIfNecessary(..)
TODO-2 createProxy
稍微提一下 TargetSource
這個概念,它用於封裝真實實現類的信息,在我理解看來就是把獲取目標對象這個步驟做了一個代理的操作,提供一個擴展點給外部,使得使用者可以通過這個擴展點去對目標對象做一些處理;
上面用了 SingletonTargetSource
這個實現類,其實我們這裏也不太需要關心這個,知道有這麼回事就可以了,個人感覺這個擴展點用處不是特別的大。
來條分割線,正式進入今天的核心內容。
現在,讓我們開始解析,Spring AOP創建代理類的流程。
源碼位置:AbstractAutoProxyCreator#createProxy(..)
流程:
- 獲取當前類中的屬性
- 添加代理接口
- 封裝Advisor並加入到ProxyFactory中
- 設置要代理的類
- Spring中爲子類提供了定製的函數
customizeProxyFactory
,子類可以在此函數中對ProxyFactory的進一步封裝 - ★★★ 獲取代理操作
主要分析關鍵的生成代理類的操作。
源碼位置:ProxyFactory#getProxy(..)
public Object getProxy(ClassLoader classLoader) {
return createAopProxy().getProxy(classLoader);
}
這裏要分爲兩步,
- 創建
AopProxy
- 獲取代理類
1. 創建 AopProxy
protected final synchronized AopProxy createAopProxy() {
if (!this.active) {
activate();
}
return getAopProxyFactory().createAopProxy(this);
}
這一步之後我們根據ProxyConfig 獲取到了對應的AopProxy
的實現類,分別是JdkDynamicAopProxy
和ObjenesisCglibAopProxy
。
2. 獲取代理類
JDK 動態代理
源碼位置:JdkDynamicAopProxy#getProxy(..)
我們關注的是最後一行代碼Proxy.newProxyInstance(classLoader, proxiedInterfaces, this)
,
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h) {
...
}
注:到這裏,你需要了解一下JDK動態代理的使用知識,如果能瞭解原理,那就更好了
第一個參數是類加載器,第二個參數是目標類的接口集合,第三個參數則是InvocationHandler
的實現類,JdkDynamicAopProxy
在創建代理的時候,是將自身作爲 InvocationHandler
傳入的,由此可知JdkDynamicAopProxy
本身實現了InvocationHandler
接口。
熟悉JDK動態代理實現機制的同學應該會知道,調用代理類的對應方法時,代理類實際上是通過invoke(Object proxy, Method method, Object[] args)
方法來完成 target class 方法的調用,並在裏面進行一些代理類想做的其他的操作。
在AOP中,invoke
方法會完成AOP編織實現的封裝。所以讓我們看看這個invoke
方法是怎麼實現的。
invoke
方法的關鍵就在於,利用責任鏈模式,遞歸調用的方法,來完成advice 的織入。
ReflectiveMethodInvocation
構造方法
關鍵的ReflectiveMethodInvocation#proceed()
方法
我們來看看((MethodInterceptor) interceptorOrInterceptionAdvice).invoke(this)
,這個方法有多種實現,其中一些我們熟悉的或者說需要關注的實現,對應的就是我們Advice的類型,或者說增強的時機。
術語 | 概念 |
---|---|
Before |
在方法被調用之前執行增強 |
After |
在方法被調用之後執行增強 |
After-returning |
在方法成功執行之後執行增強 |
After-throwing |
在方法拋出指定異常後執行增強 |
Around |
在方法調用的前後執行自定義的增強行爲(最靈活的方式) |
這裏我們用概覽的方式過一下這幾種的實現,
① MethodBeforeAdviceInterceptor#invoke(..)
② AspectJAfterAdvice#invoke(..)
③ AfterReturningAdviceInterceptor#invoke(..)
④ AspectJAfterThrowingAdvice#invoke(..)
⑤ AspectJAroundAdvice#invoke(..)
Cglib 代理
Cglib 代理 和 JDK 代理 在流程上相似,只是在具體實現上不一樣。核心就是Enhancer
和獲得callbacks
的過程。這裏就不分析了。
小結
本章的核心內容就是,創建代理類時,Spring 根據 AOP 配置選擇JDK動態代理或是 Cglib 代理,增強器的織入是按照事先排序好的順序、advice 的類型來起作用的。
個人認爲核心難點還是在對JDK動態代理和Cglib代理 原理的理解。讀者如果對這塊不熟悉,可以查閱其他的文章進行學習。
可以學習到責任鏈的設計模式、JDK 動態代理和反射、Cglib代理等Java 核心知識。
最後,作者寫到這裏,也是長呼一口氣,源碼分析不像新技術那樣,一開始就抓人眼球,很難寫得引人入勝,通常篇幅過長,寫的人會乏,看的人也會乏。所幸作者堅持了下來,在這期間對AOP的源碼也有了更深的理解。
附錄
理解JDK 動態代理 和 CGLIB 代理 生成的代理類的源碼會讓你對advice織入的時機有更深的理解。
JDK 動態代理 類源碼
TestSvc
public interface TestSvc {
void process();
}
@Service("testSvc")
public class TestSvcImpl implements TestSvc {
@Override
public void process() {
System.out.println("test svc is working");
}
}
生成代理類:
關鍵點:實現接口,method.invoke(…) 反射調用
package com.sun.proxy;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;
import ric.study.demo.aop.svc.TestSvc;
public final class $Proxy19
extends Proxy
implements TestSvc
{
private static Method m1;
private static Method m2;
private static Method m0;
private static Method m3;
public $Proxy19(InvocationHandler paramInvocationHandler)
{
super(paramInvocationHandler);
}
public final boolean equals(Object paramObject)
{
try
{
return ((Boolean)this.h.invoke(this, m1, new Object[] { paramObject })).booleanValue();
}
catch (Error|RuntimeException localError)
{
throw localError;
}
catch (Throwable localThrowable)
{
throw new UndeclaredThrowableException(localThrowable);
}
}
public final String toString()
{
try
{
return (String)this.h.invoke(this, m2, null);
}
catch (Error|RuntimeException localError)
{
throw localError;
}
catch (Throwable localThrowable)
{
throw new UndeclaredThrowableException(localThrowable);
}
}
public final int hashCode()
{
try
{
return ((Integer)this.h.invoke(this, m0, null)).intValue();
}
catch (Error|RuntimeException localError)
{
throw localError;
}
catch (Throwable localThrowable)
{
throw new UndeclaredThrowableException(localThrowable);
}
}
public final void process()
{
try
{
this.h.invoke(this, m3, null);
return;
}
catch (Error|RuntimeException localError)
{
throw localError;
}
catch (Throwable localThrowable)
{
throw new UndeclaredThrowableException(localThrowable);
}
}
static
{
try
{
m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[] { Class.forName("java.lang.Object") });
m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);
m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);
m3 = Class.forName("ric.study.demo.aop.svc.TestSvc").getMethod("process", new Class[0]);
return;
}
catch (NoSuchMethodException localNoSuchMethodException)
{
throw new NoSuchMethodError(localNoSuchMethodException.getMessage());
}
catch (ClassNotFoundException localClassNotFoundException)
{
throw new NoClassDefFoundError(localClassNotFoundException.getMessage());
}
}
}
如何保存JDK 動態代理的源文件
只需要在系統變量中設置sun.misc.ProxyGenerator.saveGeneratedFiles
爲true
即可。比如這樣,
會在項目目錄下生成com.sun.proxy
目錄,並存儲對應的文件。想要找到你的代理類到底是哪個,你還需要打印出(或者debug查看)這個代理類的類名,像我上圖一樣。
Cglib 代理 類源碼
關鍵:繼承;MethodInterceptor.intercept();
BTW:Cglib 的 源碼未免太過冗長,放上來的閱讀體驗非常不好(1000+行)。讀者可以按照我後面提到的方法自己生成,然後利用反編譯工具查看。
如何保存 Cglib 生成代理類 的源文件
和JDK 動態代理類似,System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "...");
,設置class 文件的輸出目錄即可。