結構型:代理模式及相關應用
文章目錄
代理(Proxy)
代理模式為其它物件提供一種代理,以控制對這個物件的訪問,代理物件在客戶端和目標物件之間起到中介的作用。
我們有多種不同的方式來實現代理。如果按照代理建立的時期來進行分類的話, 可以分為兩種:靜態代理、動態代理。靜態代理是由程式設計師建立或特定工具自動生成原始碼,再對其編譯,在執行之前,代理類.class檔案就已經被建立了。動態代理是在程式執行時通過反射機制動態建立的。
適用場景:
- 保護目標物件
- 增強目標物件
優缺點
優點:能將代理物件與真實被呼叫的目標物件分離;保護目標物件;增強目標物件。
缺點:會造成系統設計中類的數目增加;在客戶端和目標物件增加一個代理物件,會造成請求處理速度變慢;增加系統的複雜度。
應用場景
靜態代理
使用靜態代理可以做到在符合開閉原則的情況下對目標物件進行功能擴充套件,但我們得為每一個服務都建立代理類,工作量太大,不易管理。
服務介面:
public interface BuyHouse {
void buyHosue();
}
實現服務介面:
public class BuyHouseImpl implements BuyHouse {
@Override
public void buyHosue() {
System.out.println("我要買房");
}
}
代理類:
public class BuyHouseProxy implements BuyHouse {
private BuyHouse buyHouse;
public BuyHouseProxy(BuyHouse buyHouse) {
this.buyHouse = buyHouse;
}
@Override
public void buyHosue() {
beforeMethod();
buyHouse.buyHosue();
afterMethod();
}
private void beforeMethod(){
System.out.println("買房前準備");
}
private void afterMethod(){
System.out.println("買房後裝修");
}
}
客戶端類:
public class Test{
public static void main(String[] args) {
BuyHouse buyHouse = new BuyHouseImpl();
buyHouse.buyHosue();
BuyHouseProxy buyHouseProxy = new BuyHouseProxy(buyHouse);
buyHouseProxy.buyHosue();
}
}
動態代理
在動態代理中我們不再需要手動的建立代理類,我們只需要編寫一個動態處理器就可以了。真正的代理物件由JDK再執行時為我們動態的來建立。
動態處理器,實現了InvocationHandler
介面:
public class DynamicProxyHandler implements InvocationHandler {
private Object object;
public DynamicProxyHandler(final Object object) {
this.object = object;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
beforeMethod();
Object result = method.invoke(object, args);
afterMethod();
return result;
}
private void beforeMethod(){
System.out.println("買房前準備");
}
private void afterMethod(){
System.out.println("買房後裝修");
}
}
客戶端類:
public class Test {
public static void main(String[] args) {
BuyHouse buyHouse = new BuyHouseImpl();
BuyHouse proxyBuyHouse = (BuyHouse) Proxy.newProxyInstance(buyHouse .getClass().getClassLoader(), buyHouse.getClass().getInterfaces(), new DynamicProxyHandler(buyHouse));
proxyBuyHouse.buyHosue();
}
}
其中Proxy.newProxyInstance()
方法接受三個引數:
ClassLoader loader
:指定當前目標物件使用的類載入器,獲取載入器的方法是固定的Class<?>[] interfaces
:指定目標物件實現的介面的型別,使用泛型方式確認型別InvocationHandler
:指定動態處理器,執行目標物件的方法時,會觸發事件處理器的方法
動態代理雖然不需要自己手動實現代理類和目標方法,但動態代理目標物件必須有介面,沒有介面不能實現JDK版動態代理。
CGLIB代理
JDK實現動態代理需要實現類通過介面定義業務方法,對於沒有介面的類,如何實現動態代理呢,這就需要CGLib了。CGLib採用了非常底層的位元組碼技術,其原理是通過位元組碼技術為一個類建立子類,並在子類中採用方法攔截的技術攔截所有父類方法的呼叫,順勢織入橫切邏輯。但因為採用的是繼承,所以不能對final修飾的類進行代理。JDK動態代理與CGLib動態代理均是實現Spring AOP的基礎。
CGLIB建立的動態代理物件比JDK建立的動態代理物件的效能更高,但是CGLIB建立代理物件時所花費的時間卻比JDK多得多。所以對於單例的物件,因為無需頻繁建立物件,用CGLIB合適,反之使用JDK方式要更為合適一些。
Spring的代理選擇
- 當Bean有實現介面時,Spring就會用JDK的動態代理。
- 當Bean沒有實現介面時,Spring使用CGlib。
- 可以強制使用CGLib。
參考資料
- 弗里曼. Head First 設計模式 [M]. 中國電力出版社, 2007.
- 慕課網java設計模式精講 Debug 方式+記憶體分析
- 設計模式—代理模式