理解JDK動(dòng)態(tài)代理為什么必須要基于接口
1. 前言
JDK 動(dòng)態(tài)代理的應(yīng)用還是非常廣泛的,例如在 Spring、MyBatis 以及 Feign 等很多框架中動(dòng)態(tài)代理都被大量的使用,可以說(shuō)學(xué)好 JDK 動(dòng)態(tài)代理,對(duì)于我們閱讀這些框架的底層源碼還是很有幫助的
2. 一個(gè)簡(jiǎn)單的例子
在分析原因之前,我們先完整的看一下實(shí)現(xiàn) JDK 動(dòng)態(tài)代理需要幾個(gè)步驟,首先需要定義一個(gè)接口
2.1. 定義接口
public interface Worker {
void work();
}2.2. 接口實(shí)現(xiàn)類
public class Programmer implements Worker {
@Override
public void work() {
System.out.println("coding...");
}
}2.3. 自定義 Handler
自定義一個(gè) Handler,實(shí)現(xiàn) InvocationHandler 接口,通過(guò)重寫(xiě)內(nèi)部的 invoke() 方法實(shí)現(xiàn)邏輯增強(qiáng)
public class WorkHandler implements InvocationHandler {
private final Object target;
public WorkHandler(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if (method.getName().equals("work")) {
System.out.println("before work...");
Object result = method.invoke(target, args);
System.out.println("after work...");
return result;
}
return method.invoke(target, args);
}
}2.4. 測(cè)試
在 main() 方法中進(jìn)行測(cè)試,使用 Proxy 類的靜態(tài)方法 newProxyInstance() 生成一個(gè)代理對(duì)象并調(diào)用方法
public class MainTest {
public static void main(String[] args) {
Programmer programmer = new Programmer();
Worker worker = (Worker) Proxy.newProxyInstance(
programmer.getClass().getClassLoader(),
programmer.getClass().getInterfaces(),
new WorkHandler(programmer));
worker.work();
}
}2.5. 輸出結(jié)果
before work...
coding...
after work...
3. 源碼分析
既然是一個(gè)代理的過(guò)程,那么肯定存在原生對(duì)象和代理對(duì)象之分,下面我們查看源碼中是如何動(dòng)態(tài)的創(chuàng)建代理對(duì)象的過(guò)程。
上面例子中,創(chuàng)建代理對(duì)象調(diào)用的是 Proxy 類的靜態(tài)方法 newProxyInstance(),查看一下源碼
3.1. newProxyInstance() 方法
public class Proxy implements java.io.Serializable {
protected InvocationHandler h;
// 有參構(gòu)造器,參數(shù)是 InvocationHandler
protected Proxy(InvocationHandler h) {
Objects.requireNonNull(h);
this.h = h;
}
@CallerSensitive
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)
throws IllegalArgumentException {
// 如果h為空直接拋出空指針異常,之后所有的單純的判斷null并拋異常,都是此方法
Objects.requireNonNull(h);
// 拷貝類實(shí)現(xiàn)的所有接口
final Class<?>[] intfs = interfaces.clone();
// 獲取當(dāng)前系統(tǒng)安全接口
final SecurityManager sm = System.getSecurityManager();
if (sm != null) {
// Reflection.getCallerClass 返回調(diào)用該方法的方法的調(diào)用類;loader:接口的類加載器
// 進(jìn)行包訪問(wèn)權(quán)限、類加載器權(quán)限等檢查
checkProxyAccess(Reflection.getCallerClass(), loader, intfs);
}
// 查找或生成指定的代理類
Class<?> cl = getProxyClass0(loader, intfs);
// 用指定的調(diào)用處理程序調(diào)用它的構(gòu)造函數(shù)
try {
if (sm != null) {
checkNewProxyPermission(Reflection.getCallerClass(), cl);
}
// 獲取代理類的構(gòu)造函數(shù)對(duì)象
// constructorParams是類常量,作為代理類構(gòu)造函數(shù)的參數(shù)類型,常量定義如下:
// private static final Class<?>[] constructorParams = { InvocationHandler.class };
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;
}
});
}
// 根據(jù)代理類的構(gòu)造函數(shù)對(duì)象來(lái)創(chuàng)建需要返回的代理類對(duì)象
return cons.newInstance(new Object[]{h});
} // 省略 catch......
}
}
- 在 checkProxyAccess() 方法中,進(jìn)行參數(shù)驗(yàn)證
- 在 getProxyClass0() 方法中,生成一個(gè)代理類 Class 或?qū)ふ乙焉蛇^(guò)的代理類的緩存
- 通過(guò) getConstructor() 方法獲取生成的代理類的構(gòu)造方法
- 通過(guò) newInstance() 方法,生成最終的代理對(duì)象
上面這個(gè)過(guò)程中,獲取構(gòu)造方法和生成代理對(duì)象都是利用的 Java 中的反射機(jī)制,而需要重點(diǎn)看的是生成代理類的方法 getProxyClass0()
3.1.1. getProxyClass0() 方法
private static Class<?> getProxyClass0(ClassLoader loader,
Class<?>... interfaces) {
// 接口數(shù)不得超過(guò) 65535 個(gè),這么大,足夠使用的了
if (interfaces.length > 65535) {
throw new IllegalArgumentException("interface limit exceeded");
}
// 如果緩存中有代理類了直接返回,否則將由代理類工廠ProxyClassFactory創(chuàng)建代理類
return proxyClassCache.get(loader, interfaces);
}如果緩存中已經(jīng)存在了就直接從緩存中取,這里的 proxyClassCache 是一個(gè) WeakCache 類型,如果緩存中目標(biāo) classLoader 和接口數(shù)組對(duì)應(yīng)的類已經(jīng)存在,那么返回緩存的副本。如果沒(méi)有就使用 ProxyClassFactory 去生成 Class 對(duì)象
3.1.1.1. get() 方法
// key:類加載器;parameter:接口數(shù)組
public V get(K key, P parameter) {
// 檢查指定類型的對(duì)象引用不為空null。當(dāng)參數(shù)為null時(shí),拋出空指針異常
Objects.requireNonNull(parameter);
// 清除已經(jīng)被 GC 回收的弱引用
expungeStaleEntries();
// 將ClassLoader包裝成CacheKey, 作為一級(jí)緩存的key
Object cacheKey = CacheKey.valueOf(key, refQueue);
// 獲取得到二級(jí)緩存
ConcurrentMap<Object, Supplier<V>> valuesMap = map.get(cacheKey);
// 沒(méi)有獲取到對(duì)應(yīng)的值
if (valuesMap == null) {
ConcurrentMap<Object, Supplier<V>> oldValuesMap
= map.putIfAbsent(cacheKey,
valuesMap = new ConcurrentHashMap<>());
if (oldValuesMap != null) {
valuesMap = oldValuesMap;
}
}
// 根據(jù)代理類實(shí)現(xiàn)的接口數(shù)組來(lái)生成二級(jí)緩存key
Object subKey = Objects.requireNonNull(subKeyFactory.apply(key, parameter));
// 通過(guò)subKey獲取二級(jí)緩存值
Supplier<V> supplier = valuesMap.get(subKey);
Factory factory = null;
// 這個(gè)循環(huán)提供了輪詢機(jī)制, 如果條件為假就繼續(xù)重試直到條件為真為止
while (true) {
if (supplier != null) {
// 在這里supplier可能是一個(gè)Factory也可能會(huì)是一個(gè)CacheValue
// 在這里不作判斷, 而是在Supplier實(shí)現(xiàn)類的get方法里面進(jìn)行驗(yàn)證
V value = supplier.get();
if (value != null) {
return value;
}
}
if (factory == null) {
factory = new Factory(key, parameter, subKey, valuesMap);
}
if (supplier == null) {
// 到這里表明subKey沒(méi)有對(duì)應(yīng)的值, 就將factory作為subKey的值放入
supplier = valuesMap.putIfAbsent(subKey, factory);
if (supplier == null) {
supplier = factory;
}
// 否則, 可能期間有其他線程修改了值, 那么就不再繼續(xù)給subKey賦值, 而是取出來(lái)直接用
} else {
// 期間可能其他線程修改了值, 那么就將原先的值替換
if (valuesMap.replace(subKey, supplier, factory)) {
supplier = factory;
} else {
supplier = valuesMap.get(subKey);
}
}
}
}
很明顯,重點(diǎn)關(guān)注下面代碼
Object subKey = Objects.requireNonNull(subKeyFactory.apply(key, parameter));
3.1.1.1.1. apply() 方法
private static final class ProxyClassFactory
implements BiFunction<ClassLoader, Class<?>[], Class<?>> {
// 代理類的前綴名都以 $Proxy 開(kāi)始
private static final String proxyClassNamePrefix = "$Proxy";
// 使用唯一的編號(hào)給作為代理類名的一部分,如 $Proxy0,$Proxy1 等
private static final AtomicLong nextUniqueNumber = new AtomicLong();
@Override
public Class<?> apply(ClassLoader loader, Class<?>[] interfaces) {
Map<Class<?>, Boolean> interfaceSet = new IdentityHashMap<>(interfaces.length);
for (Class<?> intf : interfaces) {
// 驗(yàn)證指定的類加載器(loader)加載接口所得到的Class對(duì)象(interfaceClass)是否與intf對(duì)象相同
Class<?> interfaceClass = null;
try {
interfaceClass = Class.forName(intf.getName(), false, loader);
} catch (ClassNotFoundException e) {
}
if (interfaceClass != intf) {
throw new IllegalArgumentException(
intf + " is not visible from class loader");
}
// 驗(yàn)證該Class對(duì)象是不是接口
if (!interfaceClass.isInterface()) {
throw new IllegalArgumentException(
interfaceClass.getName() + " is not an interface");
}
// 驗(yàn)證該接口是否重復(fù)
if (interfaceSet.put(interfaceClass, Boolean.TRUE) != null) {
throw new IllegalArgumentException(
"repeated interface: " + interfaceClass.getName());
}
}
// 聲明代理類所在包
String proxyPkg = null;
int accessFlags = Modifier.PUBLIC | Modifier.FINAL;
// 驗(yàn)證所有非公共的接口在同一個(gè)包內(nèi);公共的就無(wú)需處理
for (Class<?> intf : interfaces) {
int flags = intf.getModifiers();
if (!Modifier.isPublic(flags)) {
accessFlags = Modifier.FINAL;
String name = intf.getName();
int n = name.lastIndexOf('.');
// 截取完整包名
String pkg = ((n == -1) ? "" : name.substring(0, n + 1));
if (proxyPkg == null) {
proxyPkg = pkg;
} else if (!pkg.equals(proxyPkg)) {
throw new IllegalArgumentException(
"non-public interfaces from different packages");
}
}
}
// 1.根據(jù)規(guī)則生成文件名
if (proxyPkg == null) {
/*如果都是public接口,那么生成的代理類就在com.sun.proxy包下如果報(bào) java.io.FileNotFoundException: com\sun\proxy\$Proxy0.class
(系統(tǒng)找不到指定的路徑。)的錯(cuò)誤,就先在你項(xiàng)目中創(chuàng)建com.sun.proxy路徑*/
proxyPkg = ReflectUtil.PROXY_PACKAGE + ".";
}
long num = nextUniqueNumber.getAndIncrement();
// 代理類的完全限定類名,如 com.sun.proxy.$Proxy0.calss
String proxyName = proxyPkg + proxyClassNamePrefix + num;
// 2.生成代理的字節(jié)碼數(shù)組
byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
proxyName, interfaces, accessFlags);
// 3.生成 Class
try {
return defineClass0(loader, proxyName,
proxyClassFile, 0, proxyClassFile.length);
} catch (ClassFormatError e) {
throw new IllegalArgumentException(e.toString());
}
}
}
在 apply() 方法中,主要做了下面 3 件事
- 根據(jù)規(guī)則生成文件名
- 利用 ProxyGenerator.generateProxyClass() 方法生成代理的字節(jié)碼數(shù)組
- 調(diào)用方法 defineClass0() 生成 Class
3.1.2. getConstructor() 和 newInstance() 方法
返回代理類的 Class 后的流程,獲取構(gòu)造方法和生成代理對(duì)象都是利用的 Java 中的反射機(jī)制
4. 代理對(duì)象長(zhǎng)啥樣
4.1. 代理對(duì)象長(zhǎng)啥樣
創(chuàng)建代理對(duì)象流程的源碼分析完了,我們可以先通過(guò) debug 來(lái)看看上面生成的這個(gè)代理對(duì)象究竟是個(gè)什么

