1. 程式人生 > >JDK動態代理 原始碼解析

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  版權宣告:本文為博主原創文章,轉載請附上博文連結!