1. 程式人生 > >抽絲剝繭——代理設計模式

抽絲剝繭——代理設計模式

### 代理設計模式 代理設計模式再生活中應該很常見了,現在各種中間商的貨物代售方便了我們的生活也增加了我們生活的成本。這種生活中的中間商行為就是一種代理模式。 **拿一個品牌來說明:** ![](https://gitee.com/onlyzl/image/raw/master/img/20200905081622.png) 在程式設計領域中一般存在兩種代理模式 - 靜態代理。(僅僅可以代理一個類的行為,不能隨類的變化而變化) - 動態代理。(可以代理所有類的行為) 接下來我們先來看靜態代理 ### 1. 靜態代理 僅僅用來代理一個類的行為。 **程式碼演示一下:** - 繼承實現代理(**不推薦,耦合性大**) ```java class NaiKe { void run() { System.out.println("耐克"); } } //代理類 class ShoesProxy extends NaiKe{ @Override void run() { System.out.println("agency shoes before"); super.run(); System.out.println("agency shoes after"); } } ``` - 組合實現代理(推薦) ```java class NaiKe{ void run() { System.out.println("耐克"); } } class ShoesProxy { NaiKe naiKe = new NaiKe(); void run() { System.out.println("agency shoes before"); naiKe.run(); System.out.println("agency shoes after"); } } ``` - 多型實現代理,多個代理巢狀 ```java public class ProxyDesgin { public static void main(String[] args) { Shoes shoes = new ShoesProxy(new ShoesTimer(new NaiKe())); shoes.run(); } } abstract class Shoes{ abstract void run(); } class NaiKe extends Shoes{ @Override void run() { System.out.println("耐克"); } } class Adi extends Shoes{ @Override void run() { System.out.println("阿迪達斯"); } } //代理類 class ShoesProxy extends Shoes { Shoes shoes ; public ShoesProxy(Shoes shoes){ this.shoes = shoes ; } void run() { System.out.println("agency shoes before"); shoes.run(); System.out.println("agency shoes after"); } } class ShoesTimer extends Shoes { Shoes shoes ; public ShoesTimer(Shoes shoes){ this.shoes = shoes ; } void run() { System.out.println("log timer shoes before"); shoes.run(); System.out.println("log timer shoes after"); } } ``` 畫個圖瞅瞅靜態代理 ![](https://gitee.com/onlyzl/image/raw/master/img/20200905103104.png) 這個就是靜態代理,兄弟們應該已經發現了它的缺點,只能指定自己想要進行代理的類,而不能對所有的類進行代理,擴充套件性太差,所以引出了**動態代理** ### 2.動態代理 談到動態代理,腦子裡第一個出現的肯定就是`Java`動態代理了。我們先來聊一下`Java`動態代理。 #### 2.1 Java動態代理 先來看一個動態代理的案例 ```java NaiKe naiKe = new NaiKe(); Shoes shoes = (Shoes) Proxy.newProxyInstance(NaiKe.class.getClassLoader(), new Class[]{Shoes.class}, new InvocationHandler() { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("begin timer : " + System.currentTimeMillis()); method.invoke(naiKe,args); System.out.println("after timer : " + System.currentTimeMillis()); return null; } }); shoes.run(); ``` - 第一個引數。**通過動態代理建立的物件被哪個載入器載入,一般使用本類的類載入器即可** - 第二個引數。**被代理物件要實現的方法** - 第三個引數。**被代理物件被呼叫的時候該如何處理邏輯** 我們看一下動態代理的原始碼。 我們可以通過以下方式讓`JVM`將動態生成的代理類儲存到我們的專案中 - `JDK1.8`使用`System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");` - `JDK1.8`以上可以使用`1 System.getProperties().put("jdk.proxy.ProxyGenerator.saveGeneratedFiles", "true");` 生成的代理類如下: ```java final class $Proxy0 extends Proxy implements Shoes { 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 { } public final void run() throws { try { super.h.invoke(this, m3, (Object[])null); } catch (RuntimeException | Error var2) { throw var2; } catch (Throwable var3) { throw new UndeclaredThrowableException(var3); } } public final String toString() throws { } public final int hashCode() throws { } static { try { m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object")); m3 = Class.forName("desgin.proxy.Shoes").getMethod("run"); 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()); } } } ``` 從這個類的結構中,我們可以看出很多的東西 - 為什麼說`JAVA`動態代理僅僅只能代理介面。(**類單繼承,代理物件預設繼承Proxy類**) - 動態代理的第二個引數,介面內部的方法會被代理物件重寫,然後呼叫第三個引數的`invoke`方法。 上面兩個也是動態代理的原理了。我們來仔細看一下我們的`run()`方法,也就是我們代理物件要實現的介面 ```java public final void run() throws { try { super.h.invoke(this, m3, (Object[])null); } catch (RuntimeException | Error var2) { throw var2; } catch (Throwable var3) { throw new UndeclaredThrowableException(var3); } } ``` - 呼叫了父類的`h`,父類的`h`是`InvocationHandler`,然後呼叫了`invoke`方法執行了我們的執行邏輯。 **這個就是動態代理的全部實現過程** 還有一個非常牛逼的點,它怎麼生成的這個代理類。來看一下代理的全過程 ![](https://gitee.com/onlyzl/image/raw/master/img/20200906150632.png) 圖中的`ASM`就是為我們動態生成一個代理類的工具,它直接操作了`Class`位元組碼的二進位制,然後建立了一個代理類,返回給我們。 `Java`動態代理就聊到這裡了。下面看一看`CGLIb`和`AOP` #### 2.2 CGLIB動態代理 彌補了`Java`動態代理的不足,`CGLIB`動態代理可以代理類。它直接建立了一個被代理物件的子類,實現了對其的代理過程。我們來看一下它的代理過程 ```java //列印生成的代理物件,放置於當前專案下 System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "."); //建立Enhancer物件,類似於JDK動態代理的Proxy類,下一步就是設定幾個引數 Enhancer enhancer = new Enhancer(); //設定目標類的位元組碼檔案 enhancer.setSuperclass(Tank.class); //設定回撥函式 enhancer.setCallback(new MethodInterceptor() { @Override public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable { methodProxy.invokeSuper(o,objects); return null; } }); //這裡的creat方法就是正式建立代理類 Tank proxyDog = (Tank)enhancer.create(); //呼叫代理類的eat方法 proxyDog.tank(); ``` 還是和`Java`動態代理相似,傳入一個需要代理的`Class`,設定代理的回撥函式。然後呼叫`create`建立一個代理物件,呼叫代理物件的方法。 代理第一行可以輸出代理物件,會生成三個代理物件。 ![](https://gitee.com/onlyzl/image/raw/master/img/20200906153314.png) 檢視中間那個,可以看到我們被代理物件的方法 ```java public class Tank$$EnhancerByCGLIB$$a4ec679a extends Tank implements Factory { //構造方法 public Tank$$EnhancerByCGLIB$$a4ec679a() { CGLIB$BIND_CALLBACKS(this); } //被代理方法 final void tank() { MethodInterceptor var10000 = this.CGLIB$CALLBACK_0; if (var10000 == null) { CGLIB$BIND_CALLBACKS(this); var10000 = this.CGLIB$CALLBACK_0; } if (var10000 != null) { //呼叫增強的方法 var10000.intercept(this, CGLIB$tank$0$Method, CGLIB$emptyArgs, CGLIB$tank$0$Proxy); } else { super.tank(); } } } ``` 在之前的`CGLIB`動態代理實現中,我們看到了攔截的回撥中傳入了四個引數,從上面的原始碼中可以看到對應引數的作用。 - `Object o`代表生成的代理物件 - `Method method`代表當前代理物件呼叫的方法 - `Object[] objects`代表方法的引數 - `MethodProxy methodProxy`我們呼叫方法的方法代理,它沒有使用`Java`本身的反射,而是動態生成一個新的類,(繼承`FastClass`),向類中寫入委託類例項直接呼叫方法的語句。 我們可以看一下`superinvoke`的原始碼 ```java public Object invokeSuper(Object obj, Object[] args) throws Throwable { try { this.init(); MethodProxy.FastClassInfo fci = this.fastClassInfo; return fci.f2.invoke(fci.i2, obj, args); } catch (InvocationTargetException var4) { throw var4.getTargetException(); } } private static class FastClassInfo { FastClass f1; FastClass f2; int i1; int i2; private FastClassInfo() { } } ``` 一個圖理解`CgLib`動態代理過程 ![](https://gitee.com/onlyzl/image/raw/master/img/20200906161347.png) 寫了這麼多,感覺對於代理設計模式講解的篇幅不是很大,而是著重講解了動態代理的實現方式。總的而言,代理設計模式與我們日常生活非常的接近,生活中的事物幾乎都在被代理,所以這個設計模式應該很好懂,所以著重講解了動態代理的實現