目錄
一 代理模式
1.1 簡介
Java的動態代理在實踐中有着廣泛的使用場景,比如最場景的Spring AOP、Java註解的獲取、日誌、用戶鑑權等。
先看百度百科的定義:
代理模式的定義:爲其他對象提供一種代理以控制對這個對象的訪問。在某些情況下,一個對象不適合或者不能直接引用另一個對象,而代理對象可以在客戶端和目標對象之間起到中介的作用。
代理模式解釋起來就是,設定指定的“代理人(Proxy代理類)”執行RealSubject類中的具體方法。我們對RealSubject類中的左右操作都通過“代理人(Proxy代理類)調用RealSubject類進行執行。
通過代理模式,我們可以做到兩點:
1、隱藏代理類的具體實現(RealSubject類中才是具體實現)。
2、實現客戶與代理類的解耦,可以在不改變代理類代碼的情況下添加一些額外的功能(日誌、權限)等。
1.2 代理模式角色定義
在上述的過程中在編程的過程中我們可以定義爲三類對象:
- Subject(抽象主題角色):一般爲接口,定義代理類和真實主題的公共對外方法,也是代理類代理真實主題的方法。比如:出售商品等。
- RealSubject(真實主題角色):真正實現業務邏輯的類。比如實現了廣告、出售等方法的廠家(Factoryer)。
- Proxy(代理主題角色):用來代理和封裝真實主題。比如,同樣實現了廣告、出售等方法的超時(Shop)。
以上三個角色對應的類圖如下:
二 靜態代理
2.1 介紹和實例
靜態代理是指代理類在程序運行前就已經存在,這種情況下的代理類通常都是我們在Java代碼中定義的。
靜態代理例子:工廠主(Factoryer)需要執行的工廠(IFactory)的主要function就是賣工廠的貨(sell方法),我們現在需要委託商店(shop)這個代理來代賣貨物。顧客也就是client是通過商店shop這個代理買東西的。
IFactory接口定義如下:
/**
* Subject 接口
* IFactory 定義了要做的事情,就是賣東西,但是工廠是委託給商店代賣的
* 委託類和代理類都實現了IFactory接口
**/
public interface IFactory {
/** 出售 */
void sell();
}
Vendor類定義如下
/**
* RealSubject類
* 真實主題類,是客戶端通過代理類間接調用的 真正實現具體需要實現方法的主體。
* 工廠主,他要賣東西,他是Factory這個抽象主體的具體實現,他要賣東西
**/
public class Factoryer implements IFactory{
@Override
public void sell() {
System.out.println("出售貨物");
}
}
Shop類定義如下:
/**
* Proxy代理類
* 超市,代理賣東西
**/
public class Shop implements IFactory{
private Factoryer factoryer;
public Shop(Factoryer factoryer){
this.factoryer = factoryer;
}
@Override
public void sell() {
System.out.println("Shop是商店代理,用於直接對接client顧客,執行sell方法");
factoryer.sell();
}
}
其中代理類Shop通過聚合的方式持有了被代理類Factoryer的引用,並在對應的方法中調用Factoryer對應的方法。
下面看看在客戶端中如何使用代理類,即客人怎麼通過這套代理體系買東西。
public class ClientBuy {
public static void main(String[] args) {
// 被代理類
Factoryer factoryer = new Factoryer();
// 創建供應商(也就是真正執行賣東西這個方法操作的factoryer)的代理類Shop
IFactoryer ifactoryer = new Shop(factoryer);
// 客人買東西時面向的是代理類Shop。
ifactoryer.sell();
}
}
在上述代碼中,我們可以在Shop中修改或新增一些內容,而不影響被代理類Factoryer 。比如我們可以在Shop類中新增一些額外的處理,類似於篩選購買用戶、記錄日誌等操作。
2.2 靜態代理的缺點
靜態代理實現簡單且不侵入原代碼,但當場景複雜時,靜態代理會有以下缺點:
1、當需要代理多個類時,代理對象要實現與目標對象一致的接口。我們只有兩個選擇,1:只維護一個代理類來實現多個接口,但這樣會導致代理類過於龐大;2:新建多個代理類,但這樣會產生過多的代理類。
2、當接口(IFactoryer)需要增加、刪除、修改方法時,目標對象(ClientBuy)與代理類(shop)都要同時修改,不易維護。
於是,動態代理便派上用場了。
三 動態代理
動態代理是指代理類在程序運行時進行創建的代理方式。這種情況下,代理類並不是在Java代碼中定義的,而是在運行時根據Java代碼中的“指示”動態生成的。
相比於靜態代理,動態代理的優勢在於可以很方便的對代理類的函數進行統一的處理,而不用修改每個代理類的函數。
3.1 基於JDK原生動態代理實現
實現動態代理通常有兩種方式:JDK原生動態代理和CGLIB動態代理。這裏,我們以JDK原生動態代理爲例來進行講解。
JDK動態代理主要涉及兩個類:java.lang.reflect.Proxy和java.lang.reflect.InvocationHandler。
InvocationHandler接口定義瞭如下方法:
/**
* 調用處理程序
*/
public interface InvocationHandler {
Object invoke(Object proxy, Method method, Object[] args);
}
顧名思義,實現了該接口的中介類用做“調用處理器”。當調用代理類對象的方法時,這個“調用”會轉送到invoke方法中。動態代理中,Proxy 動態產生的代理會調用 InvocationHandler 實現類,所以 InvocationHandler 是實際執行者。
InvocationHandler方法的,第一個參數proxy參數爲代理類對象,第二個參數爲method,表示具體調用的是代理類的哪個方法,第三個參數args爲爲第二個參數該方法的參數。
這樣對代理類中的所有方法的調用都會變爲對invoke的調用,可以在invoke方法中添加統一的處理邏輯(也可以根據method參數對不同的代理類方法做不同的處理)。
下面以添加日誌爲例來演示一下動態代理。
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.util.Date;
public class LogHandler implements InvocationHandler {
Object target; // 被代理的對象,實際的方法執行者
public LogHandler(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
before();
Object result = method.invoke(target, args); // 調用 target 的 method 方法
after();
return result; // 返回方法的執行結果
}
// 調用invoke方法之前執行
private void before() {
System.out.println("調用方法sell之前的日誌處理");
}
// 調用invoke方法之後執行
private void after() {
System.out.println("調用方法sell之後的日誌處理");
}
}
客戶端編寫程序使用動態代理代碼如下:
import java.lang.reflect.Proxy;
/**
* 動態代理測試
*
* @author sec
* @version 1.0
* @date 2020/3/21 10:40 AM
**/
public class DynamicClientBuy {
public static void main(String[] args) {
// 創建中介類實例
LogHandler logHandler = new LogHandler(new Factoryer());
// 獲取代理類實例IFactory
IFactory iFactory = (IFactory) (Proxy.newProxyInstance(
IFactory.class.getClassLoader(),
new Class[]{IFactory.class},
logHandler)
);
// 通過代理類對象調用代理類方法,實際上會轉到invoke方法調用
iFactory.sell();
}
}
Proxy.newProxyInstance的三個參數含義爲:
- loader 自然是類加載器
- interfaces 代碼要用來代理的接口
- h 一個 InvocationHandler 對象
執行之後,打印日誌如下:
調用方法sell之前的日誌處理
Shop sell goods
調用方法sell之後的日誌處理
經過上述驗證,我們發現已經成功爲我們的被代理類統一添加了執行方法之前和執行方法之後的日誌。
四 小結
瞭解代理模式可以讓我們的系統設計的更加具有可擴展性。而動態代理的應用就更廣了,各類框架及業務場景都在使用。
Java動態代理的優勢是實現無侵入式的代碼擴展,也就是方法的增強:不改變這個方法的前提下去豐富它;你可以在不用修改源碼的情況下,增強一些方法;在方法的前後你可以做你任何想做的事情(甚至不去執行這個方法就可以)。老功能用原來的方法不會有問題,新功能通過代理豐富了原來的方法也能被廣泛試用。
動態代理類:在程序運行時,通過反射機制動態生成。
動態代理類通常代理接口下的所有類。
動態代理事先不知道要代理的是什麼,只有在運行的時候才能確定。
動態代理的調用處理程序必須事先InvocationHandler接口,及使用Proxy類中的newProxyInstance方法動態的創建代理類。
Java動態代理只能代理接口,要代理類需要使用第三方的CGLIB等類庫
掌握到這裏其實已經差不多了,關於更多一些例子包括源碼的一些分析,這邊文章寫得很好輕鬆學,Java 中的代理模式及動態代理,我就不再畫蛇添足了,建議看完本篇後快速看下這篇的一些代碼例子(多代理的例子),心中會更加有數。
參考:
https://www.choupangxia.com/2020/03/21/java-dynamic-proxy/
https://blog.csdn.net/lovejj1994/article/details/78080124
https://blog.csdn.net/yaomingyang/article/details/81053130