設計模式(四)——代理模式

一、概述

1、介紹

  代理模式(Proxy):為一個對象提供一個代理,以控制對這個對象的訪問,即通過代理對象訪問目標對象。這樣做的好處是,可以在目標對象實現的基礎上,增強額外的功能操作,即擴展目標對象的功能。
  被代理的對象可以是遠程對象、創建開銷大的對象或需要安全控制的對象。
  代理模式有不同的形式,主要有三種,靜態代理、動態代理(JDK代理、介面代理)和Cglib代理(可以在記憶體動態的創建對象,而不需要實現介面,屬於動態代理的範疇)。

二、靜態代理

1、介紹

  靜態代理在使用時,需要定義介面或者父類,被代理對象(即目標對象)與代理對象一起實現相同的介面或者繼承相同父類。調用的時候通過調用代理對象的方法來調用目標對象。

2、模式原理

3、程式碼

  程式碼示例:靜態代理

 1 // 介面
 2 public interface ITeacher {
 3     void teach();
 4 }
 5 
 6 // 目標對象
 7 public class Teacher implements ITeacher {
 8     @Override
 9     public void teach() {
10         System.out.println("老師正在上課");
11     }
12 }
13 
14 // 代理對象,靜態代理
15 public class TeacherProxy implements ITeacher {
16     // 目標對象,通過構造器來聚合
17     private Teacher teacher;
18 
19     public TeacherProxy(Teacher teacher) {
20         this.teacher = teacher;
21     }
22 
23     @Override
24     public void teach() {
25         System.out.println("開始靜態代理...");
26         // ...
27         // 執行目標對象的方法
28         teacher.teach();
29         // ...
30         System.out.println("靜態代理提交...");
31     }
32 }
33 
34 public class Main {
35     public static void main(String[] args) {
36         // 創建代理對象,同時將目標對象作為參數傳遞
37         TeacherProxy teacherProxy = new TeacherProxy(new Teacher());
38         // 通過代理對象執行目標對象的方法
39         teacherProxy.teach();
40     }
41 }

4、優缺點

  優點:在不修改目標對象功能的前提下,能通過代理對象對目標功能進行擴展。
  缺點:因為代理對象需要與目標對象實現相同的介面,所以會有很多代理類。一旦介面增加方法,目標對象與代理對象都要維護。

三、動態代理(JDK代理)

1、介紹

  代理對象,不需要實現介面,但是目標對象要實現介面,否則不能用動態代理。代理對象的生成是利用JDK的API,動態的在記憶體中構建代理對象,動態代理也叫JDK代理、介面代理。

2、模式原理

  根據傳入的目標對象,利用返回機制,返回一個代理對象。然後通過代理對象,調用目標對象方法。

3、程式碼

  程式碼示例:動態代理,JDK代理

 1 // 介面
 2 public interface ITeacher {
 3     void teach();
 4 
 5     int add(int i, int j);
 6 }
 7 
 8 // 目標對象
 9 public class Teacher implements ITeacher {
10     @Override
11     public void teach() {
12         System.out.println("老師正在上課");
13     }
14 
15     @Override
16     public int add(int i, int j) {
17         System.out.println("老師正在做加法");
18         return i + j;
19     }
20 }
21 
22 // 代理對象,動態代理
23 public class TeacherProxy {
24     // 目標對象,通過介面來聚合
25     private Object target;
26 
27     public TeacherProxy(Object target) {
28         this.target = target;
29     }
30     
31     // 給目標對象生成一個代理對象
32     public Object getProxyInstance() {
33         // ClassLoader loader:指定當前目標對象使用的類載入器
34         // Class<?>[] interfaces:目標對象實現的介面類型,使用泛型方式確認
35         // InvocationHandler h:事件處理,執行目標對象的方法時,會觸發事件處理器方法,會把當前執行的目標對象方法作為參數傳入
36         return Proxy.newProxyInstance(target.getClass().getClassLoader(),
37                 target.getClass().getInterfaces(),
38                 new InvocationHandler() {
39                     @Override
40                     public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
41                         System.out.println("JDK動態代理開始...");
42                         // 通過反射機制調用目標對象的方法
43                         Object invoke = method.invoke(target, args);
44                         System.out.println("JDK動態代理提交...");
45                         return invoke;
46                     }
47                 });
48     }
49 }
50 
51 public class Main {
52     public static void main(String[] args) {
53         // 創建代理對象,同時將創建的目標對象作為參數傳遞
54         TeacherProxy proxy = new TeacherProxy(new Teacher());
55 
56         // 通過代理對象執行目標對象的方法
57         ITeacher iTeacher = (ITeacher) proxy.getProxyInstance();
58         iTeacher.teach();
59 
60         int add = iTeacher.add(1, 2);
61         System.out.println(add);
62     }
63 }

