原來這才是動態(tài)代理?。。?/h1>
各位小伙伴們大家吼啊!我是 cxuan,距離上次更新已經(jīng)有段時間了,臨近過年了,項目這邊也比較忙,而且最近很多時間都花在看書、提升自己上面,文章寫的比較拖沓,這里我要自我反思(其實我已經(jīng)籌備了幾篇文章,就等結(jié)尾了,嘿嘿嘿)。
我們上篇文章聊了一波什么是動態(tài)代理,然后我又從動態(tài)代理的四種實現(xiàn)為切入點,為你講解 JDK 動態(tài)代理、CGLIB 動態(tài)代理、Javaassist、ASM 反向生成字節(jié)碼的區(qū)別,具體的內(nèi)容你可以參見下面這篇文章。
動態(tài)代理竟然如此簡單!
那么這篇文章我們來聊一下動態(tài)代理的實現(xiàn)原理。
為了保險起見,我們首先花幾分鐘回顧一下什么是動態(tài)代理吧!
什么是動態(tài)代理
首先,動態(tài)代理是代理模式的一種實現(xiàn)方式,代理模式除了動態(tài)代理還有 靜態(tài)代理,只不過靜態(tài)代理能夠在編譯時期確定類的執(zhí)行對象,而動態(tài)代理只有在運行時才能夠確定執(zhí)行對象是誰。代理可以看作是對最終調(diào)用目標的一個封裝,我們能夠通過操作代理對象來調(diào)用目標類,這樣就可以實現(xiàn)調(diào)用者和目標對象的解耦合。
動態(tài)代理的應用場景有很多,最常見的就是 AOP 的實現(xiàn)、RPC 遠程調(diào)用、Java 注解對象獲取、日志框架、全局性異常處理、事務處理等。
動態(tài)代理的實現(xiàn)有很多,但是 JDK 動態(tài)代理是很重要的一種,下面我們就 JDK 動態(tài)代理來深入理解一波。
JDK 動態(tài)代理
首先我們先來看一下動態(tài)代理的執(zhí)行過程
在 JDK 動態(tài)代理中,實現(xiàn)了 InvocationHandler的類可以看作是 代理類(因為類也是一種對象,所以我們上面為了描述關系,把代理類形容成了代理對象)。JDK 動態(tài)代理就是圍繞實現(xiàn)了 InvocationHandler 的代理類進行的,比如下面就是一個 InvocationHandler 的實現(xiàn)類,同時它也是一個代理類。
public class UserHandler implements InvocationHandler {
private UserDao userDao;
public UserHandler(UserDao userDao){
this.userDao = userDao;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
saveUserStart();
Object obj = method.invoke(userDao, args);
saveUserDone();
return obj;
}
public void saveUserStart(){
System.out.println("---- 開始插入 ----");
}
public void saveUserDone(){
System.out.println("---- 插入完成 ----");
}
}
代理類一個最最最重要的方法就是 invoke 方法,它有三個參數(shù)
- Object proxy: 動態(tài)代理對象,關于這個方法我們后面會說。
- Method method: 表示最終要執(zhí)行的方法,method.invoke 用于執(zhí)行被代理的方法,也就是真正的目標方法
- Object[] args: 這個參數(shù)就是向目標方法傳遞的參數(shù)。
我們構(gòu)造好了代理類,現(xiàn)在就要使用它來實現(xiàn)我們對目標對象的調(diào)用,那么如何操作呢?請看下面代碼
public static void dynamicProxy(){
UserDao userDao = new UserDaoImpl();
InvocationHandler handler = new UserHandler(userDao);
ClassLoader loader = userDao.getClass().getClassLoader();
Class<?>[] interfaces = userDao.getClass().getInterfaces();
UserDao proxy = (UserDao)Proxy.newProxyInstance(loader, interfaces, handler);
proxy.saveUser();
}
如果要用 JDK 動態(tài)代理的話,就需要知道目標對象的類加載器、目標對象的接口,當然還要知道目標對象是誰。構(gòu)造完成后,我們就可以調(diào)用 Proxy.newProxyInstance方法,然后把類加載器、目標對象的接口、目標對象綁定上去就完事兒了。
這里需要注意一下 Proxy 類,它就是動態(tài)代理實現(xiàn)所用到的代理類。
Proxy 位于java.lang.reflect 包下,這同時也旁敲側(cè)擊的表明動態(tài)代理的本質(zhì)就是反射。
下面我們就圍繞 JDK 動態(tài)代理,來深入理解一下它的原理,以及搞懂為什么動態(tài)代理的本質(zhì)就是反射。
動態(tài)代理的實現(xiàn)原理
在了解動態(tài)代理的實現(xiàn)原理之前,我們先來了解一下 InvocationHandler 接口
InvocationHandler 接口
JavaDoc 告訴我們,InvocationHandler 是一個接口,實現(xiàn)這個接口的類就表示該類是一個代理實現(xiàn)類,也就是代理類。
InvocationHandler 接口中只有一個 invoke 方法。
動態(tài)代理的優(yōu)勢在于能夠很方便的對代理類中方法進行集中處理,而不用修改每個被代理的方法。因為所有被代理的方法(真正執(zhí)行的方法)都是通過在 InvocationHandler 中的 invoke 方法調(diào)用的。所以我們只需要對 invoke 方法進行集中處理。
invoke 方法只有三個參數(shù)
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable;
- proxy:代理對象
- method: 代理對象調(diào)用的方法
- args:調(diào)用方法中的參數(shù)。
動態(tài)代理的整個代理過程不像靜態(tài)代理那樣一目了然,清晰易懂,因為在動態(tài)代理的過程中,我們沒有看到代理類的真正代理過程,也不明白其具體操作,所以要分析動態(tài)代理的實現(xiàn)原理,我們必須借助源碼。
那么問題來了,首先第一步應該從哪分析?如果不知道如何分析的話,干脆就使用倒推法,從后往前找,我們直接先從 Proxy.newProxyInstance入手,看看是否能略知一二。
Proxy.newInstance 方法分析
Proxy 提供了創(chuàng)建動態(tài)代理類和實例的靜態(tài)方法,它也是由這些方法創(chuàng)建的所有動態(tài)代理類的超類。
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)
throws IllegalArgumentException
{
Objects.requireNonNull(h);
final Class<?>[] intfs = interfaces.clone();
final SecurityManager sm = System.getSecurityManager();
if (sm != null) {
checkProxyAccess(Reflection.getCallerClass(), loader, intfs);
}
Class<?> cl = getProxyClass0(loader, intfs);
try {
if (sm != null) {
checkNewProxyPermission(Reflection.getCallerClass(), cl);
}
final Constructor<?> cons = cl.getConstructor(constructorParams);
final InvocationHandler ih = h;
if (!Modifier.isPublic(cl.getModifiers())) {
AccessController.doPrivileged(new PrivilegedAction<Void>() {
public Void run() {
cons.setAccessible(true);
return null;
}
});
}
return cons.newInstance(new Object[]{h});
} catch (Exception e) {
...
}
乍一看起來有點麻煩,其實源碼都是這樣,看起來非常復雜,但是慢慢分析、厘清條理過后就好,最重要的是分析源碼不能著急。
上面這個 Proxy.newProxyInstsance 其實就做了下面幾件事,我畫了一個流程圖作為參考。
從上圖中我們也可以看出,newProxyInstsance 方法最重要的幾個環(huán)節(jié)就是獲得代理類、獲得構(gòu)造器,然后構(gòu)造新實例。
對反射有一些了解的同學,應該會知道獲得構(gòu)造器和構(gòu)造新實例是怎么回事。關于反射,可以參考筆者的這篇文章
學會反射后,我被錄取了!
所以我們的重點就放在了獲得代理類,這是最關鍵的一步,對應源碼中的 Class cl = getProxyClass0(loader, intfs); 我們進入這個方法一探究竟
private static Class<?> getProxyClass0(ClassLoader loader,
Class<?>... interfaces) {
if (interfaces.length > 65535) {
throw new IllegalArgumentException("interface limit exceeded");
}
return proxyClassCache.get(loader, interfaces);
}
這個方法比較簡單,首先會直接判斷接口長度是否大于 65535(剛開始看到這里我是有點不明白的,我心想這個判斷是要判斷什么?interfaces 這不是一個 class 類型嗎,從 length 點進去也看不到這個屬性,細看一下才明白,這居然是可變參數(shù),Class ... 中的 ... 就是可變參數(shù),所以這個判斷我猜測應該是判斷接口數(shù)量是否大于 65535。)
然后會直接從 proxyClassCache 中根據(jù) loader 和 interfaces 獲取代理對象實例。如果能夠根據(jù) loader 和 interfaces 找到代理對象,將會返回緩存中的對象副本;否則,它將通過 ProxyClassFactory 創(chuàng)建代理類。
proxyClassCache.get 就是一系列從緩存中的查詢操作,注意這里的 proxyClassCache 其實是一個WeakCache,WeakCahe 也是位于 java.lang.reflect 包下的一個緩存映射 map,它的主要特點是一個弱引用的 map,但是它內(nèi)部有一個 SubKey ,這個子鍵卻是強引用的。
這里我們不用去追究這個 proxyClassCache 是如何進行緩存的,只需要知道它的緩存時機就可以了:即在類加載的時候進行緩存。
如果無法找到代理對象,就會通過 ProxyClassFactory 創(chuàng)建代理,ProxyClassFactory 繼承于 BiFunction
private static final class ProxyClassFactory
implements BiFunction<ClassLoader, Class<?>[], Class<?>>
{...}
ProxyClassFactory 里面有兩個屬性一個方法。
proxyClassNamePrefix:這個屬性表明使用 ProxyClassFactory 創(chuàng)建出來的代理實例的命名是以 "$Proxy" 為前綴的。
nextUniqueNumber:這個屬性表明 ProxyClassFactory 的后綴是使用 AtomicLong 生成的數(shù)字
所以代理實例的命名一般是 、Proxy1這種。
這個 apply 方法是一個根據(jù)接口和類加載器進行代理實例創(chuàng)建的工廠方法,下面是這段代碼的核心。
@Override
public Class<?> apply(ClassLoader loader, Class<?>[] interfaces) {
...
long num = nextUniqueNumber.getAndIncrement();
String proxyName = proxyPkg + proxyClassNamePrefix + num;
byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
proxyName, interfaces, accessFlags);
try {
return defineClass0(loader, proxyName,
proxyClassFile, 0, proxyClassFile.length);
} catch (ClassFormatError e) {
throw new IllegalArgumentException(e.toString());
}
}
可以看到,代理實例的命名就是我們上面所描述的那種命名方式,只不過它這里加上了 proxyPkg 包名的路徑。然后下面就是生成代理實例的關鍵代碼。
ProxyGenerator.generateProxyClass 我們跟進去是只能看到 .class 文件的,class 文件是虛擬機編譯之后的結(jié)果,所以我們要看一下 .java 文件源碼。.java 源碼位于 OpenJDK中的 sun.misc 包中的 ProxyGenerator 下。
此類的 generateProxyClass() 靜態(tài)方法的核心內(nèi)容就是去調(diào)用 generateClassFile() 實例方法來生成 Class 文件。方法太長了我們不貼了,這里就大致解釋以下其作用:
- 第一步:收集所有要生成的代理方法,將其包裝成 ProxyMethod 對象并注冊到 Map 集合中。
- 第二步:收集所有要為 Class 文件生成的字段信息和方法信息。
- 第三步:完成了上面的工作后,開始組裝 Class 文件。
而 defineClass0 這個方法我們點進去是 native ,底層是 C/C++ 實現(xiàn)的,于是我又去看了一下 C/C++ 源碼,路徑在
點開之后的 C/C++ 源碼還是挺讓人絕望的。
不過我們再回頭看一下這個 defineClass0 方法,它實際上就是根據(jù)上面生成的 proxyClassFile 字節(jié)數(shù)組來生成對應的實例罷了,所以我們不必再深究 C/C++ 對于代理對象的合成過程了。
所以總結(jié)一下可以看出,JDK 為我們的生成了一個叫 $Proxy0 的代理類,這個類文件放在內(nèi)存中的,我們在創(chuàng)建代理對象時,就是通過反射獲得這個類的構(gòu)造方法,然后創(chuàng)建的代理實例。
所以最開始的 dynamicProxy 方法我們反編譯后的代碼就是這樣的
public final class $Proxy0 extends java.lang.reflect.Proxy implements com.cxuan.dynamic.UserDao {
public $Proxy0(java.lang.reflect.InvocationHandler) throws ;
Code:
0: aload_0
1: aload_1
2: invokespecial #8 // Method java/lang/reflect/Proxy."<init>":(Ljava/lang/reflect/InvocationHandler;)V
5: return
我們看到代理類繼承了 Proxy 類,所以也就決定了 Java 動態(tài)代理只能對接口進行代理。
于是,上面這個圖你應該就可以看懂了。
invoke 方法中第一個參數(shù) proxy 的作用
細心的小伙伴們可能都發(fā)現(xiàn)了,invoke 方法中第一個 proxy 的作用是啥?代碼里面好像 proxy 也沒用到啊,這個參數(shù)的意義是啥呢?它運行時的類型是啥啊?為什么不使用 this 代替呢?
Stackoverflow 給出了我們一個回答 https://stackoverflow.com/questions/22930195/understanding-proxy-arguments-of-the-invoke-method-of-java-lang-reflect-invoca
什么意思呢?
就是說這個 proxy ,它是真正的代理對象,invoke 方法可以返回調(diào)用代理對象方法的返回結(jié)果,也可以返回對象的真實代理對象,也就是 $Proxy0,這也是它運行時的類型。
至于為什么不用 this 來代替 proxy,因為實現(xiàn)了 InvocationHandler 的對象中的 this ,指代的還是 InvocationHandler 接口實現(xiàn)類本身,而不是真實的代理對象。
后記
另外,這段時間公眾號出了一些狀況,大家在公眾號回復的一些關鍵詞都沒有對應的連接了,這里和大家說明抱歉。