設計模式之代理模式(Java)

  • 2019 年 11 月 10 日
  • 筆記

簡介

代理模式出場率真的相當的高,幾乎所有框架中無一例外都用到了代理模式,所以了解一下收益還是很高的。

代理模式是什麼

如果用一句話來描述代理模式:

代理模式就是為其他對象提供一種代理以控制對被代理對象的訪問,也就是我們常說的中介

在開發以及生活中經常聽到正向代理,反向代理這樣的詞,舉例說明

  • 正向代理

    由於網路原因我們訪問不了Google,這時候我們就需要找個梯子,替我們去訪問Google,並且把我們需要的資訊返回,這個梯子代理

  • 反向代理

    作為服務端為了安全,我們不想把實際伺服器的資訊暴露出去,已防止不法分子的攻擊,這時候我們我需要一個代理統一接受用戶的請求,並且幫助用戶請求後端用戶返回給用戶

代理模式的作用

一言以蔽之就是解耦合,創建一個沒法訪問對象的代理供我們使用,同時我們又可以在代理對象中加入一些補充的功能,這樣完全不會破壞封裝,滿足開閉原則

UML

動物有一個睡覺行為,大多數人都沒法見到北極熊(RealSubject),我們只能通過動物世界節目組的攝影師(Proxy)去北極拍攝,從傳回的畫面中我們看到一隻北極熊在洞里睡覺,並且畫面上還加上了字幕「快看這裡有隻冬眠的北極熊!」

實踐

代理模式的實現有多種方式主要分為靜態代理和動態代理

靜態代理

  • Subject
public interface LifeService {      String sleep();  }
  • RealSubject
public class WhiteBear implements LifeService {      @Override      public String sleep() {          return "Zzzzzzz";      }  }
  • Proxy
public class Proxy implements LifeService {        // 被代理對象      private LifeService target;        public Proxy(LifeService target) {          this.target = target;      }        @Override      public String sleep() {          // 拿到被代理對象行為的返回值,加上輔助功能,一起返回          return "快看這裡有隻冬眠的北極熊! n" + this.target.sleep();      }  }
  • Factory,也可以不用工廠客戶端直接new
public class ProxyFactory {        public static Proxy getLifeServiceProxy(Class clz) throws IllegalAccessException, InstantiationException {          LifeService target = (LifeService) clz.newInstance();          return new Proxy(target);      }  }
  • Client
public class Test {      public static void main(String[] args) throws IllegalAccessException, InstantiationException {          Proxy proxy = ProxyFactory.getLifeServiceProxy(WhiteBear.class);          System.out.println(proxy.sleep());      }        /**       * 輸出:       * 快看這裡有隻冬眠的北極熊!       * Zzzzzzz       */  }

可以看到靜態代理其實挺好理解的,就是我們把被代理和代理類都寫好,生成兩個class位元組碼文件, 所謂靜態也就是在程式運行前就已經存在代理類的位元組碼文件

不足

  • 代理對象的一個介面只服務於一種類型的對象,如果要代理的方法很多,勢必要為每一種方法都進行代理,靜態代理在程式規模稍大時就無法勝任了。
  • 如果介面增加一個方法,除了所有實現類需要實現這個方法外,所有代理類也需要實現此方法。增加了程式碼維護的複雜度

動態代理

JDK自帶

大致思路是在運行過程中JVM進行監控,發生指定行為時動態的創建的代理,通過反射去訪問被代理對象

  • Subject
public interface LifeService {      String sleep();      String wake();  }
  • RealSubject
public class Person implements LifeService {      @Override      public String sleep() {          return "晚安晚安";      }        @Override      public String wake() {          return "早鴨";      }  }
  • Proxy

    我們實現InvocationHandler這個介面,通過invoke方法去執行代理行為

public class InvocationProxy implements InvocationHandler {        // 被監控的對象(此例中為Person類實例)      private LifeService lifeService;        // 監控啟動拿到需要被監控的對象      public InvocationProxy(LifeService lifeService) {          this.lifeService = lifeService;      }        /**       * 監控的行為發生時,JVM會攔截到行為執行invoke       *       * @param proxy  監控對象:監控行為是否發生       * @param method 被監控的行為方法       * @param args   被監控行為方法的參數       * @return       * @throws Throwable       */      @Override      public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {          // 因為我們攔截了行為,並且加了一些輔助行為,完成之後我們要替被攔截行為把值返回          Object result = null;          String methodName = method.getName();          if ("sleep".equals(methodName)) {              result = getTime();              result += (String) method.invoke(this.lifeService, args);          } else if ("wake".equals(methodName)) {              result = getTime();              result += (String) method.invoke(this.lifeService, args);          }          return result;      }        // 輔助方法      private String getTime() {          return Clock.systemDefaultZone().instant().toString() + "n";      }    }
  • Factory,也可以不用工廠客戶端直接new
public class ProxyFactory {        public static LifeService getLifeServiceProxyInstance(Class clz) throws IllegalAccessException, InstantiationException {          // 創建被代理對象          LifeService target = (LifeService) clz.newInstance();          // 綁定到代理執行器中          InvocationHandler handler = new InvocationProxy(target);          // JVM層面對被代理對象進行監控,行為發生就動態創建代理對象處理          LifeService $proxy = (LifeService) Proxy.newProxyInstance(                  target.getClass().getClassLoader(),                  target.getClass().getInterfaces(),                  handler);          return $proxy;      }  }
  • Client
public class Test {      public static void main(String[] args) throws InstantiationException, IllegalAccessException, InterruptedException {          LifeService zhang = ProxyFactory.getLifeServiceProxyInstance(Person.class);          System.out.println(zhang.sleep());          System.out.println(zhang.wake());      }        /**       * 輸出:       * 2019-11-10T05:24:16.932Z       * 晚安晚安       * 2019-11-10T05:24:16.942Z       * 早鴨       */  }

用了動態代理我們把所有代理需要實現的行為集中到了invoke這一個方法去執行,不要再寫大量模板程式碼了,並且我們實際上可以在一個InvocationHandler代理多個介面

不足

  • 如果InvocationHandler中代理了兩個介面,兩個介面中有完全一模一樣的兩個方法,就沒法去區分了
  • 代理必須基於介面,沒有實現介面的類沒法被代理

三方庫Cglib

Cglib 基於 ASM 框架操作位元組碼幫我們生成需要的代理對象,並且不要求實現介面

加入依賴

<dependency>      <groupId>cglib</groupId>      <artifactId>cglib</artifactId>      <version>3.3.0</version>  </dependency>
  • RealSubject

    我們不需要實現特定介面了

public class Person {        public String sleep() {          return "晚安晚安";      }        public String wake() {          return "早鴨";      }  }
  • Proxy

    我們的邏輯和JDK自帶的動態代理是一樣的

public class CglibProxy implements MethodInterceptor {      //需要代理的目標對象      private Object target;        public CglibProxy(Object target) {          this.target = target;      }        /**       *       * @param o 監控對象:監控行為是否發生       * @param method 被監控的行為方法       * @param objects 被監控行為方法的參數       * @param methodProxy 代理中生成的方法       * @return       * @throws Throwable       */      @Override      public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {          Object result = null;            String methodName = method.getName();          if ("sleep".equals(methodName)) {              result = getTime();              result += (String) method.invoke(this.target, objects);          } else if ("wake".equals(methodName)) {              result = getTime();              result += (String) method.invoke(this.target, objects);          }          return result;      }        // 輔助行為      private String getTime() {          return Clock.systemDefaultZone().instant().toString() + "n";      }  }
  • Factory,也可以不用工廠客戶端直接new
public class ProxyFactory {        public static Object getCglibProxyInstance(Class clz) throws IllegalAccessException, InstantiationException {          // Enhancer類是CGLib中的一個位元組碼增強器          Enhancer enhancer=new Enhancer();          // 設置被代理類的位元組碼文件,這裡我們關注的不再是介面          enhancer.setSuperclass(clz);          // 創建被代理對象          Object target = clz.newInstance();          // 綁定到代理執行器中          CglibProxy proxy = new CglibProxy(target);          // 設置回調這個代理對象          enhancer.setCallback(proxy);          // 生成返回代理對象          return enhancer.create();      }  }
  • Client
public class Test {        public static void main(String[] args) throws InstantiationException, IllegalAccessException {          Person zhang = (Person) ProxyFactory.getCglibProxyInstance(Person.class);          System.out.println(zhang.sleep());          System.out.println(zhang.wake());      }        /**       * 輸出:       * 2019-11-10T06:01:13.105Z       * 晚安晚安       * 2019-11-10T06:01:13.115Z       * 早鴨       */  }

不需要實現介面也可以動態代理啦,真的很了不起

不足

  • 依賴三方庫
  • 對於final的類和方法不能代理, 因為Cglib 生成的代理類需要重寫代理類中所有的方法