動態代理的簡介
Java動態代理機制的出現,使得Java開發人員不用手工編寫代理類,只要簡單地指定一組接口及委托類對象,便能動態地獲得代理類。代理類會負責將所有的方法調用分派到委托對象上反射執行,在分派執行的過程中,開發人員還可以按需調整委托類對象及其功能,這是一套非常靈活有彈性的代理框架。Java動態代理實際上通過反射技術,把代理對象和被代理對象(真實對象)的代理關系建立延遲到程序運行之后,動態創建新的代理類去完成對真實對象的代理操作(可以改變原來真實對象的方法行為),這一點成為了當前主流的AOP框架和延遲加載功能的基礎。本文在查看和編寫動態代理相關的代碼使用的是JDK11,不過JDK動態代理相關的功能和接口已經相對穩定,不必擔心JDK版本升級帶來的兼容性問題,但是需要注意由於JDK9引入了模塊概念,動態代理的源碼也有不少的改動。下文先介紹設計模式中的代理模式,接着會分析JDK動態代理的核心類庫、流程和機制,最后分析其底層源碼級別實現。
設計模式中的代理模式
代理模式是一種常用的設計模式,其目的就是為其他對象提供一個代理以控制對某個對象的訪問。代理類負責為委托類預處理消息,過濾消息並轉發消息,以及進行消息被委托類執行后的后續處理。
代理模式主要包括三種角色:
- Subject抽象主題角色:一般定義為抽象類或者接口,是作為功能的定義,提供一系列抽象的功能方法。
- RealSubject具體(真實)主題角色:一般稱為被委托角色或者被代理角色,它是Subject的一個具體實現。
- ProxySubject代理主題角色:一般稱為委托角色或者代理角色,一般ProxySubject也實現(或者繼承)Subject,接收一個具體的Subject實例RealSubject,在RealSubject處理前后做預定義或者后置操作,甚至可以直接忽略RealSubject原來的方法。
把上面的類圖編寫成代碼如下:
public interface Subject {
void doSomething();
}
public class RealSubject implements Subject {
@Override
public void doSomething() {
System.out.println("RealSubject doSomething...");
}
}
public class ProxySubject implements Subject {
private final Subject subject;
public ProxySubject(Subject subject) {
this.subject = subject;
}
@Override
public void doSomething() {
subject.doSomething();
doOtherThing();
}
private void doOtherThing() {
System.out.println("ProxySubject doOtherThing...");
}
}
public class Client {
public static void main(String[] args) throws Exception {
Subject subject = new RealSubject();
ProxySubject proxySubject = new ProxySubject(subject);
proxySubject.doSomething();
}
}
運行Client#main()
輸出:
RealSubject doSomething...
ProxySubject doOtherThing...
代理模式在日常的場景中也經常碰到,比較常見的一個場景就是游戲代練,套進去上面的代碼可以寫個比較生動的例子:
public interface Player {
void playGame();
}
public class I implements Player {
@Override
public void playGame() {
System.out.println("操作Throwable游戲角色打怪升級");
}
}
public class ProxyPlayer implements Player {
private final Player player;
public ProxyPlayer(Player player) {
this.player = player;
}
@Override
public void playGame() {
login();
this.player.playGame();
logout();
}
private void login() {
System.out.println("登錄Throwable游戲角色");
}
private void logout() {
System.out.println("退出Throwable游戲角色");
}
}
代理模式有幾個比較大的優點:
- 職責清晰:也就是真實主題角色只需要實現具體的邏輯,不需關注代理類的職責,而代理類也只需要處理預處理和后置的邏輯,類的職責分明。
- 高擴展性:由於職責分明,也就是真實主題角色可以隨時修改實現,這樣就能通過更新或者替換真實主題的實現並且不改變代理主題角色的情況下改變具體功能。
- 高靈活性:主要體現在后面提到的動態代理。
JDK動態代理的核心API
JDK動態代理提供外部使用的主要依賴兩個類:
java.lang.reflect.Proxy
:可以理解為代理類的工廠類(其實也是父類,見下文)。java.lang.reflect.InvocationHandler
:代理實例需要實現的調用處理器接口。
Proxy
java.lang.reflect.Proxy
是JDK動態代理的核心類,它的核心功能是提供靜態方法來為一組接口動態地生成代理類並且返回代理實例對象,類似於代理類實例的工廠類。java.lang.reflect.Proxy
主要提供四個public靜態方法:
// 方法 1: 該方法用於獲取指定代理對象所關聯的調用處理器
public static InvocationHandler getInvocationHandler(Object proxy)
// 方法 2:該方法用於獲取關聯於指定類裝載器和一組接口的動態代理類的類對象
public static Class<?> getProxyClass(ClassLoader loader, Class<?>[] interfaces)
// 方法 3:該方法用於判斷指定類對象是否是一個動態代理類
public static boolean isProxyClass(Class<?> cl)
// 方法 4:該方法用於為指定類裝載器、一組接口及調用處理器生成動態代理類實例
public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)
InvocationHandler getInvocationHandler(Object proxy)
:通過制定的代理類實例查找它關聯的調用處理器實例。Class<?> getProxyClass(ClassLoader loader, Class<?>[] interfaces)
:用於獲取關聯於指定類裝載器和一組接口的動態代理類的類對象,也就是獲取$ProxyXXX
的類型,此方法在JDK9以后標記為過期,原因是:在命名模塊中生成的代理類是封閉的,模塊外的代碼無法訪問這些類(違反模塊規則調用了會拋異常)。boolean isProxyClass(Class<?> cl)
:用於判斷指定類是否是一個動態代理類。Object newProxyInstance(ClassLoader loader, Class[] interfaces, InvocationHandler h)
:這個是JDK動態代理最核心的方法,用於為指定類裝載器、一組接口及調用處理器生成動態代理類實例,也就是生成$ProxyXXX
的實例。此方法需要指定類加載器java.lang.ClassLoader
,Proxy靜態方法生成動態代理類同樣需要通過類裝載器來進行裝載才能使用,它與普通類的唯一區別就是其字節碼是在運行時動態生成的而非預存在於任何一個.class文件中。interfaces是Class數組,也就是需要使用InvocationHandler進行代理訪問的接口類型數組,這里的h參數就是調用處理器的實例。
InvocationHandler
java.lang.reflect.InvocationHandler
是調用處理器接口,它自定義了一個invoke
方法,用於集中處理在動態代理類對象上的方法調用,通常在該方法中實現對委托類的代理訪問。
public interface InvocationHandler {
Object invoke(Object proxy, Method method, Object[] args) throws Throwable;
}
參數說明:
- proxy:Object類型,此參數即是代理類實例,也就是
$ProxyXXX
的實例。 - method:
java.lang.reflect.Method
類型,被調用的方法的實例。 - args:Object[]類型,被調用方法的參數數組。
實現java.lang.reflect.InvocationHandler
接口,通過實現invoke
方法即可添加代理訪問的邏輯,在這個邏輯代碼塊中除了可以調用委托類的方法,還可以織入額外的自定義邏輯,AOP就是這樣實現的。
JDK動態代理的流程
JDK動態代理的使用流程如下:
- 1、通過實現
java.lang.reflect.InvocationHandler
接口創建自定義的調用處理器。 - 2、通過為
java.lang.reflect.Proxy
類指定ClassLoader
對象和一組interface來創建動態代理類。 - 3、通過反射機制獲得動態代理類的構造函數,其唯一參數類型是調用處理器接口類型。
- 4、通過構造函數創建動態代理類實例,構造時調用處理器對象作為參數被傳入。
偽代碼如下:
// InvocationHandlerImpl 實現了 InvocationHandler 接口,並能實現方法調用從代理類到委托類的分派轉發
// 其內部通常包含指向委托類實例的引用,用於真正執行分派轉發過來的方法調用
InvocationHandler handler = new InvocationHandlerImpl(..);
// 通過Proxy為包括Interface接口在內的一組接口動態創建代理類的類對象
Class clazz = Proxy.getProxyClass(classLoader, new Class[] { Interface.class, ... });
// 通過反射從生成的類對象獲得構造函數對象
Constructor constructor = clazz.getConstructor(new Class[] { InvocationHandler.class });
// 通過構造函數對象創建動態代理類實例
Interface Proxy = (Interface)constructor.newInstance(new Object[] { handler });
上面的過程比較復雜,可以進行精簡。簡化后的偽代碼如下:
// InvocationHandlerImpl實現了InvocationHandler 接口,並能實現方法調用從代理類到委托類的分派轉發
InvocationHandler handler = new InvocationHandlerImpl(..);
// 通過Proxy直接創建動態代理類實例
Interface proxy = (Interface) Proxy.newProxyInstance(classLoader, new Class[] { Interface.class }, handler);
JDK動態代理的機制
首先是JDK動態代理生成的代理類本身的特點:
- 1、包(或者JDK9引入的模塊):如果所代理的接口都是public的,那么它將被定義在包
com.sun.proxy
;如果所代理的接口中有非public的接口(因為接口不能被定義為protect或private,所以除public之外就是默認的package訪問級別,修飾符為default),那么它將被定義在該接口所在包(假設代理了throwable.club
包中的某非public接口A,那么新生成的代理類所在的包就是throwable.club
),值得注意的是,如果接口數組中存在非public的接口,那么它們必須在同一個包路徑下,否則會拋異常。這樣設計的目的是為了最大程度的保證動態代理類不會因為包管理的問題而無法被成功定義並訪問。 - 2、類修飾符:該代理類具有final和public修飾符,意味着它可以被所有的類訪問,但是不能被再度繼承。
- 3、類名:代理類名稱格式是
$ProxyN
,其中N是一個逐一遞增的阿拉伯數字,代表java.lang.reflect.Proxy
類第N次生成的動態代理類,值得注意的一點是,並不是每次調用Proxy的靜態方法創建動態代理類都會使得N值增加,原因是如果對同一組接口(包括接口排列的順序相同)試圖重復創建動態代理類,它會從緩存中獲取先前已經創建好的代理類的類對象,而不會再嘗試去創建一個全新的代理類,這樣可以節省不必要的代碼重復生成,提高了代理類的創建效率。 - 4、類繼承關系:代理類的繼承關系圖如下:
由圖可知,java.lang.reflect.Proxy
類是代理類的父類,這個規則適用於所有由java.lang.reflect.Proxy
創建的動態代理類。而且該類還實現了其所代理的一組接口,這就是為什么它能夠被安全地類型轉換到其所代理的某接口的根本原因。
代理類實例的特點
每個代理類實例都會關聯一個調用處理器對象,可以通過java.lang.reflect.Proxy
提供的靜態方法getInvocationHandler()
去獲得代理類實例的調用處理器對象。在代理類實例上調用其代理的接口中所聲明的方法時,這些方法最終都會由調用處理器的 invoke 方法執行,此外,值得注意的是,代理類的根類java.lang.Object
中有三個方法也同樣會被分派到調用處理器的invoke
方法執行,它們是hashCode
、equals
和toString
,可能的原因有:
- 一、因為這些方法為public且非final類型,能夠被代理類覆蓋。
- 二、因為這些方法往往呈現出一個類的某種特征屬性,具有一定的區分度,所以為了保證代理類與委托類對外的一致性,這三個方法也應該被分派到委托類執行。當代理的一組接口有重復聲明的方法且該方法被調用時,代理類總是從排在最前面的接口中獲取方法對象並分派給調用處理器,而無論代理類實例是否正在以該接口(或繼承於該接口的某子接口)的形式被外部引用,因為在代理類內部無法區分其當前的被引用類型。
被代理的一組接口的特點
首先,要注意不能有重復的接口,以避免動態代理類代碼生成時的編譯錯誤。其次,這些接口對於類裝載器必須可見,否則類裝載器將無法鏈接它們,將會導致類定義失敗。再次,需被代理的所有非public的接口必須在同一個包中,否則代理類生成也會失敗。最后,接口的數目不能超過65535,這是JVM設定的限制,這一點在代理類生成的時候也做了判斷。
異常處理
從調用處理器接口聲明的方法中可以看到理論上它能夠拋出任何類型的異常,因為所有的異常都繼承於Throwable接口,但事實是否如此呢?答案是否定的,原因是我們必須遵守一個繼承原則:即子類覆蓋父類或實現父接口的方法時,拋出的異常必須在原方法支持的異常列表之內。所以雖然調用處理器理論上講能夠,但實際上往往受限制,除非父接口中的方法支持拋Throwable異常。那么如果在invoke方法中的確產生了接口方法聲明中不支持的異常,那將如何呢?放心,Jdk動態代理類已經為我們設計好了解決方法:它將會拋出UndeclaredThrowableException
異常。這個異常是一個RuntimeException
類型,所以不會引起編譯錯誤。通過該異常的getCause
方法,還可以獲得原來那個不受支持的異常對象,以便於錯誤診斷。
JDK動態代理源碼分析
因為JDK動態代理核心邏輯都在java.lang.reflect.Proxy
類中,下面簡單分析一下這個類的源碼。先看Proxy
類中的幾個重要的靜態變量:
// 接口組中接口都為為public時候代理類創建的包路徑:com.sun.proxy
private static final String PROXY_PACKAGE_PREFIX = ReflectUtil.PROXY_PACKAGE;
// 代理類的構造方法參數類型數組,可見代理類的構造參數只有InvocationHandler類型
private static final Class<?>[] constructorParams = { InvocationHandler.class };
// 緩存了所有已經調用過setAccessible(true)的代理類的構造(Constructor)實例
private static final ClassLoaderValue<Constructor<?>> proxyCache = new ClassLoaderValue<>();
這里注意到ClassLoaderValue
,下文會調用到它的一個很復雜的調用鏈:
//intf是Class<?>類型
//loader是類加載器實例
return proxyCache.sub(intf).computeIfAbsent(
loader,
(ld, clv) -> new ProxyBuilder(ld, clv.key()).build()
);
public V computeIfAbsent(ClassLoader cl,
BiFunction<? super ClassLoader,? super CLV,? extends V> mappingFunction)
throws IllegalStateException {
上面的computeIfAbsent
中使用了函數式接口和Lambda表達式,如果Lambda表達式玩的比較熟練看起來應該沒問題,它的功能可以解讀為:通過接口類型和類加載器實例計算通過接口類型和類加載器實例構建ProxyBuilder
實例並且調用ProxyBuilder#build()
得到的結果,如果結果已經存在則直接返回緩存。其實computeIfAbsent
在Map
接口中也定義了同樣的方法,功能是相似的。
接着看Proxy
的構造函數:
protected InvocationHandler h;
private Proxy() {
}
protected Proxy(InvocationHandler h) {
Objects.requireNonNull(h);
this.h = h;
}
到此可以明確一點,既然所有動態代理類都是java.lang.reflect.Proxy
的子類,那么它們一定具備一個包含InvocationHandler
參數的構造器。接着查看``方法的源碼:
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h) {
// 空判斷
Objects.requireNonNull(h);
// 當前調用類獲取
final Class<?> caller = System.getSecurityManager() == null
? null
: Reflection.getCallerClass();
// 獲取代理類的構造器實例
Constructor<?> cons = getProxyConstructor(caller, loader, interfaces);
// 生成代理實例
return newProxyInstance(caller, cons, h);
}
先看getProxyConstructor
方法:
private static Constructor<?> getProxyConstructor(Class<?> caller,
ClassLoader loader,
Class<?>... interfaces){
// 這里需要區分代理接口數組中只有單個接口和多個接口的邏輯
// 而基本的邏輯都是先校驗當前調用類的權限,后續獲取Constructor實例委托到ProxyBuilder
if (interfaces.length == 1) {
Class<?> intf = interfaces[0];
if (caller != null) {
checkProxyAccess(caller, loader, intf);
}
return proxyCache.sub(intf).computeIfAbsent(
loader,
(ld, clv) -> new ProxyBuilder(ld, clv.key()).build()
);
} else {
// 接口克隆
final Class<?>[] intfsArray = interfaces.clone();
if (caller != null) {
checkProxyAccess(caller, loader, intfsArray);
}
final List<Class<?>> intfs = Arrays.asList(intfsArray);
return proxyCache.sub(intfs).computeIfAbsent(
loader,
(ld, clv) -> new ProxyBuilder(ld, clv.key()).build()
);
}
}
可以明確,核心的邏輯都交給了Proxy
的內部類ProxyBuilder
完成,先看ProxyBuilder
的靜態成員變量:
// Unsafe實例
private static final Unsafe UNSAFE = Unsafe.getUnsafe();
// 代理類的簡單類名的前置字符串
private static final String proxyClassNamePrefix = "$Proxy";
// 用於生成下一個代理類的數字計數器,記住它是靜態的
private static final AtomicLong nextUniqueNumber = new AtomicLong();
// 記錄了已經生成的代理類-Boolean的映射,已經生成過對應代理類則記錄為true
private static final ClassLoaderValue<Boolean> reverseProxyCache = new ClassLoaderValue<>();
// 單個代理接口的情況,其實也是把接口轉換為List
ProxyBuilder(ClassLoader loader, Class<?> intf) {
this(loader, Collections.singletonList(intf));
}
// 多個代理接口的情況
ProxyBuilder(ClassLoader loader, List<Class<?>> interfaces) {
// 通過JVM參數強制關閉動態代理功能則拋出異常
if (!VM.isModuleSystemInited()) {
throw new InternalError("Proxy is not supported until "
+ "module system is fully initialized");
}
// 代理接口數量不能超過65535,也就是最多代理65535個接口
if (interfaces.size() > 65535) {
throw new IllegalArgumentException("interface limit exceeded: "
+ interfaces.size());
}
// 收集接口數組中所有接口的非靜態方法的返回值類型、共享(shared)參數類型和共享(shared)異常類型,注釋說是收集代理接口的方法簽名
Set<Class<?>> refTypes = referencedTypes(loader, interfaces);
// 確保上一步得到的代理接口方法簽名的類型都是"可見(其實就是類型都存在)"的,通過遍歷調用Class.forName(type.getName(), false, ld)去判斷
validateProxyInterfaces(loader, interfaces, refTypes);
this.interfaces = interfaces;
// 獲取代理類最終生成的模塊,規則如下:
// 1、所有代理接口的修飾符都為public,接口所在模塊都能公開訪問,則返回unnamed模塊
// 2、如果有任意的代理接口是包私有,則返回該包所在的模塊 、
// 3、所有代理接口的修飾符都為public,有任意至少一個接口所在模塊不能公開訪問,則返回該不能公開訪問的模塊,
this.module = mapToModule(loader, interfaces, refTypes);
assert getLoader(module) == loader;
}
一個構造器處理的邏輯也是相對復雜,主要是因為引入模塊管理的概念,接着看ProxyBuilder#build()
的源碼:
Constructor<?> build() {
// 定義代理類,實際上是動態生成代理類字節碼和緩存它的類型的過程
Class<?> proxyClass = defineProxyClass(module, interfaces);
final Constructor<?> cons;
try {
// 返回代理類的構造
cons = proxyClass.getConstructor(constructorParams);
} catch (NoSuchMethodException e) {
throw new InternalError(e.toString(), e);
}
AccessController.doPrivileged(new PrivilegedAction<Void>() {
public Void run() {
cons.setAccessible(true);
return null;
}
});
return cons;
}
最后到邏輯最復雜的代理類的生成過程ProxyBuilder#defineProxyClass()
:
private static Class<?> defineProxyClass(Module m, List<Class<?>> interfaces) {
String proxyPkg = null; // package to define proxy class in
int accessFlags = Modifier.PUBLIC | Modifier.FINAL;
// 這里就是定義代理類包路徑的邏輯,規則如下:
// 1、代理接口數組所有接口都是public修飾,則代理類包路徑為com.sun.proxy
// 2、代理接口數組有任意接口是包私有的,則代理類包路徑為該私有包的路徑
for (Class<?> intf : interfaces) {
int flags = intf.getModifiers();
if (!Modifier.isPublic(flags)) {
accessFlags = Modifier.FINAL; // non-public, final
String pkg = intf.getPackageName();
if (proxyPkg == null) {
proxyPkg = pkg;
} else if (!pkg.equals(proxyPkg)) {
throw new IllegalArgumentException(
"non-public interfaces from different packages");
}
}
}
// 下面幾個if都是包路徑的合法性判斷
if (proxyPkg == null) {
// all proxy interfaces are public
proxyPkg = m.isNamed() ? PROXY_PACKAGE_PREFIX + "." + m.getName()
: PROXY_PACKAGE_PREFIX;
} else if (proxyPkg.isEmpty() && m.isNamed()) {
throw new IllegalArgumentException(
"Unnamed package cannot be added to " + m);
}
if (m.isNamed()) {
if (!m.getDescriptor().packages().contains(proxyPkg)) {
throw new InternalError(proxyPkg + " not exist in " + m.getName());
}
}
// 計數器加1返回新的計數值
long num = nextUniqueNumber.getAndIncrement();
// 生成代理類全類名,一個常見的格式是:com.sun.proxy.$Proxy1
String proxyName = proxyPkg.isEmpty()
? proxyClassNamePrefix + num
: proxyPkg + "." + proxyClassNamePrefix + num;
ClassLoader loader = getLoader(m);
trace(proxyName, m, loader, interfaces);
// 動態生成代理類字節碼字節數組
byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
proxyName, interfaces.toArray(EMPTY_CLASS_ARRAY), accessFlags);
try {
// 通過Unsafe定義代理類-這里是通過字節碼定義新的類
Class<?> pc = UNSAFE.defineClass(proxyName, proxyClassFile,
0, proxyClassFile.length,
loader, null);
// 緩存代理類已經生成過的標記
reverseProxyCache.sub(pc).putIfAbsent(loader, Boolean.TRUE);
return pc;
} catch (ClassFormatError e) {
/*
* A ClassFormatError here means that (barring bugs in the
* proxy class generation code) there was some other
* invalid aspect of the arguments supplied to the proxy
* class creation (such as virtual machine limitations
* exceeded).
*/
throw new IllegalArgumentException(e.toString());
}
}
到這一步為止,代理類的生成過程已經大致分析完畢,ProxyGenerator
中涉及到大量字節碼操作,這里就不深入分析了。那么回到最前面的方法,得到代理類和它的構造實例,接着就可以生成代理實例:
private static Object newProxyInstance(Class<?> caller, // null if no SecurityManager
Constructor<?> cons,
InvocationHandler h) {
try {
if (caller != null) {
checkNewProxyPermission(caller, cons.getDeclaringClass());
}
// 這里簡單反射調用Constructor#newInstance(h)
return cons.newInstance(new Object[]{h});
} catch (IllegalAccessException | InstantiationException e) {
throw new InternalError(e.toString(), e);
} catch (InvocationTargetException e) {
Throwable t = e.getCause();
if (t instanceof RuntimeException) {
throw (RuntimeException) t;
} else {
throw new InternalError(t.toString(), t);
}
}
}
小結一下:
- 接口數組中所有接口元素的類修飾符最好一致為public。如果接口數組中存在非default修飾的接口元素,那么接口數組中的所有接口類都要放在同一個包下,並且都要使用default修飾。
- 很少情況下我們修改接口的修飾符,默認為public,那么所有代理類的包路徑都是
com.sun.proxy
,全類名是:com.sun.proxy.$ProxyN
。 - 代理接口數量不能超過65535。
JDK動態代理類的源代碼
前面已經分析完了代理類的生成過程,這里舉個簡單的使用例子,並且觀察生成的動態代理類的源代碼。
使用例子:
// 接口
public interface Simple {
void sayHello(String name);
}
// 接口實現
public class DefaultSimple implements Simple {
@Override
public void sayHello(String name) {
System.out.println(String.format("%s say hello!", name));
}
}
// 場景類
public class Main {
public static void main(String[] args) throws Exception {
Simple simple = new DefaultSimple();
Object target = Proxy.newProxyInstance(Main.class.getClassLoader(), new Class[]{Simple.class}, new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("Before say hello...");
method.invoke(simple, args);
System.out.println("After say hello...");
return null;
}
});
Simple proxy = (Simple) target;
proxy.sayHello("throwable");
}
}
調用后輸出:
Before say hello...
throwable say hello!
After say hello...
可以看到,我們在被代理類DefaultSimple
實例的方法調用前后織入了自定義的邏輯,這就是通過JDK動態代理實現AOP的底層原理。在JDK8中可以直接使用sun.misc.ProxyGenerator
去輸出代理類的class文件,但是JDK11中這個代理類生成器已經變成java.lang.reflect.ProxyGenerator
,並且這個類是包私有的,我們無法使用,但是它提供了jdk.proxy.ProxyGenerator.saveGeneratedFiles
這個VM參數讓我們可以保存代理類的class文件:
# JVM參數
-Djdk.proxy.ProxyGenerator.saveGeneratedFiles=true
配置好VM參數后,再次調用mian方法就能看到在項目的頂層包路徑下看到對應的類com.sun.proxy.$Proxy0
,目前從java.lang.reflect.ProxyGenerator
源碼看無法控制代理類文件的輸出路徑,生成的代理類內容如下:
public final class $Proxy0 extends Proxy implements Simple {
private static Method m1;
private static Method m3;
private static Method m2;
private static Method m0;
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});
} catch (RuntimeException | Error var3) {
throw var3;
} catch (Throwable var4) {
throw new UndeclaredThrowableException(var4);
}
}
public final void sayHello(String var1) throws {
try {
super.h.invoke(this, m3, new Object[]{var1});
} catch (RuntimeException | Error var3) {
throw var3;
} catch (Throwable var4) {
throw new UndeclaredThrowableException(var4);
}
}
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);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
static {
try {
m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
m3 = Class.forName("club.throwable.jdk.sample.reflection.proxy.Simple").getMethod("sayHello", Class.forName("java.lang.String"));
m2 = Class.forName("java.lang.Object").getMethod("toString");
m0 = Class.forName("java.lang.Object").getMethod("hashCode");
} catch (NoSuchMethodException var2) {
throw new NoSuchMethodError(var2.getMessage());
} catch (ClassNotFoundException var3) {
throw new NoClassDefFoundError(var3.getMessage());
}
}
}
代理類的代碼比較簡單,有如下幾個特點:
- 1、代理類繼承於
java.lang.reflect.Proxy
,實現了接口數組中的接口元素類,構造函數只有一個InvocationHandler
類型的參數。 - 2、接口中的所有被代理方法包括
equals
、toString
、hashCode
都建立了一個對應的Method私有靜態實例,在最后面的靜態代碼塊中實例化。 - 3、所有代理方法都是用public final修飾,也就是代理類中的代理方法是不能覆蓋的。
- 4、所有代理方法都是通過
InvocationHandler
實例的invoke
方法進行調用的,記得第一個參數是代理類實例本身,如果用了在InvocationHandler#invoke()
方法實現過程中使用了這個參數有可能造成死循環。
小結
誠然,Proxy已經設計得非常優美,但是還是有一點點小小的遺憾之處,那就是它始終無法擺脫僅支持interface代理的桎梏,因為它的設計注定了這個遺憾。回想一下那些動態生成的代理類的繼承關系圖,它們已經注定有一個共同的父類叫Proxy。Java的單繼承機制注定了這些動態代理類們無法實現對class的動態代理(所以只能代理接口,實際上是基於反射對方法級別的邏輯進行編織)。有很多條理由,可以否定對class代理的必要性,但是同樣有一些理由,相信支持class動態代理會更美好。但是,不完美並不等於不偉大,偉大是一種本質,JDK動態代理就是佐例。
參考資料:
- Java動態代理機制分析及擴展-第1部分
- Java動態代理機制分析及擴展-第2部分
- JDK11相關源碼
個人博客
(本文完 e-20181208 c-3-d)
技術公眾號(《Throwable文摘》),不定期推送筆者原創技術文章(絕不抄襲或者轉載):
娛樂公眾號(《天天沙雕》),甄選奇趣沙雕圖文和視頻不定期推送,緩解生活工作壓力: