JDK動態代理 原始碼解析
Java 靜態代理 靜態代理通常用於對原有業務邏輯的擴充。比如持有二方包的某個類,並呼叫了其中的某些方法。然後出於某種原因,比如記錄日誌、列印方法執行時間,但是又不好將這些邏輯寫入二方包的方法裡。所以可以建立一個代理類實現和二方方法相同的方法,通過讓代理類持有真實物件,然後在原始碼中呼叫代理類方法,來達到新增我們需要業務邏輯的目的。
這其實也就是代理模式的一種實現,通過對真實物件的封裝,來實現擴充套件性。
一個典型的代理模式通常有三個角色,這裡稱之為**代理三要素**
共同介面 public interface Action { public void doSomething(); } 真實物件 public class RealObject implements Action{ public void doSomething() { System.out.println("do something"); } } 代理物件 public class Proxy implements Action { private Action realObject; public Proxy(Action realObject) { this.realObject = realObject; } public void doSomething() { System.out.println("proxy do"); realObject.doSomething(); } } 執行程式碼
Proxy proxy = new Proxy(new RealObject()); proxy.doSomething();
這種代理模式也最為簡單,就是通過proxy持有realObject的引用,並進行一層封裝。
靜態代理的優點和缺點 先看看代理模式的優點: 擴充套件原功能,不侵入原始碼。
再看看這種代理模式的缺點:
假如有這樣一個需求,有十個不同的RealObject,同時我們要去代理的方法是不同的,比要代理方法:doSomething、doAnotherThing、doTwoAnotherThing,新增代理前,原始碼可能是這樣的:
realObject.doSomething(); realObject1.doAnotherThing(); realObject2.doTwoAnother(); 為了解決這個問題,我們有方案一:
為這些方法建立不同的代理類,代理後的程式碼是這樣的:
proxy.doSomething(); proxy1.doAnotherThing(); proxy2.doTwoAnother(); 當然,也有方案二:
通過建立一個proxy,持有不同的realObject,實現Action1、Action2、Action3介面,來讓程式碼變成這樣:
proxy.doSomething(); proxy.doAnotherThing(); proxy.doTwoAnother(); 於是你的代理模型會變成這樣:
毫無疑問,僅僅為了擴充套件同樣的功能,在方案一種,我們會重複建立多個邏輯相同,僅僅RealObject引用不同的Proxy。
而在方案二中,會導致proxy的膨脹,而且這種膨脹往往是無意義的。此外,假如方法簽名是相同的,更需要在呼叫的時候引入額外的判斷邏輯。
java 動態代理
搞清楚靜態代理的缺點十分重要,因為動態代理的目的就是為了解決靜態代理的缺點。通過使用動態代理,我們可以通過在執行時,動態生成一個持有RealObject、並實現代理介面的Proxy,同時注入我們相同的擴充套件邏輯。哪怕你要代理的RealObject是不同的物件,甚至代理不同的方法,都可以動過動態代理,來擴充套件功能。
簡單理解,動態代理就是我們上面提到的方案一,只不過這些proxy的建立都是自動的並且是在執行期生成的。
動態代理基本用法 使用動態代理,需要將要擴充套件的功能寫在一個InvocationHandler 實現類裡:
public class DynamicProxyHandler implements InvocationHandler { private Object realObject; public DynamicProxyHandler(Object realObject) { this.realObject = realObject; } public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { //代理擴充套件邏輯 System.out.println("proxy do"); return method.invoke(realObject, args); } } 這個Handler中的invoke方法中實現了代理類要擴充套件的公共功能。
到這裡,需要先看一下這個handler的用法:
public static void main(String[] args) { RealObject realObject = new RealObject(); Action proxy = (Action) Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(), new Class[]{Action.class}, new DynamicProxyHandler(realObject)); proxy.doSomething(); } Proxy.newProxyInstance 傳入的是一個ClassLoader, 一個代理介面,和我們定義的handler,返回的是一個Proxy的例項。
仔細體會這個過程,其實有點類似我們在靜態代理中提到的方案一,生成了一個包含我們擴充套件功能,持有RealObject引用,實現Action介面的代理例項Proxy。只不過這個Proxy不是我們自己寫的,而是java幫我們生成的,有沒有一點動態的味道。
讓我們再回顧一下代理三要素:真實物件:RealObject,代理介面:Action,代理例項:Proxy
上面的程式碼實含義也就是,輸入 RealObject、Action,返回一個Proxy。妥妥的代理模式。
綜上,動態生成+代理模式,也就是動態代理。 網上搜了不少文章,到了這裡,接下來就是和cglib等動態代理實現方法做一下橫向比較。本文不做橫向比較,為了不偏離主題,接下來做縱向挖掘。
看一下原始碼 道理清楚了,但是這篇文章題目是搞懂,所以來看一下這個Proxy是如何自動被生成的。入口就在newProxyInstance方法,核心程式碼如下:
private static final Class<?>[] constructorParams = { InvocationHandler.class }; public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) throws IllegalArgumentException { Class<?> cl = getProxyClass0(loader, intfs); ... final Constructor<?> cons = cl.getConstructor(constructorParams); if (!Modifier.isPublic(cl.getModifiers())) { AccessController.doPrivileged(new PrivilegedAction<Void>() { public Void run() { cons.setAccessible(true); return null; } }); } return cons.newInstance(new Object[]{h}); } 整體流程就是:
1、生成代理類Proxy的Class物件。
2、如果Class作用域為私有,通過 setAccessible 支援訪問
3、獲取Proxy Class建構函式,建立Proxy代理例項。
生成Proxy的Class檔案 生成Class物件的方法中,先是通過傳進來的ClassLoader引數和Class[] 陣列作為組成鍵,維護了一個對於Proxy的Class物件的快取。這樣需要相同Proxy的Class物件時,只需要建立一次。
第一次建立該Class檔案時,為了執行緒安全,方法進行了大量的處理,最後會來到ProxyClassFactory的apply方法中,經過以下流程:
1、校驗傳入的介面是否由傳入的ClassLoader載入的。
2、校驗傳入是否是介面的Class物件。
3、校驗是否傳入重複的介面。
4、拼裝代理類包名和類名,生成.class 檔案的位元組碼。
5、呼叫native方法,傳入位元組碼,生成Class物件。
proxyPkg = ReflectUtil.PROXY_PACKAGE + "."; long num = nextUniqueNumber.getAndIncrement(); String proxyName = proxyPkg + proxyClassNamePrefix + num; byte[] proxyClassFile = ProxyGenerator.generateProxyClass( proxyName, interfaces, accessFlags); return defineClass0(loader, proxyName, proxyClassFile, 0, proxyClassFile.length); 看一下第四步生成.class檔案位元組碼的過程,主要分為兩個階段:
addProxyMethod(hashCodeMethod, Object.class); addProxyMethod(equalsMethod, Object.class); addProxyMethod(toStringMethod, Object.class); for (int i = 0; i < interfaces.length; i++) { Method[] methods = interfaces[i].getMethods(); for (int j = 0; j < methods.length; j++) { addProxyMethod(methods[j], interfaces[i]); } } methods.add(this.generateConstructor()); for (List<ProxyMethod> sigmethods : proxyMethods.values()) { for (ProxyMethod pm : sigmethods) { fields.add(new FieldInfo(pm.methodFieldName, "Ljava/lang/reflect/Method;", ACC_PRIVATE | ACC_STATIC)); methods.add(pm.generateMethod()); } } methods.add(generateStaticInitializer()); 第一個階段的程式碼比較清晰,主要就是新增各種Method,比如toString()、equals,以及傳入的代理介面中的方法。再新增一下構造方法以及靜態初始化方法。這要構成了一個物件,儲存生成Proxy的Class的一些資訊。
到了這裡,已經把要構造的Proxy的方法基本定義完成了,接下來就要生成這個.class檔案了。
ByteArrayOutputStream bout = new ByteArrayOutputStream(); DataOutputStream dout = new DataOutputStream(bout); dout.writeInt(0xCAFEBABE); ... dout.writeShort(ACC_PUBLIC | ACC_FINAL | ACC_SUPER); ... return bout.toByteArray(); 看到這個CAFEBABE,就清楚第二階段的內容了。CAFEBABE是Class檔案的魔數,關於Class檔案這個咖啡寶貝的魔數,相信做Java的人都知道。沒錯,第二階段就是生成位元組碼。按JVM規範,寫入Class檔案中包括許可權控制、方法表、欄位表等內容,生成符合規範的Class檔案。最後返回對應的位元組碼。
位元組碼生成以後,通過呼叫native方法defineClass解析位元組碼,就生成了Proxy的Class物件。
Proxy構造方法 看一下Proxy的構造方法位元組碼生成部分:
MethodInfo minfo = new MethodInfo("<init>", "(Ljava/lang/reflect/InvocationHandler;)V",ACC_PUBLIC); DataOutputStream out = new DataOutputStream(minfo.code); code_aload(0, out); code_aload(1, out); out.writeByte(opc_invokespecial); out.writeShort(cp.getMethodRef(superclassName,"<init>", "(Ljava/lang/reflect/InvocationHandler;)V")); ... 關鍵在於,生成了一個引數為InvocationHandler的構造方法,code載入的是jvm方法區中的程式碼,然後通過invokespecial指令呼叫了父類構造方法。
檢視生成的Class檔案 上面利用位元組碼生成技術產生Class檔案的過程,看起來可能比較晦澀,其實我們可以檢視這個產生的Proxy到底是個什麼樣子。
注意ProxyGenerator中有這樣一個邏輯:
if(saveGeneratedFiles) { ... FileOutputStream file = new FileOutputStream(dotToSlash(name) + ".class"); file.write(classFile); ... } 再看一下saveGeneratedFiles這個變數:
private final static boolean saveGeneratedFiles = java.security.AccessController.doPrivileged( new GetBooleanAction("sun.misc.ProxyGenerator.saveGeneratedFiles")) .booleanValue(); 這是一個final型別的變數,通過GetBooleanAction方法讀取系統變數,獲取系統設定。預設這個值是false,稍微看一下System這個類的原始碼,發現有可以設定系統變數的Api,然後在程式的main 函式設定一下這個變數:
System.getProperties().setProperty("sun.misc.ProxyGenerator.saveGeneratedFiles", "true"); 這個時候,再跑一遍程式,就可以看到生成的Proxy的Class檔案了,直接雙擊利用 ide 反編譯。
package com.sun.proxy; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; import java.lang.reflect.UndeclaredThrowableException; public final class $Proxy0 extends Proxy implements Action { 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 void doSomething() throws { try { super.h.invoke(this, m3, (Object[])null); } catch (RuntimeException | Error var2) { throw var2; } catch (Throwable var3) { throw new UndeclaredThrowableException(var3); } } ... static { try { ... m3 = Class.forName("Action").getMethod("doSomething", new Class[0]); } catch (NoSuchMethodException var2) { throw new NoSuchMethodError(var2.getMessage()); } catch (ClassNotFoundException var3) { throw new NoClassDefFoundError(var3.getMessage()); } } } 省略一些無關程式碼,可以看到兩個重要的方法。
一個就是我們的代理方法doSomething、另一個就是構造方法。
這個$Proxy0 繼承 Proxy並呼叫了父類的構造方法,回憶一下上文提到的invokeSpecial,怎麼樣,對上了吧。
看一下Proxy中這個構造方法:
protected Proxy(InvocationHandler h) { Objects.requireNonNull(h); this.h = h; } 在看一下$Proxy0 的代理方法:
super.h.invoke(this, m3, (Object[])null); 再來回顧一下生成Proxy例項的過程:
private static final Class<?>[] constructorParams = { InvocationHandler.class }; ... final Constructor<?> cons = cl.getConstructor(constructorParams); ... return cons.newInstance(new Object[]{h}); 其實newInstance生成Proxy例項時,通過$Proxy0的Class物件,選擇了這個InvocationHandler為引數的構造方法,傳入我們定義的InvocationHandler並生成了一個 Proxy0的例項!InvocationHandler 裡有realObject的邏輯以及我們的擴充套件邏輯,當我們呼叫Proxy0的doSomething方法時,就會呼叫到我們InvocationHandler 裡 實現的invoke方法。
對上面這個過程,做一張圖總結一下:
--------------------- 作者:Wangqyoho 來源:CSDN 原文:https://blog.csdn.net/wangqyoho/article/details/77584832 版權宣告:本文為博主原創文章,轉載請附上博文連結!