1. 程式人生 > >Java動態代理設計模式

Java動態代理設計模式

本文主要介紹`Java`中兩種常見的動態代理方式:`JDK原生動態代理`和`CGLIB動態代理`。 ## 什麼是代理模式 就是為其他物件提供一種代理以控制對這個物件的訪問。代理可以在不改動目標物件的基礎上,增加其他額外的功能(擴充套件功能)。 代理模式角色分為 3 種: - `Subject`(抽象主題角色):定義代理類和真實主題的公共對外方法,也是代理類代理真實主題的方法; - `RealSubject`(真實主題角色):真正實現業務邏輯的類; - `Proxy`(代理主題角色):用來代理和封裝真實主題; 如果根據位元組碼的建立時機來分類,可以分為靜態代理和動態代理: - 所謂靜態也就是在程式執行前就已經存在代理類的位元組碼檔案,代理類和真實主題角色的關係在執行前就確定了。 - 而動態代理的原始碼是在程式執行期間由JVM根據反射等機制動態的生成,所以在執行前並不存在代理類的位元組碼檔案 ## 靜態代理 學習動態代理前,有必要來學習一下靜態代理。 靜態代理在使用時,需要定義介面或者父類,被代理物件(目標物件)與代理物件(Proxy)一起實現相同的介面或者是繼承相同父類。 來看一個例子,模擬小貓走路的時間。 ```java // 介面 public interface Walkable { void walk(); } // 實現類 public class Cat implements Walkable { @Override public void walk() { System.out.println("cat is walking..."); try { Thread.sleep(new Random().nextInt(1000)); } catch (InterruptedException e) { e.printStackTrace(); } } } ``` 如果我想知道走路的時間怎麼辦?可以將實現類`Cat`修改為: ```java public class Cat implements Walkable { @Override public void walk() { long start = System.currentTimeMillis(); System.out.println("cat is walking..."); try { Thread.sleep(new Random().nextInt(1000)); } catch (InterruptedException e) { e.printStackTrace(); } long end = System.currentTimeMillis(); System.out.println("walk time = " + (end - start)); } } ``` 這裡已經侵入了原始碼,如果原始碼是不能改動的,這樣寫顯然是不行的,這裡可以引入時間代理類`CatTimeProxy`。 ```java public class CatTimeProxy implements Walkable { private Walkable walkable; public CatTimeProxy(Walkable walkable) { this.walkable = walkable; } @Override public void walk() { long start = System.currentTimeMillis(); walkable.walk(); long end = System.currentTimeMillis(); System.out.println("Walk time = " + (end - start)); } } ``` 如果這時候還要加上常見的日誌功能,我們還需要建立一個日誌代理類`CatLogProxy`。 ```java public class CatLogProxy implements Walkable { private Walkable walkable; public CatLogProxy(Walkable walkable) { this.walkable = walkable; } @Override public void walk() { System.out.println("Cat walk start..."); walkable.walk(); System.out.println("Cat walk end..."); } } ``` 如果我們需要先記錄日誌,再獲取行走時間,可以在呼叫的地方這麼做: ```java public static void main(String[] args) { Cat cat = new Cat(); CatLogProxy p1 = new CatLogProxy(cat); CatTimeProxy p2 = new CatTimeProxy(p1); p2.walk(); } ``` 這樣的話,計時是包括打日誌的時間的。 ### 靜態代理的問題 如果我們需要計算`SDK`中100個方法的執行時間,同樣的程式碼至少需要重複100次,並且建立至少100個代理類。往小了說,如果`Cat`類有多個方法,我們需要知道其他方法的執行時間,同樣的程式碼也至少需要重複多次。因此,靜態代理至少有以下兩個侷限性問題: - 如果同時代理多個類,依然會導致類無限制擴充套件 - 如果類中有多個方法,同樣的邏輯需要反覆實現 所以,我們需要一個通用的代理類來代理所有的類的所有方法,這就需要用到動態代理技術。 ## 動態代理 學習任何一門技術,一定要問一問自己,這到底有什麼用。其實,在這篇文章的講解過程中,我們已經說出了它的主要用途。你發現沒,使用動態代理我們居然可以在不改變原始碼的情況下,直接在方法中插入自定義邏輯。這有點不太符合我們的一條線走到底的程式設計邏輯,這種程式設計模型有一個專業名稱叫`AOP`。所謂的`AOP`,就像刀一樣,抓住時機,趁機插入。 ![](https://user-gold-cdn.xitu.io/2018/3/2/161e5ba2dfeb24bb?imageView2/0/w/1280/h/960/format/webp/ignore-error/1) ## Jdk動態代理 JDK實現代理只需要使用newProxyInstance方法,但是該方法需要接收三個引數: ```java @CallerSensitive public static Object newProxyInstance(ClassLoader loader, Class[] interfaces, InvocationHandler h) throws IllegalArgumentException { 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. */ 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 Privileg