設計模式之代理模式(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 生成的代理類需要重寫代理類中所有的方法