四、Cglib代理

1、介紹

  目標對象不需要實現介面。靜態代理和JDK代理都要求目標對象實現一個介面。但是有時候目標對象只是一個單獨的對象,並沒有實現任何的介面,這個時候可使用目標對象子類來實現代理,即Cglib代理。
  Cglib代理也叫作子類代理,它是記憶體中構建一個子類對象從而實現對目標對象功能擴展,Cglib代理也可以歸屬到動態代理。
  Cglib是一個強大的高性能的程式碼生成包,它可以在運行期擴展Java類與實現Java介面。它廣泛的被許多AOP框架使用,例如Spring AOP,實現方法攔截。
  在AOP編程中如何選擇代理模式:①目標對象需要實現介面:用JDK代理。②目標對象不需要實現介面:用Cglib代理。
  Cglib包的底層是通過使用位元組碼處理框架ASM來轉換位元組碼並生成新的類。

2、程式碼

  程式碼示例:動態代理,cglib代理

 1 // 目標對象.未實現介面
 2 public class Teacher {
 3 
 4     public void teach() {
 5         System.out.println("老師正在上課");
 6     }
 7 
 8     public int add(int i, int j) {
 9         System.out.println("老師正在做加法");
10         return i + j;
11     }
12 }
13 
14 // 代理對象
15 public class ProxyFactory implements MethodInterceptor {
16     private final Object target;
17 
18     public ProxyFactory(Object target) {
19         this.target = target;
20     }
21 
22     public Object getProxyInstance() {
23         // 1.創建一個工具類
24         Enhancer enhancer = new Enhancer();
25         // 2.設置父類
26         enhancer.setSuperclass(target.getClass());
27         // 3.設置回調函數
28         enhancer.setCallback(this);
29         // 4.創建子類對象,即代理對象
30         return enhancer.create();
31     }
32 
33     @Override
34     public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
35         System.out.println("cglib代理模式開始...");
36         Object o1 = method.invoke(target, objects);
37         System.out.println("cglib代理模式結束...");
38 
39         return o1;
40     }
41 }
42 
43 public class Main {
44     public static void main(String[] args) {
45         // 創建目標對象
46         Teacher teacher = new Teacher();
47         
48         // 生成目標對象的代理對象
49         Teacher teacherProxy = (Teacher) new ProxyFactory(teacher).getProxyInstance();
50 
51         int add = teacherProxy.add(1, 2);
52         System.out.println("----" + add);
53     }
54 }

  注意:cglib代理在記憶體中動態構建子類,代理的類不能為final,否則報錯java.lang.IllegalArgumentException。
  目標對象的方法如果為final/static,就不會被代理,即不會執行目標對象額外的業務方法。

3、應用

  性能開發:比如,監控、統計、鑒權、限流、事務、冪等、日誌。將這些性能類功能與業務邏輯功能解耦,放到代理類中實現,讓程式設計師更專註於業務方面的開發。典型例子就是 SpringAOP。
  RPC:遠程代理框架也可以看作一種代理模式。通過遠程代理,將網路通訊、數據編解碼等細節隱藏起來。客戶端在使用 RPC 服務的時候,就像使用本地函數一樣,無需了解跟伺服器交互的細節。除此之外,RPC 服務的開發者也只需要開發業務邏輯,就像開發本地使用的函數一樣,不需要關注跟客戶端的交互細節。
  防火牆代理:內網通過代理穿透防火牆,實現對公網的訪問。
  快取代理:比如,當請求圖片文件等資源時,先到快取代理取,如果取到資源則ok;如果取不到資源,再到公網或者資料庫取,然後快取。
  同步代理:主要使用在多執行緒編程中,完成多執行緒間同步工作。