Java 中的靜態代理與動態代理
什麼是代理模式
人話來講就是由代理物件來執行目標物件的方法,且還可以在代理物件中增強目標物件方法的一種設計模式。類比生活,像是房產中介。代理模式存在的意義和一個架構設計原則息息相關 —— 開閉原則(對擴充套件開放,對修改關閉),即一種好的設計模式,都是在不修改原有形態的基礎上擴展出新的功能。
為什麼需要代理
代理模式的概念很容易理解,但是早期的我即使讀懂了代理模式的概念,對為什麼要使用代理模式,還是一頭霧水。為什麼我不直接呼叫目標物件的方法,非得要藉助個代理物件呢?
1. 呼叫的目標物件在遠端主機上,並不在你本地
類似中介就是房東出國了,聯絡不上,你只能跟我溝通。對應到我們程式設計的時候就是:客戶端無法直接操作實際目標物件。為什麼無法直接操作?一種情況是你需要呼叫的物件在另外一臺機器上,你需要跨越網路才能訪問,如果讓你直接編碼實現遠端呼叫,你需要處理網路連線、處理打包、解包等等非常複雜的步驟,所以為了簡化客戶端的處理,我們使用代理模式,在客戶端建立一個遠端目標物件的代理,客戶端就象呼叫本地物件一樣呼叫該代理,再由代理去跟實際物件聯絡,對於客戶端來說背後這個通訊過程是透明的。
2. 你不想理會目標類繁雜的功能,只希望增加一些自己的行為進去
例如常見的例子就是 Spring AOP 實現日誌功能,你不必關心目標類究竟如何繁雜,你只是想要在前後呼叫的時候列印一下日誌,那麼你就可以使用代理模式,通過 AOP 提供的切面進行編碼實現,你通過代理模式達到了在目標物件的方法前後增加了一些自定義行為的目的。類似的例子還有許可權校驗。這樣做的好處有很多,一方面你需要在意目標類的程式碼,二來你維護了目標類功能的單一性,而不是將日誌或者許可權校驗的功能硬編碼到目標類的方法中。
靜態代理
靜態代理非常簡單,就是實現類和代理類均實現同樣的介面,然後在代理類中通過構造器將介面或者實現類注入進來,然後就可以在代理類的方法實現中增加一些自己的邏輯。看個
靜態代理的例子
// 介面
public interface BuyHouse {
void buyHosue();
}
// 實現類
public class BuyHouseImpl implements BuyHouse {
@Override
public void buyHosue() {
System.out.println("HHHHH");
}
}
// 代理類
public class BuyHouseProxy implements BuyHouse {
private BuyHouse buyHouse;
// 將介面引入
public BuyHouseProxy(final BuyHouse buyHouse) {
this.buyHouse = buyHouse;
}
@Override
public void buyHosue() {
// 增加一些自己的邏輯
System.out.println("HHHHH");
buyHouse.buyHosue();
System.out.println("66666");
}
}
靜態代理的缺點
很明顯,靜態代理中,一個代理類只能對一個業務介面的實現類進行包裝,如果有多個業務介面的話就要定義很多實現類和代理類才行。而且,如果代理類對業務方法的預處理、呼叫後操作都是一樣的(比如:呼叫前輸出提示、呼叫後自動關閉連線),則多個代理類就會有很多重複程式碼。這時我們可以定義這樣一個代理類,它能代理所有實現類的方法呼叫:根據傳進來的業務實現類和方法名進行具體呼叫。即動態代理模式。Java 中常見的有 JDK 動態代理和 CGLib 動態代理,前者只能代理介面,後者可以代理實現類。
JDK 動態代理
JDK 的動態代理使用到 Java reflect 包下的 Proxy 類和 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 {
System.out.println("HHHHH");
Object result = method.invoke(object, args);
System.out.println("66666");
return result;
}
}
public class DynamicProxyTest {
public static void main(String[] args) {
BuyHouse buyHouse = new BuyHouseImpl();
BuyHouse proxyBuyHouse = (BuyHouse) Proxy.newProxyInstance(BuyHouse.class.getClassLoader(), new Class[]{BuyHouse.class}, new DynamicProxyHandler(buyHouse));
proxyBuyHouse.buyHosue();
}
}
DynamicProxyHandler 實現了 InvocationHandler 介面,並複寫其 invoke 方法,我們可以看到 invoke 方法的引數是實現類和方法引數列表。測試類中通過 newProxyInstance 這個靜態工廠方法建立了代理物件,代理物件的每個執行方法都會替換執行InvocationHandler 中的 invoke 方法。這個方法總共有3個引數:
- ClassLoader loader用來指明生成代理物件使用哪個類裝載器
- Class<?>[] interfaces用來指明生成哪個物件的代理物件,通過介面指定,這就是為什麼 JDK 動態代理必須要通過介面的方式
- InvocationHandler 用來指明產生的這個代理物件要做什麼事情。
newProxyInstance 內部本質上是根據反射機制生成了一個新類。
CGLib 動態代理
CGLib 是針對類來實現代理的,原理是對指定的實現類生成一個子類,並覆蓋其中的方法實現代理。因為採用的是繼承,所以不能對final 修飾的類進行代理。例子 如下:
// 實現類,有沒有實現介面無所謂
public class BookFacadeImpl {
public void addBook() {
System.out.println("新增圖書...");
}
}
public class BookFacadeCglib implements MethodInterceptor {
// 業務類物件,供代理方法中進行真正的業務方法呼叫
private Object target;
// 相當於JDK動態代理中的繫結
public Object getInstance(Object target) {
// 給業務物件賦值
this.target = target;
// 建立加強器,用來建立動態代理類
Enhancer enhancer = new Enhancer();
// 為加強器指定要代理的業務類(即:為下面生成的代理類指定父類)
enhancer.setSuperclass(this.target.getClass());
// 設定回撥:對於代理類上所有方法的呼叫,都會呼叫CallBack,而Callback則需要實現intercept()方法進行攔
enhancer.setCallback(this);
// 建立動態代理類物件並返回
return enhancer.create();
}
// 實現回撥方法
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy)
throws Throwable {
System.out.println("預處理——————");
//呼叫業務類(父類中)的方法
proxy.invokeSuper(obj, args);
System.out.println("呼叫後操作——————");
return null;
}
}
// 測試類
public static void main(String[] args) {
BookFacadeImpl bookFacade = new BookFacadeImpl();
BookFacadeCglib cglib = new BookFacadeCglib();
BookFacadeImpl bookCglib = (BookFacadeImpl)cglib.getInstance(bookFacade);
bookCglib.addBook();
}
參考資料
License
- 本文遵守創作共享 CC BY-NC-SA 3.0協議