1. 程式人生 > >用大白話講Java動態代理的原理

用大白話講Java動態代理的原理

### 動態代理是什麼 首先說下代理模式,**代理模式**是常見的一種java設計模式,特徵是**代理類**與**委託類**實現了同樣的介面,代理類主要負責為委託類預處理、過濾、轉發,以及事後處理等。代理類與委託類之間通常會存在關聯關係,一個代理類的例項與它的委託類的例項是關聯的。代理類的例項本身是並不真正關心被呼叫方法的內部邏輯,而是會通過內部訪問呼叫 委託類的例項真正實現了的方法,來為呼叫者提供服務。 有代理的話,在訪問實際物件時,是通過代理例項來訪問、呼叫委託類方法的,代理模式就是在訪問實際物件時引入一定程度的間接性,因為這種間接性,可以附加多種用途。 **動態代理**對比靜態代理,最大的特點是代理類是在程式執行時生成的,並非在編譯期生成,能做的事情也多了,自然風險也高了。 ### 動態代理最簡單的用法 用一個比較接近生活的例子:中午,餓了的室友 委託 持家有道的你 去點外賣 Hungry.java :介面 ```java public interface Hungry { void callLunch(); } ``` Roommate.java :Hungry介面的實現類,也就是委託類 ```java public class Roommate implements Hungry{ private String name; public Roommate(String name) { this.name = name; } @Override public void callLunch() { System.out.println("好餓,今天午飯點外賣吧"); } } ``` ```java public class RoommateInvocationHandler implements InvocationHandler { private T rommate; public RoommateInvocationHandler(T roommate){ this.rommate = roommate; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("下單前,我先幫你看下有沒有平臺優惠券吧"); Object result = method.invoke(rommate , args); return result; } } ``` **InvocationHandler**是一個介面,由代理例項內部的`invocation handler`實現的介面。每個代理例項都有一個關聯的`invocation handler`。當代理例項上呼叫方法時,`method.invoke(baseImpl, args)`,此方法將被編碼並織入到代理例項內部的 `invocation handler`實現的`invoke`方法中。 利用 **Proxy** 的方式實現動態代理,呼叫 委託類介面 的方法,完成午餐點外賣這個操作 ```java public static void main(String[] args) { Roommate roommate = new Roommate("zhangsan"); Hungry proxyInstance = (Hungry) Proxy.newProxyInstance( roommate.getClass().getClassLoader(), roommate.getClass().getInterfaces(), new RoommateInvocationHandler(roommate) ); proxyInstance.callLunch(); } //輸出結果 下單前,我先幫你看下有沒有平臺優惠券吧 好餓,今天午飯點外賣吧 ``` 代理例項`proxyInstance`的型別是Hungry,所以只能呼叫Hungry裡規定的方法。Roommate作為介面實現類,不是來自介面的其他的方法,是無法通過動態代理呼叫的。 可以看到代理例項在呼叫委託類實現的方法時,可以很方便地在呼叫方法的前後執行一些操作,在示例程式碼中則是在呼叫方法前簡單輸出了一行: `System.out.println("下單前,我先幫你看下有沒有平臺優惠券吧")`,還可以有其他用途,例如記錄這個方法的耗時時間,對方法的引數或者返回結果進行修改等等。這也是Spring,Dagger進行AOP程式設計的原理。 那為什麼繼承`InvocationHandler`介面和持有委託類引用的RoommateInvocationHandler呼叫來自`Hungry`介面的`callLunch()`方法時可以呼叫到委託類對callLunch()的邏輯實現呢,看看它的背後原理: ### 動態代理的實現原理 從`Proxy.newProxyInstance()` 入手,逐步分析 InvocationHandler 如何建立代理例項和委託例項的關聯: ```java public static Object newProxyInstance(ClassLoader loader , Class[] interfaces, InvocationHandler h) throws IllegalArgumentException { //InvocationHandler必須非空,說明是個重要角色 Objects.requireNonNull(h); //獲取委託類的介面 final Class[] intfs = interfaces.clone(); final SecurityManager sm = System.getSecurityManager(); if (sm != null) { checkProxyAccess(Reflection.getCallerClass(), loader, intfs); } /* * Look up or generate the designated proxy class. * 核心:通過類載入器和委託類介面,在記憶體中查找出或者生成指定的代理類 */ Class cl = getProxyClass0(loader, intfs); /* * Invoke its constructor with the designated invocation handler. * 利用指定的invocation handler呼叫它的構造器方法,構建代理類的例項返回 */ try { if (sm != null) { checkNewProxyPermission(Reflection.getCallerClass(), cl); } final Constructor cons = cl.getConstructor(constructorParams); final InvocationHandler ih = h; if (!Modifier.isPublic(cl.getModifiers())) { AccessController.doPrivileged(new PrivilegedAction() { public Void run() { cons.setAccessible(true); return null; } }); } 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); } } catch (NoSuchMethodException e) { throw new InternalError(e.toString(), e); } } ``` 來到這一步好像就停下了,那麼接下來探究 cl 這個例項建立過程發生了什麼: 在上面示例程式碼main函式的後面接著補充。利用`ProxyGenerator.generateProxyClass`生成這個動態生成的類檔案,寫入了指定路徑的class檔案內 ` $Proxy0` 是 代理類在系統內部的編號,在示例程式碼只生成了一個代理類所以編號是 ` $Proxy0` 。 ```java byte[] classFile = ProxyGenerator.generateProxyClass("$Proxy0",Roommate.class.getInterfaces()); String filePath = "C:\\Users\\ODM\\Desktop\\RoommateProxy.class"; try(FileOutputStream fos = new FileOutputStream(filePath)) { fos.write(classFile); fos.flush(); }catch (IOException e){ e.printStackTrace(); System.out.println("error:寫入檔案"); } ``` 使用反編譯工具,我這裡用的是jd-gui反編譯,這個**$Proxy0**類,實現了`Proxy`類,繼承了和委託類相同的介面 ```java import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; import java.lang.reflect.UndeclaredThrowableException; import proxy_test.Hungry; public final class $Proxy0 extends Proxy implements Hungry{ private static Method m1; private static Method m3; //由下方靜態程式碼塊得知,m3代表callLunch()這一個方法 private static Method m2; private static Method m0; /* * 父類Proxy的構造器,其中 h 屬性為 InvocationHandler引用 * protected Proxy(InvocationHandler h) { Objects.requireNonNull(h); this.h = h; } */ public $Proxy0(InvocationHandler paramInvocationHandler) throws { super(paramInvocationHandler); } //關鍵!可供外界呼叫,方法名與委託類實現介面的方法相同,利用 InvocationHandler呼叫invoke public final void callLunch() throws { try{ this.h.invoke(this, m3, null); return; } catch (Error|RuntimeException localError){ throw localError; } catch (Throwable localThrowable){ throw new UndeclaredThrowableException(localThrowable); } } public final boolean equals(Object paramObject) throws {} public final String toString() throws {...} public final int hashCode() throws {...} static{ try{ m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[] { Class.forName("java.lang.Object") }); m3 = Class.forName("proxy_test.Hungry").getMethod("callLunch", 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]); return; } catch (NoSuchMethodException localNoSuchMethodException){ throw new NoSuchMethodError(localNoSuchMethodException.getMessage()); } catch (ClassNotFoundException localClassNotFoundException){ throw new NoClassDefFoundError(localClassNotFoundException.getMessage()); } } } ``` 事情逐漸明朗起來,從這個動態類的原始碼,可以分析出:` $Proxy0` ,在構建這個類時,會呼叫了父類Proxy的構造方法,將`InvocationHandler`引用傳遞給了父類Proxy的 `h` 屬性,於是當我們在外界使用 代理例項 呼叫了 `callLunch()` 這個方法時,就會來到這一句 `this.h.invoke(this, m3, null);` 由於`h`屬性其實是`InvocationHandler`引用,呼叫了它的`invoke`,也就導致了上面示例程式碼中的`RoommateInvocationHandler`類的重寫過的`invoke`方法也就被呼叫了,`RoommateInvocationHandler`也持有委託類的引用,所以委託類的方法也被呼叫起來了。 Java的繼承機制是單繼承,多介面。代理類因為必須要繼承Proxy類,所以java的動態代理只能對介面進行代理,無法對一個class類進行動態代理。 ### 動態代理原理總結 用大白話的方式講: 有一個類`InvocationHandler`,它的性質類似一箇中介,中介類構建時持有了委託物件,所以可以在它的`invoke`方法中呼叫了委託物件實現介面的具體方法。當外部呼叫這個`InvocationHandler`的invoke方法時,對 `invoke` 的呼叫最終都轉為對委託物件的方法呼叫。 建立明面上負責代理的代理例項時,在記憶體中動態生成的類不但繼承了`Proxy`,也實現了與委託物件相同的介面,因此代理例項可以呼叫此介面的方法,然後通過持有的中介類物件來呼叫中介類物件的`invoke`方法,最終達到代理例項執行了委託者的