設計模式(四)——代理模式
一、概述
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;如果取不到資源,再到公網或者資料庫取,然後快取。
同步代理:主要使用在多執行緒編程中,完成多執行緒間同步工作。