一文搞懂設(shè)計模式—代理模式
代理模式(Proxy Pattern)是一種結(jié)構(gòu)型設(shè)計模式,也叫做委托模式,它允許你提供一個間接訪問對象的方式。
用一句話描述代理模式就是:為其他對象提供一種代理以控制對這個對象的訪問
使用場景
- 遠程代理(Remote Proxy):用于在不同地址空間中代表對象,使得客戶端可以訪問遠程的對象。
- 虛擬代理(Virtual Proxy):用于按需創(chuàng)建昂貴對象的代表,延遲對象的實例化,提高系統(tǒng)性能。
- 保護代理(Protection Proxy):用于控制對真實對象的訪問權(quán)限,在訪問真實對象之前進行安全檢查。
- 智能引用(Smart Reference):用于在訪問對象時執(zhí)行額外的操作,如引用計數(shù)、懶加載等。
- 日志記錄(Logging Proxy):用于記錄方法調(diào)用的日志信息,方便調(diào)試和監(jiān)控系統(tǒng)運行狀態(tài)。
- 權(quán)限控制(Access Control Proxy):用于控制用戶對對象的訪問權(quán)限,限制某些用戶的操作。
- 延遲加載(Lazy Loading Proxy):用于延遲加載對象的數(shù)據(jù),直到真正需要使用時才進行加載。
代理模式在Java中的Spring框架和Dubbo框架中都有廣泛的應(yīng)用:
- Spring框架中的AOP(面向切面編程):Spring使用代理模式實現(xiàn)AOP功能,允許開發(fā)者定義切面(Aspect),并通過代理機制將切面織入到目標對象的方法調(diào)用中,實現(xiàn)橫切關(guān)注點的管理,如日志記錄、事務(wù)管理等。
- Dubbo框架中的遠程服務(wù)代理:Dubbo是一種高性能的分布式服務(wù)框架,其中的服務(wù)消費者與服務(wù)提供者之間的通信通過代理模式來實現(xiàn)。Dubbo會根據(jù)配置信息動態(tài)生成接口的代理實現(xiàn)類,在遠程調(diào)用時通過代理對象進行通信,隱藏了遠程調(diào)用的復雜性,使得調(diào)用方可以像調(diào)用本地方法一樣調(diào)用遠程服務(wù)。
通過代理模式,可以實現(xiàn)對對象的訪問控制、附加功能增強、性能優(yōu)化等目的,提高系統(tǒng)的靈活性、可維護性和可擴展性。
具體實現(xiàn)
代理模式涉及以下幾個角色:
- 抽象主題(Subject):是一個接口或抽象類,定義了真實主題和代理對象共同實現(xiàn)的方法,客戶端通過抽象主題訪問真實主題。
- 真實主題(Real Subject):是真正執(zhí)行業(yè)務(wù)邏輯的對象,實現(xiàn)了抽象主題定義的方法,是代理模式中被代理的對象。
- 代理(Proxy):持有對真實主題的引用,可以控制對真實主題的訪問,在其自身的方法中可以調(diào)用真實主題的方法,同時也可以在調(diào)用前后執(zhí)行一些附加操作。
實現(xiàn)代理模式步驟如下:
首先定義一個接口:
public interface Subject {
void request();
}
然后實現(xiàn)真實主題類:
public class RealSubject implements Subject {
@Override
public void request() {
System.out.println("Real Subject handles the request.");
}
}
接著創(chuàng)建代理類:
public class Proxy implements Subject {
private RealSubject realSubject;
@Override
public void request() {
if (realSubject == null) {
realSubject = new RealSubject();
}
preRequest();
realSubject.request();
postRequest();
}
//前置處理
private void preRequest() {
System.out.println("Proxy performs pre-request actions.");
}
//后置處理
private void postRequest() {
System.out.println("Proxy performs post-request actions.");
}
}
客戶端調(diào)用:
public static void main(String[] args) {
Proxy proxy = new Proxy();
proxy.request();
}
輸出:
Proxy performs pre-request actions.
Real Subject handles the request.
Proxy performs post-request actions.
Tips:一個代理類,可以代理多個真實角色,并且真實角色之間允許有耦合關(guān)系。
普通代理 & 強制代理
在代理模式中,可以區(qū)分普通代理和強制代理:
- 普通代理(Normal Proxy):由代理類控制對真實主題的訪問,客戶端直接與代理類交互,代理類負責將請求轉(zhuǎn)發(fā)給真實主題,調(diào)用者只知代理而不用知道真實的角色是誰,屏蔽了真實角色的變更對高層模塊的影響。
- 強制代理(Force Proxy):“強制”必須通過真實角色查找到代理角色,否則不能訪問。并且只有通過真實角色指定的代理類才可以訪問,也就是說由真實角色管理代理角色。強制代理不需要產(chǎn)生一個代理出來,代理的管理由真實角色自己完成。
上面提供的代碼例子就是普通代理,下面用代碼演示下強制代理:
// 抽象主題接口
public interface Subject {
/**
* 待具體實現(xiàn)的方法
*/
void request();
/**
* 獲取每個具體實現(xiàn)對應(yīng)的代理對象實例
* @return 返回對應(yīng)的代理對象
*/
Subject getProxy();
}
// 強制代理對象
public class ForceProxy implements Subject {
private Subject subject;
public ForceProxy(Subject subject) {
this.subject = subject;
}
/**
* 待具體實現(xiàn)的方法
*/
@Override
public void request() {
preRequest();
subject.request();
postRequest();
}
/**
* @return 返回對應(yīng)的代理對象就是自己
*/
@Override
public Subject getProxy() {
return this;
}
private void postRequest() {
System.out.println("訪問真實主題以后的后續(xù)處理");
}
private void preRequest() {
System.out.println("訪問真實主題之前的預處理");
}
}
// 具體的實現(xiàn)對象
public class RealSubject implements Subject {
/**
* 該具體實現(xiàn)對象的代理對象
*/
private Subject proxy;
@Override
public Subject getProxy() {
proxy = new ForceProxy(this);
return proxy;
}
/**
* 待具體實現(xiàn)的方法
*/
@Override
public void request() {
if (isProxy()) {
System.out.println("訪問真實主題方法");
} else {
System.out.println("請使用指定的代理訪問");
}
}
private boolean isProxy() {
return proxy != null;
}
}
客戶端調(diào)用:
public static void main(String[] args) {
Subject subject = new RealSubject();
subject.request();
}
Output:
請使用指定的代理訪問
public static void main(String[] args) {
Subject subject = new RealSubject();
Subject proxy = new ForceProxy(subject);
proxy.request();
}
Output:
訪問真實主題之前的預處理
請使用指定的代理訪問
訪問真實主題以后的后續(xù)處理
public static void main(String[] args) {
Subject subject = new RealSubject();
Subject proxy = subject.getProxy();
proxy.request();
}
Output:
訪問真實主題之前的預處理
訪問真實主題方法
訪問真實主題以后的后續(xù)處理
通過代碼可以觀察到,強制代理模式下,不允許通過真實角色來直接訪問,只有通過真實角色來獲取代理對象,才能訪問。
高層模塊只需調(diào)用getProxy就可以訪問真實角色的所有方法,它根本就不需要產(chǎn)生一個代理出來,代理的管理已經(jīng)由真實角色自己完成。
動態(tài)代理
前面講的普通代理和強制代理都屬于靜態(tài)代理,也就是說自己寫代理類的方式就是靜態(tài)代理。
靜態(tài)代理有一個缺點就是要在實現(xiàn)階段就要指定代理類以及被代理者,很不靈活。
而動態(tài)代理是一種在運行時動態(tài)生成代理類的機制,可以在不預先知道接口的情況下動態(tài)創(chuàng)建接口的實現(xiàn)類,允許在運行階段才指定代理哪一個對象,比如Spring AOP就是非常經(jīng)典的動態(tài)代理的應(yīng)用
下面是兩個動態(tài)代理常用的實現(xiàn)方式:
- JDK 動態(tài)代理 :基于 Java 反射機制,在運行時動態(tài)創(chuàng)建代理類和對象。JDK 動態(tài)代理要求被代理的類實現(xiàn)一個或多個接口,通過 java.lang.reflect.Proxy 和 java.lang.reflect.InvocationHandler 接口來實現(xiàn)代理對象的生成和方法調(diào)用。
- CGLIB 動態(tài)代理:不要求被代理的類實現(xiàn)接口,通過繼承被代理類來生成代理對象。CGLIB 使用字節(jié)碼生成庫ASM來動態(tài)生成代理類,因此性能略高于 JDK 動態(tài)代理。
JDK動態(tài)代理
JDK實現(xiàn)動態(tài)代理的核心機制就是java.lang.reflect.Proxy類和java.lang.reflect.InvocationHandler接口。
JDK動態(tài)代理的動態(tài)代理類需要去實現(xiàn)JDK自帶的java.lang.reflect.InvocationHandler接口,該接口中的invoke()方法能夠讓動態(tài)代理類實例在運行時調(diào)用被代理類需要對外實現(xiàn)的所有接口中的方法,也就是完成對真實主題類方法的調(diào)用。
具體實現(xiàn)步驟如下:
- 創(chuàng)建一個接口Subject表示被代理的對象需要實現(xiàn)的方法。
- 創(chuàng)建一個真實主題類RealSubject,實現(xiàn)Subject接口,定義真正的業(yè)務(wù)邏輯。
- 創(chuàng)建一個實現(xiàn)InvocationHandler接口的代理處理器類DynamicProxyHandler,在invoke方法中執(zhí)行額外的操作,并調(diào)用真實主題的方法。
- 在主程序中使用Proxy.newProxyInstance()方法動態(tài)生成代理對象,并調(diào)用代理對象的方法。
下面是動態(tài)代理的示例代碼,一起來感受一下:
// 1. 創(chuàng)建接口
public interface Subject {
void request();
}
// 2. 創(chuàng)建真實主題類
public class RealSubject implements Subject {
@Override
public void request() {
System.out.println("RealSubject handles the request.");
}
}
// 3. 創(chuàng)建代理處理器類
public class DynamicProxyHandler implements InvocationHandler {
private Object target;
DynamicProxyHandler(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 執(zhí)行額外操作
System.out.println("Before requesting...");
// 調(diào)用真實主題對象的方法
Object result = method.invoke(target, args);
// 執(zhí)行額外操作
System.out.println("After requesting...");
return result;
}
}
public class DynamicProxyExample {
public static void main(String[] args) {
// 創(chuàng)建真實主題對象
Subject realSubject = new RealSubject();
// 創(chuàng)建代理處理器對象
InvocationHandler handler = new DynamicProxyHandler(realSubject);
// 創(chuàng)建動態(tài)代理對象
Subject proxy = (Subject) Proxy.newProxyInstance(
realSubject.getClass().getClassLoader(),
realSubject.getClass().getInterfaces(),
handler);
// 調(diào)用代理對象的方法
proxy.request();
}
}
這段代碼演示了使用 JDK 動態(tài)代理實現(xiàn)動態(tài)代理的過程:
- 首先,創(chuàng)建了一個真實主題對象 realSubject,表示被代理的真實對象。
- 接著,創(chuàng)建了一個代理處理器對象 handler,類型為 InvocationHandler,并將真實主題對象傳入代理處理器中,用于處理代理對象的方法調(diào)用。
- 然后,通過 Proxy.newProxyInstance() 方法創(chuàng)建了一個動態(tài)代理對象 proxy,該方法接受三個參數(shù):
- 類加載器:使用真實主題對象的類加載器。
- 接口數(shù)組:指定代理對象需要實現(xiàn)的接口,這里使用真實主題對象的接口數(shù)組。
- 處理器:指定代理對象的調(diào)用處理器,即前面創(chuàng)建的代理處理器對象 handler。
- 最后,通過代理對象 proxy 調(diào)用 request() 方法,實際上會委托給代理處理器 handler 的 invoke() 方法來處理方法調(diào)用,進而調(diào)用真實主題對象的對應(yīng)方法。
這段代碼通過 JDK 動態(tài)代理機制實現(xiàn)了代理對象的動態(tài)創(chuàng)建和方法調(diào)用處理,實現(xiàn)了對真實主題對象的間接訪問,并在調(diào)用真實主題對象方法前后進行了額外的處理。
其動態(tài)調(diào)用過程如圖所示:
cglib動態(tài)代理
JDK的動態(tài)代理機制只能代理實現(xiàn)了接口的類,否則不能實現(xiàn)JDK的動態(tài)代理,具有一定的局限性。
CGLIB(Code Generation Library)是一個功能強大的字節(jié)碼生成庫,可以用來在運行時擴展Java類和實現(xiàn)動態(tài)代理。
相對于JDK動態(tài)代理基于接口的代理,cglib動態(tài)代理基于子類的代理,可以代理那些沒有接口的類,通俗說cglib可以在運行時動態(tài)生成字節(jié)碼。
cglib的原理是對指定的目標類生成一個子類,并覆蓋其中方法實現(xiàn)增強,因為采用的是繼承,所以不能對final修飾符的類進行代理。
下面是一個使用cglib實現(xiàn)動態(tài)代理的示例代碼,包括實現(xiàn)步驟:
- 創(chuàng)建一個真實主題類RealSubject,無需實現(xiàn)任何接口。
- 創(chuàng)建一個實現(xiàn)MethodInterceptor接口的代理處理器類DynamicProxyHandler,在intercept方法中執(zhí)行額外的操作,并調(diào)用真實主題的方法。
- 在主程序中使用Enhancer類創(chuàng)建代理對象,并設(shè)置代理處理器。
使用 cglib 需要添加對應(yīng)的依賴:
<!-- https://mvnrepository.com/artifact/cglib/cglib -->
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.3.0</version>
</dependency>
// 1. 創(chuàng)建真實主題類
public class RealSubject {
public void request() {
System.out.println("RealSubject handles the request.");
}
}
// 2. 創(chuàng)建代理處理器類
public class DynamicProxyHandler implements MethodInterceptor {
/**
* 通過Enhancer 創(chuàng)建代理對象
*/
private Enhancer enhancer = new Enhancer();
/**
* 通過class對象獲取代理對象
* @param clazz class對象
* @return 代理對象
*/
public Object getProxy(Class<?> clazz) {
// 設(shè)置需要代理的類
enhancer.setSuperclass(clazz);
// 設(shè)置enhancer的回調(diào)
enhancer.setCallback(this);
return enhancer.create();
}
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
// 執(zhí)行額外操作
System.out.println("Before requesting...");
// 調(diào)用真實主題對象的方法
Object result = proxy.invokeSuper(obj, args);
// 執(zhí)行額外操作
System.out.println("After requesting...");
return result;
}
}
public class CglibProxyExample {
public static void main(String[] args) {
DynamicProxyHandler proxy = new DynamicProxyHandler();
RealSubject realSubject = (RealSubject) proxy.getProxy(RealSubject.class);
// 調(diào)用代理對象的方法
realSubject.request();
}
}
輸出:
Before requesting...
RealSubject handles the request.
After requesting...
cglib動態(tài)代理相比于JDK動態(tài)代理的優(yōu)缺點如下:
優(yōu)點:
- 可以代理沒有實現(xiàn)接口的類。
- 性能更高,因為直接操作字節(jié)碼,無需反射。
缺點:
- 生成的代理類會繼承被代理類,可能會影響某些設(shè)計。
- 無法代理static方法,因為cglib是基于繼承來生成代理類的,而靜態(tài)方法是屬于類而非對象的
- 對于final方法,cglib無法覆蓋,仍然會調(diào)用父類方法。
總結(jié)
代理模式是一種常用的設(shè)計模式,在軟件開發(fā)中有著廣泛的應(yīng)用。通過引入代理對象,可以實現(xiàn)對真實對象的訪問控制、附加功能增強、性能優(yōu)化等目的。
優(yōu)點
- 可以控制對真實對象的訪問,在不改變原始類代碼的情況下擴展其行為。
- 代理模式能將客戶端與目標對象分離,在一定程序上降低了系統(tǒng)的耦合度.
缺點
- 增加了系統(tǒng)復雜性,引入了多余的代理類,因此有些類型的代理模式可能會造成請求的處理速度變慢。
- 實現(xiàn)代理模式需要額外的工作,有些代理模式的實現(xiàn)非常復雜。
總的來說,代理模式通過引入代理對象,實現(xiàn)了對真實對象的間接訪問和控制,為系統(tǒng)的設(shè)計提供了一種簡潔而有效的解決方案。在日常的軟件開發(fā)中,合理地運用代理模式可以為系統(tǒng)帶來更好的結(jié)構(gòu)和性能表現(xiàn)。