代理模式是常用的結(jié)構(gòu)型設(shè)計模式之一,當無法直接訪問某個對象或訪問某個對象存在困難時可以通過一個代理對象來間接訪問叛赚,為了保證客戶端使用的透明性俺附,所訪問的真實對象與代理對象需要實現(xiàn)相同的接口。在Java中代理實現(xiàn)分為靜態(tài)代理和動態(tài)代理召调,本文將簡要描述代理模式及其在Java中的實現(xiàn)蛮浑。
代理模式
定義
給某一個對象提供一個代理或占位符沮稚,并由代理對象來控制對原對象的訪問。
結(jié)構(gòu)
注意:上圖的注釋部分是針對Proxy類的request方法障般。
由圖可知挽荡,代理模式存在以下3種角色:
- Subject:被代理類和代理類要實現(xiàn)的公共接口(request())
- ConcreteSubject:要被代理的類定拟,又叫委托類逗嫡,它定義了代理角色所代表的真實對象
- Proxy:代理類,代理對象扮演著中介的角色延窜,增加被代理類的某些功能或去掉了某些服務(wù)
簡單實現(xiàn)
Subject接口:
<pre>
public interface Subject {
void request();
}
</pre>
ConcreteSubject類:
<pre>
public class ConcreteSubject implements Subject {
@Override
public void request() {
System.out.println("=======具體子類的request=======");
}
}
</pre>
Proxy類:
<pre>
public class Proxy implements Subject {
private ConcreteSubject subject;//被代理對象
public Proxy(ConcreteSubject subject){
this.subject=subject;
}
public void preRequest(){
System.out.println("=======代理的preRequest=======");
}
@Override
public void request() {
preRequest();
subject.request();//調(diào)用被代理對象的方法
postRequest();
}
public void postRequest(){
System.out.println("=======代理的postRequest=======");
}
}
</pre>
客戶端測試類:
<pre>
public class Test {
public static void main(String[] args) {
//1.創(chuàng)建被代理類對象
ConcreteSubject subject=new ConcreteSubject();
//2.創(chuàng)建代理對象逆瑞,使用構(gòu)造注入把被代理類對象注入到代理對象中
Proxy proxy=new Proxy(subject);
//3.調(diào)用代理對象的相應(yīng)方法
proxy.request();
}
}
</pre>
運行測試類呆万,應(yīng)該看到如下結(jié)果:
以上谋减,就實現(xiàn)了一個最簡單的代理模式出爹。接下來看一下常見的幾種代理模式及其應(yīng)用場景严就。
常見代理模式及應(yīng)用場景
- 遠程代理(Remote Proxy):為一個位于不同的地址空間的對象提供一個本地的代理對象器罐,這
個不同的地址空間可以是在同一臺主機中,也可是在另一臺主機中铸董,遠程代理又稱為大使
(Ambassador)粟害。- 虛擬代理(Virtual Proxy):如果需要創(chuàng)建一個資源消耗較大的對象,先創(chuàng)建一個消耗相對較
小的對象來表示套鹅,真實對象只在需要時才會被真正創(chuàng)建卓鹿。- 保護代理(Protect Proxy):控制對一個對象的訪問留荔,可以給不同的用戶提供不同級別的使用權(quán)限。
- 緩沖代理(Cache Proxy):為某一個目標操作的結(jié)果提供臨時的存儲空間拔疚,以便多個客戶端
可以共享這些結(jié)果稚失。- 智能引用代理(Smart Reference Proxy):當一個對象被引用時恰聘,提供一些額外的操作晴叨,例如將對象被調(diào)用的次數(shù)記錄下來等。
其中兼蕊,智能引用代理是使用得最廣泛的一種代理孙技,適合的應(yīng)用如如:日志處理、權(quán)限管理亚情、事務(wù)處理等哈雏。
代理的實現(xiàn)
代理模式的實現(xiàn)分為靜態(tài)代理和動態(tài)代理衫生。
靜態(tài)代理
概念
由程序員創(chuàng)建或工具生成代理類的源碼罪针,再編譯代理類栅迄。所謂靜態(tài)也就是在程序運行前就已經(jīng)存在代理類>的字節(jié)碼文件毅舆,代理類和委托類的關(guān)系在運行前就確定了憋活。Java編譯完成后代理類是一個實際的 class 文件虱黄。
前面舉的例子就是一個簡單的靜態(tài)代理。
靜態(tài)代理的實現(xiàn)可分為繼承和聚合辜梳,其中繼承容易導(dǎo)致類膨脹作瞄,因此推薦的實現(xiàn)方式是聚合危纫。
靜態(tài)代理優(yōu)缺點
優(yōu)點:業(yè)務(wù)類只需要關(guān)注業(yè)務(wù)邏輯本身,保證了業(yè)務(wù)類的重用性契耿。這是代理的共有優(yōu)點螃征。
缺點:
- 代理對象的一個接口只服務(wù)于一種類型的對象盯滚,如果要代理的方法很多,勢必要為每一種方法都進行代理裸燎,靜態(tài)代理在程序規(guī)模稍大時就無法勝任了泼疑。
- 如果接口增加一個方法,除了所有實現(xiàn)類需要實現(xiàn)這個方法外蕴纳,所有代理類也需要實現(xiàn)此方法个粱。增加了代碼維護的復(fù)雜度。
另外稻薇,如果要按照上述的方法使用代理模式塞椎,那么真實角色(委托類)必須是事先已經(jīng)存在的睛低,并將其作為代理對象的內(nèi)部屬性钱雷。但是實際使用時,一個真實角色必須對應(yīng)一個代理角色罩抗,如果大量使用會導(dǎo)致類的急劇膨脹澄暮;此外,如果事先并不知道真實角色(委托類)伸辟,該如何使用代理呢馍刮?這個問題可以通過Java的動態(tài)代理類來解決卡啰。
動態(tài)代理
概念
動態(tài)代理類的源碼是在程序運行期間由JVM根據(jù)反射等機制動態(tài)的生成,所以不存在代理類的字節(jié)碼文件匈辱。代理類和委托類的關(guān)系是在程序運行時確定亡脸。
實現(xiàn)方式
Java中實現(xiàn)動態(tài)代理主要有兩種典型方法:
- 使用JDK實現(xiàn)動態(tài)代理
- 使用CGLIB實現(xiàn)動態(tài)代理
我們主要來看一下第一種方法树酪。
JDK實現(xiàn)動態(tài)代理
步驟
- 創(chuàng)建被代理的類及接口
- 創(chuàng)建一個接口實現(xiàn)InvocationHandler的類(調(diào)用處理器)续语,它必須實現(xiàn)invoke方法
- 調(diào)用Proxy的靜態(tài)方法疮茄,動態(tài)創(chuàng)建一個代理類的對象
newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h)
- 通過代理調(diào)用方法
代碼舉例
看一個最簡單的例子力试,大部分步驟已注釋說明购裙。
Subject接口:
<pre>
/**
- 被代理類和代理類要實現(xiàn)的公共接口
*/
public interface Subject {
void request();
}
</pre>
要被代理的類:
<pre>
/**
-
Subject接口實現(xiàn)
*/
public class ConcreteSubject implements Subject{@Override
public void request() {
System.out.println("==========被代理對象的request方法==========");
}
}
</pre>
InvocationHandler實現(xiàn)類:
<pre>
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
/**
-
代理類的調(diào)用處理器
*/
public class ProxyHandler implements InvocationHandler {
//被代理對象
private Subject target;public ProxyHandler(Subject target) {
this.target=target;
}/*
- 參數(shù):
- proxy:表示最終生成的代理類對象躏率,注意不是被代理的對象
- method:被代理對象的方法
- args:方法的參數(shù)
- 返回值:
- Object方法的返回值
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("=========before===========");
Object object=method.invoke(target);
System.out.println("=========after===========");
return object;
}
}
</pre>
測試類:
<pre>
import java.lang.reflect.Proxy;
public class ProxyTest {
public static void main(String[] args) {
//1.創(chuàng)建被代理對象
ConcreteSubject subject=new ConcreteSubject();
//2.創(chuàng)建調(diào)用處理器對象
ProxyHandler proxyHandler=new ProxyHandler(subject);
//3.動態(tài)生成代理對象
Class<?> cls=ConcreteSubject.class;//獲得被代理類的Class對象
Subject proxySubject=(Subject)Proxy.newProxyInstance(cls.getClassLoader(), cls.getInterfaces(), proxyHandler);
//4.通過代理對象調(diào)用方法
proxySubject.request();
}
}
</pre>
運行測試類薇芝,可以得到以下結(jié)果:
至此夯到,用JDK實現(xiàn)了最簡單的動態(tài)代理耍贾。
其中需要注意的地方是:
- 需要創(chuàng)建一個java.lang.reflect.InvocationHandler的實現(xiàn)荐开,作為代理類調(diào)用處理器简肴。該接口只有一個方法:
Object invoke(Object proxy, Method method, Object[] args)
*注:參數(shù)說明見上文代碼*
- 調(diào)用
java.lang.reflect.Proxy.newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)
獲得代理實例
參數(shù)說明:
loader:定義代理類的類加載器
interfaces:代理類需要實現(xiàn)的接口列表
h:調(diào)用處理器砰识,代理對象將會將方法調(diào)用轉(zhuǎn)發(fā)給它
返回值:
Object:一個由上述參數(shù)所指定的代理實例
動態(tài)代理實現(xiàn)思路
實現(xiàn)功能:通過Proxy的newProxyInstance返回代理對象
- 聲明一段源碼(動態(tài)代理)
- 編譯源碼(JDK Compiler API),產(chǎn)生新的類(代理類)
- 將這個類load到內(nèi)存當中,產(chǎn)生一個新的對象(代理對象)
- return代理對象
注:可通過慕課網(wǎng):模擬JDK動態(tài)代理實現(xiàn)思路分析及簡單實現(xiàn)了解
CGLIB
JDK提供的實現(xiàn)動態(tài)代理的方法十分強大辫狼,但是也有一定的限制膨处,主要的限制是:
只能代理實現(xiàn)接口的類频蛔,沒有實現(xiàn)接口的類不能實現(xiàn)JDK動態(tài)代理
可以使用CGLIB來解決該問題晦溪。CGLIB的特點是:
- 針對類來實現(xiàn)代理
- 對指定目標類生成一個子類三圆,通過方法攔截技術(shù)攔截所有父類方法的調(diào)用
- 由于使用繼承實現(xiàn),因此不能為final修飾的類或方法實現(xiàn)代理
動態(tài)代理的應(yīng)用
AOP(面向切面編程),即在不改變原有類方法的基礎(chǔ)上舟肉,增加一些額外的業(yè)務(wù)邏輯路媚。
Spring的AOP就是通過JDK動態(tài)代理跟CGLIB動態(tài)代理來實現(xiàn)的整慎。
默認的策略是如果目標類是接口围苫,則使用JDK動態(tài)代理技術(shù),如果目標對象沒有實現(xiàn)接口拧揽,則默認會采用CGLIB代理淤袜。
參考: