哈嘍呀~~筒子們,小之最近在看Spring
的原始碼,正好遇到幾個Aop
的問題涉及到Java的動態代理,之前對這個東西大致能理解,但是沒有仔細的去看原始碼,今天我們來扒一扒它的真面目。
閱讀本文,你將會收穫:
-
理解為什麼
Jdk
動態代理,代理類必須實現介面 -
理解
Jdk
動態代理全過程 -
看到
Jdk
的動態代理類的類的內容如果只想看這個可以直接翻到最後/(ㄒoㄒ)/ -
如何自己得到一個代理類的內容
-
如何看原始碼
-
變強
老規矩,上程式碼觀現象
依賴的類,明白上面的過程可以跳過不看public interface HelloService {
void sayHello();
}
/******************************************************/
public class HelloServiceImpl implements HelloService {
@Override
public void sayHello() {
System.out.println("給掘金大佬們低頭 (:");
}
}
/****************************************************/
public class HelloInvocationHandle implements InvocationHandler {
private Object target;
public HelloInvocationHandle(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("proxy before=======");
Object result = method.invoke(target, args);
System.out.println("proxy end=======");
return result;
}
}
複製程式碼
上面的程式碼,相信大家在學動態代理的時候,都有寫過,我就不贅述了,大家有想過他是怎麼實現的嗎?為什麼調代理類的sayHello()就可以直接使用InvocationHandler
裡的invoke()
邏輯呢?為什麼代理類必須要實現一個介面這麼麻煩呢?我們繼續往下面看。
原始碼分析
我們看上面的Main
方法,可以看出來Proxy.newProxyInstance()
這個方法承載了代理類的所有的邏輯,所有的魔法都在這裡面
//生成一個代理物件
HelloService proxy = (HelloService) Proxy.newProxyInstance(
Thread.currentThread().getContextClassLoader(),
hello.getClass().getInterfaces(),handle);
複製程式碼
我們點進去,看看裡面長啥樣
private static final Class<?>[] constructorParams =
{ InvocationHandler.class };
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h) {
...
//這裡的interface陣列裡面放的是 HelloService,就是代理類需要實現的那個介面
//這邊複製了一份等待處理
final Class<?>[] intfs = interfaces.clone();
...
//得到了代理類的Class
Class<?> cl = getProxyClass0(loader, intfs);
...
//傳入構造物件拿到代理類的構造器
//從這裡我們可以猜出這個代理類一個有一個構造方法是傳入InvocationHandler進行初始化的
final Constructor<?> cons = cl.getConstructor(constructorParams);
//通過反射傳入之前我們定義的那個HelloInvocationHandle,進行構造器例項化代理物件
return cons.newInstance(new Object[]{h});
複製程式碼
簡單的分析一下上面的程式碼,流程也很簡單
- 傳入
HelloService.class
類進行代理類的生成,這樣代理類就有了原始類的方法資訊,例如sayHello()
- 通過一個
InvocationHandler
這個構造物件拿到該代理類的構造方法 - 傳入之前我們定義的
HelloInvocationHandle
,構造器例項化了活生生的代理物件 從這裡我們大致就能明白了Jdk
動態代理的原理:克隆一個介面,生成一個新類作為代理類,這個類裡面有著我們定義的HelloInvocationHandle進行代理邏輯的處理
這裡就回答了我們上面提到的一個問題: Jdk動態代理,代理類必須實現介面,是因為跟他的實現有關係,他規定了必須要傳一個介面去生成代理類
所以所有的謎團就在生成代理類的getProxyClass0(loader, intfs)
這個方法裡
private static Class<?> getProxyClass0(ClassLoader loader,
Class<?>... interfaces) {
...
// If the proxy class defined by the given loader implementing
// the given interfaces exists, this will simply return the cached copy;
// otherwise, it will create the proxy class via the ProxyClassFactory
//這裡從一個代理快取的Cache中得到代理,Cache裡面涉及虛引用實引用的東西不在我們討論的範圍裡,我們直接看這個類是怎麼生成的
return proxyClassCache.get(loader, interfaces);
}
//從上面的註釋和這裡我們都可以看出代理類是從一個叫ProxyClassFactory裡生成的
private static final WeakCache<ClassLoader, Class<?>[], Class<?>>
proxyClassCache = new WeakCache<>(new KeyFactory(), new ProxyClassFactory());
複製程式碼
看上面的註釋我們可以看出到ProxyClassFactory
負責生成代理類
我這裡看的是jdk1.8
的原始碼,用lambda
重構過,舊版本看到的可以不一樣,主邏輯應該沒什麼變化
// prefix for all proxy class names
private static final String proxyClassNamePrefix = "$Proxy";
public Class<?> apply(ClassLoader loader, Class<?>[] interfaces) {
...
//這裡定義了代理類的名字,所以你每次看到的代理類都是$Proxy開頭的
String proxyName = proxyPkg + proxyClassNamePrefix + num;
//關鍵點在這裡,這裡生成一個代理類
byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
proxyName, interfaces, accessFlags);
}
複製程式碼
ProxyGenerator.generateProxyClass(proxyName, interfaces, accessFlags)
這個方法如果感興趣的大佬點進去可以看到,裡面是用StringBuilder
拼出了這個代理類的位元組碼(:,然後轉成了Byte
陣列。
Bingo
關鍵點來了! 這個代理類長啥樣怎麼看?Debug沒有暴露出來啊親,看不了,小之看到上面的byte[] proxyClassFile
這個變數,漏出了一陣淫笑,前面說過,這個變數裡存的是代理類位元組碼內容啊!沒錯!輸出流伺候! 小之一頓操作,QWER!飛起
哦呵
反編譯,哦呵
public final class $Proxy0 extends Proxy implements HelloService {
private static Method m1; //equals()方法
private static Method m3; //sayHello()方法
private static Method m2; //Bingo 我們的sayHello方法
private static Method m0; //hashCode()方法
//這裡就是我們之前提交的InvocationHandler這個構造方法!!
public $Proxy0(InvocationHandler var1) throws {
super(var1);
}
public final boolean equals(Object var1) throws {
try {
return ((Boolean)super.h.invoke(this, m1, new Object[]{var1})).booleanValue();
} catch (RuntimeException | Error var3) {
throw var3;
} catch (Throwable var4) {
throw new UndeclaredThrowableException(var4);
}
}
//勇士來吧!看一看我們的代理類的sayHello()方法長什麼樣子呀!!
public final void sayHello() throws {
try {
super.h.invoke(this, m3, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
public final String toString() throws {
try {
return (String)super.h.invoke(this, m2, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
public final int hashCode() throws {
try {
return ((Integer)super.h.invoke(this, m0, (Object[])null)).intValue();
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
static {
try {
m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[]{Class.forName("java.lang.Object")});
m3 = Class.forName("proxy.service.HelloService").getMethod("sayHello", new Class[0]);
m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);
m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);
} catch (NoSuchMethodException var2) {
throw new NoSuchMethodError(var2.getMessage());
} catch (ClassNotFoundException var3) {
throw new NoClassDefFoundError(var3.getMessage());
}
}
}
複製程式碼
筒子們,請聚焦上面的代理類sayHello()
方法。真相大白了吧?代理類裡實現了介面裡的方法,全部呼叫了你的HelloInvocation
的invoke()
方法啦!
這裡說一下上面的super.h.invoke(this, m3, (Object[])null);
中super.h
是什麼東西,明眼的大佬都應該看出來了吧?繼承了Proxy類,是Proxy
裡定義的InvocationHandler
變數,也就是你的HelloInvocation
總結
大佬們,是不是覺得Jdk
動態代理也不是很複雜?好像自己也能寫出來?哈哈,本篇文章還想提供一個Debug
程式碼的思路,其實看原始碼沒那麼難,希望可以幫到大家,我們下次再見啦~
對了我這裡安利一下我的Github
:點我點我
目前正在做的幾個東西:
Triple 自己寫的一個RPC
框架,練手的,功能不是很完善,主要目的是為了藉助這個去理解網路相關的知識點,如Netty
,想起來寫一點~
資料結構和演算法 資料結構和演算法是薄弱項,多學學練練好吹吹??呀
Spring原始碼分析 還在分析,裡面會整理一些問題,帶著問題去看原始碼效率更高一點,歡迎大家有什麼關於Spring
的不懂的,可以給我提Issue
,文章寫好了應該也會發到掘金,畢竟基佬多(逃。