代理模式(Proxy Pattern),透過一個代理類,來支援原本想呼叫類的功能,並能在原有類的基礎上,附加其他功能。也就是說我原本想呼叫A類,但是現在我改成呼叫B類,並且B類擁有完整的A的功能,對呼叫方依然可以拿到A的結果,而且還可以在呼叫前後附加其他功能,或者是修改結果。
但是為什麼這麼做呢?
透過代理類,可以在呼叫A類目標方法的前後,附加一些功能,比如在A類的business()方法前後記錄日誌、記錄時間進行效能監控等。
這樣就能讓我們即擁有A的完整功能,又能擴展出其他我們需要的功能。
總結來說:代理模式將和業務不相關的功能,剝離出來,交給其他角兒來完成,從而使我們的業務類更專注與業務程式碼。
代理類分為靜態代理和動態代理。
我們先來看看靜態代理。
我們在demo中新增兩個基於使用者的方法,一個是使用者登入,一個是模擬一個使用者業務計算的方法。其中使用者登入,我們讓代理類除了登入以外,還附加日誌記錄功能,使用者每次登入時都進行日誌記錄;另一個業務計算方法,我們讓代理類另外完成效能監控功能。
1、建立抽象使用者類
package com.my.study.design.proxy;/** * www.itzhimei.com */public interface IUser { void login(String userName,String pwd); void business() throws InterruptedException;}
2、實現使用者類
package com.my.study.design.proxy;/** * www.itzhimei.com */public class User implements IUser { @Override public void login(String userName,String pwd) { System.out.println("使用者["+userName +"]登入."); } @Override public void business() throws InterruptedException { System.out.println("執行業務方法計算中...."); Thread.sleep(500L); System.out.println("執行業務方法計算完成"); }}
3、定義代理類,代理類也實現抽象使用者方法,讓代理類看起來和使用者類是一樣的,具備相同能力。
package com.my.study.design.proxy;/** * www.itzhimei.com */public class Proxy implements IUser { private IUser user; public Proxy(IUser user) { this.user = user; } @Override public void login(String userName, String pwd) { //登入時進行日誌列印 System.out.println("使用者["+userName +"]登入日誌記錄開始..."); user.login(userName, pwd); System.out.println("使用者["+userName +"]登入日誌記錄結束..."); } @Override public void business() throws InterruptedException { long startTimestamp = System.currentTimeMillis(); user.business(); long endTimeStamp = System.currentTimeMillis(); long t = endTimeStamp - startTimestamp; System.out.println("使用者執行業務計算總用時:"+ t); }}
在代理類中,持有了一個User物件,真正的使用者業務,代理類會呼叫使用者類來處理,代理類在呼叫使用者類之前或之後附加其他功能。
4、測試
package com.my.study.design.proxy;/** * www.itzhimei.com */public class Client { public static void main(String[] args) throws InterruptedException { IUser user = new User(); IUser proxy = new Proxy(user); proxy.login("張三","123"); System.out.println("----------------------------------"); proxy.business(); }}
輸出:
使用者[張三]登入日誌記錄開始...使用者類輸出:使用者[張三]登入.代理類輸出:使用者[張三]登入日誌記錄結束...----------------------------------使用者類輸出:執行業務方法計算中....使用者類輸出:執行業務方法計算完成代理類輸出:使用者執行業務計算總用時:500
以上就是一個靜態代理的完整實現。
類關係圖:
靜態代理有一個問題就是,如果被代理的目標類非常多,那我們就要生成非常多的代理類,比如我們上一節的例子中,我要記錄效能的類,如果有100+個,那我就要再寫100多個代理類,這顯然是不合理的。
這是動態代理就登場了。
動態代理就是不用事先為每一個被代理的目標類,一一生成代理類,而是在執行時,按照需要動態生成代理類,來完成代理工作。
Java本身就實現了動態代理功能,其原理是基於反射實現的。
我們來看一下上一節的靜態代理,如何演變為動態代理。
1、定義抽象使用者類
package com.my.study.design.proxy.dynamic;/** * www.itzhimei.com */public interface IUser { void business() throws InterruptedException;}
2、定義具體使用者類,實現抽象使用者類
package com.my.study.design.proxy.dynamic;/** * www.itzhimei.com */public class User implements IUser { @Override public void business() throws InterruptedException { System.out.println("使用者類輸出:執行業務方法計算中...."); Thread.sleep(500L); System.out.println("使用者類輸出:執行業務方法計算完成"); }}
3、定義動態代理的業務處理類,也就是代理的處理邏輯,要寫在這裡
import java.lang.reflect.InvocationHandler;import java.lang.reflect.Method;/** * www.itzhimei.com */public class DynamicProxy implements InvocationHandler { private Object realObject; public DynamicProxy(Object realObject) { this.realObject = realObject; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("動態代理輸出:前置業務方法計算中...."); method.invoke(realObject,args); System.out.println("動態代理輸出:附加業務方法計算中...."); return null; }}
Java的動態代理API要求動態代理的代理處理邏輯,要實現InvocationHandler介面,覆寫invoke()方法。這裡就相當於靜態代理中我們分別實現了login方法和business()方法。
4、測試
/** * www.itzhimei.com */public class Client { public static void main(String[] args) throws InterruptedException { User user = new User(); DynamicProxy handler = new DynamicProxy(user); IUser u = (IUser)Proxy.newProxyInstance(user.getClass().getClassLoader(),user.getClass().getInterfaces(),handler); u.business(); }}
這裡的一個重要方法就是Proxy.newProxyInstance(),這是生成動態代理的類和方法。
5、輸出
動態代理輸出:前置業務方法計算中....使用者類輸出:執行業務方法計算中....使用者類輸出:執行業務方法計算完成動態代理輸出:附加業務方法計算中....
我們看到,動態代理的輸出日誌和handle中定義的invoke是一致的,在實際的業務方法之前和之後執行。