和源碼中看到的規(guī)則一樣,是一個(gè) Class 為 $Proxy0 的對(duì)象。再看一下代理對(duì)象的 Class 的詳細(xì)信息

類的全限定類名是 com.sun.proxy.$Proxy0,在上面我們提到過(guò),這個(gè)類是在運(yùn)行過(guò)程中動(dòng)態(tài)生成的
4.2. $Proxy0 反編譯
看一下反編譯后 $Proxy0.java 文件的內(nèi)容,下面的代碼中,我只保留了核心部分,省略了無(wú)關(guān)緊要的 equals()、toString()、hashCode() 方法的定義
public final class $Proxy0 extends Proxy implements Worker{
public $Proxy0(InvocationHandler invocationhandler){
super(invocationhandler);
}
public final void work(){
try{
super.h.invoke(this, m3, null);
return;
}catch(Error _ex) { }
catch(Throwable throwable){
throw new UndeclaredThrowableException(throwable);
}
}
private static Method m3;
static {
try{
m3 = Class.forName("com.hydra.test.Worker").getMethod("work", new Class[0]);
//省略其他Method
}//省略catch
}
}這個(gè)臨時(shí)生成的代理類 $Proxy0 中主要做了下面的幾件事
- 在這個(gè)類的靜態(tài)代碼塊中,通過(guò) 反射機(jī)制 初始化了多個(gè)靜態(tài)方法 Method 變量,除了接口中的方法還有 equals()、toString()、hashCode() 這三個(gè)方法
- 代理類 $Proxy0 繼承了父類 Proxy,在其實(shí)例化的過(guò)程中會(huì)調(diào)用父類的構(gòu)造器,而父類 Proxy 中的構(gòu)造器中傳入的 InvocationHandler 對(duì)象實(shí)際上是我們自定義的 WorkHandler 的實(shí)例。此時(shí)就可以調(diào)用 WorkHandler 的 invoke() 方法了
- 同時(shí),代理類 $Proxy0 也實(shí)現(xiàn)了自定義的接口 Worker,并重寫(xiě)了 work() 方法,在 work()方法內(nèi)又調(diào)用了 InvocationHandler 的 invoke() 方法,也就是實(shí)際上調(diào)用了 WorkHandler 的 invoke() 方法
到這里,整體的流程就分析完了,我們可以用一張圖來(lái)簡(jiǎn)要總結(jié)上面的過(guò)程

5. JDK 動(dòng)態(tài)代理為什么要有接口
其實(shí)如果不看上面的分析,我們也應(yīng)該知道,要擴(kuò)展一個(gè)類有常見(jiàn)的兩種方式,繼承父類或?qū)崿F(xiàn)接口。這兩種方式都允許我們對(duì)方法的邏輯進(jìn)行增強(qiáng),但現(xiàn)在不是由我們自己來(lái)重寫(xiě)方法,而是要想辦法讓 JVM 去調(diào)用 InvocationHandler 中的 invoke() 方法,也就是說(shuō)代理類需要和兩個(gè)東西關(guān)聯(lián)在一起
- 被代理類
- InvocationHandler
而 JDK 動(dòng)態(tài)代理處理這個(gè)問(wèn)題的方式是選擇繼承父類 Proxy,并把 InvocationHandler 保存在父類的對(duì)象中
public class Proxy implements java.io.Serializable {
protected InvocationHandler h;
protected Proxy(InvocationHandler h) {
Objects.requireNonNull(h);
this.h = h;
}
// ......
}通過(guò)父類 Proxy 的構(gòu)造方法,保存了創(chuàng)建代理對(duì)象過(guò)程中傳進(jìn)來(lái)的 InvocationHandler 的實(shí)例,使用 protected 修飾保證了它可以在子類中被訪問(wèn)和使用。
但是同時(shí),因?yàn)?Java 是單繼承的,因此在代理類 $Proxy0 繼承了 Proxy 后,其只能通過(guò)實(shí)現(xiàn)目標(biāo)接口的方式來(lái)實(shí)現(xiàn)方法的擴(kuò)展,達(dá)到我們?cè)鰪?qiáng)目標(biāo)方法邏輯的目的
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
相關(guān)文章
Gradle構(gòu)建多模塊項(xiàng)目的方法步驟
這篇文章主要介紹了Gradle構(gòu)建多模塊項(xiàng)目的方法步驟,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2018-05-05
關(guān)于JVM默認(rèn)堆內(nèi)存大小問(wèn)題
這篇文章主要介紹了關(guān)于JVM默認(rèn)堆內(nèi)存大小問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-02-02
java用split分割字符串的一個(gè)有趣現(xiàn)象
最近在項(xiàng)目中使用了java中的split分割字符串,發(fā)現(xiàn)了一個(gè)bug,充分了展示了自己對(duì)java底層的認(rèn)知有很多的不足和欠缺。下面將這次的經(jīng)過(guò)總結(jié)出來(lái)分享給大家,有需要的朋友們可以參考借鑒,下面來(lái)一起看看吧。2016-12-12
Spring之InitializingBean接口和DisposableBean接口的使用
這篇文章主要介紹了Spring之InitializingBean接口和DisposableBean接口的使用方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-01-01
Springboot創(chuàng)建項(xiàng)目的圖文教程(idea版本)
這篇文章主要介紹了Springboot創(chuàng)建項(xiàng)目的圖文教程(idea版本),本文通過(guò)圖文并茂的形式給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-06-06

