1.寫(xiě)在前
代理模式(Proxy): 為其他對(duì)象提供一個(gè)代理,用來(lái)控制這個(gè)對(duì)象的訪問(wèn)。
解讀:使用代理模式以后,客戶端直接訪問(wèn)代理,代理在客戶端和目標(biāo)對(duì)象之間起到中介的作用。
案例分析:
當(dāng)我們開(kāi)發(fā)了一個(gè) MetricsCollector 類,用來(lái)收集接口請(qǐng)求的原始數(shù)據(jù),比如訪問(wèn)時(shí)間、處理時(shí)長(zhǎng)等。具體代碼如下:
當(dāng)我們開(kāi)發(fā)了一個(gè) MetricsCollector 類,用來(lái)收集接口請(qǐng)求的原始數(shù)據(jù),比如訪問(wèn)時(shí)間、處理時(shí)長(zhǎng)等。具體代碼如下:
public class UserController {
//...省略其他屬性和方法...
private MetricsCollector metricsCollector; //依賴注入
public UserVo login(String telephone, String password) {
long startTimestamp = System.currentTimeMillis();
// ... 省略login邏輯...
long endTimeStamp = System.currentTimeMillis();
long responseTime = endTimeStamp - startTimestamp;
RequestInfo requestInfo = new RequestInfo("login", responseTime, startTimestamp);
metricsCollector.recordRequest(requestInfo);
//...返回UserVo數(shù)據(jù)...
}
public UserVo register(String telephone, String password) {
long startTimestamp = System.currentTimeMillis();
// ... 省略register邏輯...
long endTimeStamp = System.currentTimeMillis();
long responseTime = endTimeStamp - startTimestamp;
RequestInfo requestInfo = new RequestInfo("register", responseTime, startTimestamp);
metricsCollector.recordRequest(requestInfo);
//...返回UserVo數(shù)據(jù)...
}
}
上述代碼存在的問(wèn)題:非業(yè)務(wù)代碼和業(yè)務(wù)代碼耦合!
第一,性能計(jì)數(shù)器框架代碼侵入到業(yè)務(wù)代碼中,跟業(yè)務(wù)代碼高度耦合。
第二,收集接口請(qǐng)求的代碼跟業(yè)務(wù)代碼無(wú)關(guān),本就不應(yīng)該放到一個(gè)類中。
2.重構(gòu)方案
(1)優(yōu)化方案1:原始類和代理類實(shí)現(xiàn)相同的接口
為了將框架代碼和業(yè)務(wù)代碼解耦,代理模式就派上用場(chǎng)了。
具體操作:代理類 UserControllerProxy 和原始類 UserController 實(shí)現(xiàn)相同的接口 IUserController。UserController 類只負(fù)責(zé)業(yè)務(wù)功能。代理類 UserControllerProxy 負(fù)責(zé)在業(yè)務(wù)代碼執(zhí)行前后附加其他邏輯代碼,并通過(guò)委托的方式調(diào)用原始類來(lái)執(zhí)行業(yè)務(wù)代碼。
具體操作:代理類 UserControllerProxy 和原始類 UserController 實(shí)現(xiàn)相同的接口 IUserController。UserController 類只負(fù)責(zé)業(yè)務(wù)功能。代理類 UserControllerProxy 負(fù)責(zé)在業(yè)務(wù)代碼執(zhí)行前后附加其他邏輯代碼,并通過(guò)委托的方式調(diào)用原始類來(lái)執(zhí)行業(yè)務(wù)代碼。
//1.抽象角色:抽象主題類的主要職責(zé)是生命真實(shí)主題和代理的共同接口方法,該類可以是接口,也可以是抽象類。
public interface IUserController {
UserVo login(String telephone, String password);
UserVo register(String telephone, String password);
}
//2.原始類/被代理類(RealSubject):定義了代理所表示的真實(shí)對(duì)象,只負(fù)責(zé)系統(tǒng)中的真正的業(yè)務(wù)邏輯對(duì)象
public class UserController implements IUserController {
//...省略其他屬性和方法...
@Override
public UserVo login(String telephone, String password) {
//...省略login邏輯...
//...返回UserVo數(shù)據(jù)...
}
@Override
public UserVo register(String telephone, String password) {
//...省略register邏輯...
//...返回UserVo數(shù)據(jù)...
}
}
//3.代理類:具有RealSubject的引用,具備完全的對(duì)RealSubject的代理權(quán),客戶端調(diào)用代理對(duì)象的方法,也調(diào)用被代理對(duì)象的方法,但是會(huì)在代理對(duì)象前后增加一些邏輯代碼。
public class UserControllerProxy implements IUserController {
private MetricsCollector metricsCollector;
private UserController userController;
public UserControllerProxy(UserController userController) {
this.userController = userController;
this.metricsCollector = new MetricsCollector();
}
@Override
public UserVo login(String telephone, String password) {
long startTimestamp = System.currentTimeMillis();
// 委托處理
UserVo userVo = userController.login(telephone, password);
long endTimeStamp = System.currentTimeMillis();
long responseTime = endTimeStamp - startTimestamp;
RequestInfo requestInfo = new RequestInfo("login", responseTime, startTimestamp);
metricsCollector.recordRequest(requestInfo);
return userVo;
}
@Override
public UserVo register(String telephone, String password) {
long startTimestamp = System.currentTimeMillis();
UserVo userVo = userController.register(telephone, password);
long endTimeStamp = System.currentTimeMillis();
long responseTime = endTimeStamp - startTimestamp;
RequestInfo requestInfo = new RequestInfo("register", responseTime, startTimestamp);
metricsCollector.recordRequest(requestInfo);
return userVo;
}
}
//UserControllerProxy使用舉例
//因?yàn)樵碱惡痛眍悓?shí)現(xiàn)相同的接口,是基于接口而非實(shí)現(xiàn)編程?。?!
//將UserController類對(duì)象替換為UserControllerProxy類對(duì)象,不需要改動(dòng)太多代碼
IUserController userController = new UserControllerProxy(new UserController());
(2)優(yōu)化方案2:代理類繼承原始類
如果如果原始類并沒(méi)有定義接口,并且原始類代碼并不是我們開(kāi)發(fā)維護(hù)的(比如它來(lái)自一個(gè)第三方的類庫(kù)),我們也沒(méi)辦法直接修改原始類,給它重新定義一個(gè)接口。
具體操作:對(duì)于這種外部類的擴(kuò)展,我們一般都是采用繼承的方式。我們讓代理類繼承原始類,然后擴(kuò)展附加功能。
// 代理類繼承原始類
public class UserControllerProxy extends UserController {
private MetricsCollector metricsCollector;
public UserControllerProxy() {
this.metricsCollector = new MetricsCollector();
}
public UserVo login(String telephone, String password) {
long startTimestamp = System.currentTimeMillis();
UserVo userVo = super.login(telephone, password);
long endTimeStamp = System.currentTimeMillis();
long responseTime = endTimeStamp - startTimestamp;
RequestInfo requestInfo = new RequestInfo("login", responseTime, startTimestamp);
metricsCollector.recordRequest(requestInfo);
return userVo;
}
public UserVo register(String telephone, String password) {
long startTimestamp = System.currentTimeMillis();
UserVo userVo = super.register(telephone, password);
long endTimeStamp = System.currentTimeMillis();
long responseTime = endTimeStamp - startTimestamp;
RequestInfo requestInfo = new RequestInfo("register", responseTime, startTimestamp);
metricsCollector.recordRequest(requestInfo);
return userVo;
}
}
//UserControllerProxy使用舉例
UserController userController = new UserControllerProxy();
上文的代碼實(shí)現(xiàn)還是有點(diǎn)問(wèn)題:
- 一方面,我們需要在代理類中,將原始類中的所有的方法,都重新實(shí)現(xiàn)一遍,并且為每個(gè)方法都附加相似的代碼邏輯。
- 另一方面,如果要添加的附加功能的類有不止一個(gè)(這里只包括登錄注冊(cè)功能),我們需要針對(duì)每個(gè)類都創(chuàng)建一個(gè)代理類。
(3)優(yōu)化方案3:動(dòng)態(tài)代理原理解析
動(dòng)態(tài)代理(Dynamic Proxy),就是我們不事先為每個(gè)原始類編寫(xiě)代理類,而是在運(yùn)行的時(shí)候,動(dòng)態(tài)地創(chuàng)建原始類對(duì)應(yīng)的代理類,然后在系統(tǒng)中用代理類替換掉原始類。實(shí)際上,動(dòng)態(tài)代理底層依賴的就是java的反射語(yǔ)法。
public class MetricsCollectorProxy {
private MetricsCollector metricsCollector;
public MetricsCollectorProxy() {
this.metricsCollector = new MetricsCollector();
}
// 動(dòng)態(tài)創(chuàng)建代理
public Object createProxy(Object proxiedObject) {
Class<?>[] interfaces = proxiedObject.getClass().getInterfaces();
DynamicProxyHandler handler = new DynamicProxyHandler(proxiedObject);
return Proxy.newProxyInstance(proxiedObject.getClass().getClassLoader(), interfaces, handler);
}
private class DynamicProxyHandler implements InvocationHandler {
private Object proxiedObject;
public DynamicProxyHandler(Object proxiedObject) {
this.proxiedObject = proxiedObject;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
long startTimestamp = System.currentTimeMillis();
Object result = method.invoke(proxiedObject, args);
long endTimeStamp = System.currentTimeMillis();
long responseTime = endTimeStamp - startTimestamp;
String apiName = proxiedObject.getClass().getName() + ":" + method.getName();
RequestInfo requestInfo = new RequestInfo(apiName, responseTime, startTimestamp);
metricsCollector.recordRequest(requestInfo);
return result;
}
}
}
//MetricsCollectorProxy使用舉例
MetricsCollectorProxy proxy = new MetricsCollectorProxy();
IUserController userController = (IUserController) proxy.createProxy(new UserController());
3.應(yīng)用場(chǎng)景
(1)業(yè)務(wù)系統(tǒng)的非功能性開(kāi)發(fā)
- 比如:監(jiān)控、統(tǒng)計(jì)、鑒權(quán)、限流、事務(wù)、冪等、日志。我們將這些附加功能(非功能性開(kāi)發(fā))與業(yè)務(wù)功能解耦,放到代理類中統(tǒng)一處理,讓程序員只需要關(guān)注業(yè)務(wù)方面的開(kāi)發(fā)。
(2)在RPC和緩存中應(yīng)用
實(shí)際上,RPC框架也可以看作一種代理模式,GoF 的《設(shè)計(jì)模式》一書(shū)中把它稱作遠(yuǎn)程代理。通過(guò)遠(yuǎn)程代理,將網(wǎng)絡(luò)通信、數(shù)據(jù)編解碼等細(xì)節(jié)隱藏起來(lái)??蛻舳嗽谑褂?RPC 服務(wù)的時(shí)候,就像使用本地函數(shù)一樣,無(wú)需了解跟服務(wù)器交互的細(xì)節(jié)。
-
在緩存中的應(yīng)用:
假設(shè)我們要開(kāi)發(fā)一個(gè)接口請(qǐng)求的緩存功能,對(duì)于某些接口請(qǐng)求,如果入?yún)⑾嗤谠O(shè)定的過(guò)期時(shí)間內(nèi),直接返回緩存結(jié)果,而不用重新進(jìn)行邏輯處理。
在應(yīng)用啟動(dòng)的時(shí)候,我們從配置文件中加載需要支持緩存的接口,以及相應(yīng)的緩存策略(比如過(guò)期時(shí)間)等。當(dāng)請(qǐng)求到來(lái)的時(shí)候,我們?cè)?AOP 切面中攔截請(qǐng)求,如果請(qǐng)求中帶有支持緩存的字段(比如 http://…?..&cached=true),我們便從緩存(內(nèi)存緩存或者 Redis 緩存等)中獲取數(shù)據(jù)直接返回。
4.總結(jié)與補(bǔ)充
代理模式的優(yōu)點(diǎn):降低了系統(tǒng)的耦合性,將框架代碼(非業(yè)務(wù)代碼/附加功能)和業(yè)務(wù)代碼解耦。
職責(zé)清晰
擴(kuò)展性高
缺點(diǎn):由于多了一個(gè)代理對(duì)象,可能會(huì)使請(qǐng)求的處理速度變慢。有些代理的實(shí)現(xiàn)較為復(fù)雜。
補(bǔ)充:通過(guò)對(duì)訪問(wèn)的代理,我們可以用遠(yuǎn)程代理、虛擬代理、安全代理等,來(lái)減少直接對(duì)對(duì)象訪問(wèn)產(chǎn)生的開(kāi)銷,即不同類型的代理可以對(duì)客戶端對(duì)對(duì)象的訪問(wèn)進(jìn)行不同的控制:
遠(yuǎn)程代理:使得客戶端可以訪問(wèn)在遠(yuǎn)程機(jī)器上的對(duì)象,遠(yuǎn)程機(jī)器可能具有更好的計(jì)算性能與處理速度,可以快速響應(yīng)并處理客戶端請(qǐng)求。
虛擬代理:通過(guò)使用一個(gè)小對(duì)象來(lái)代表一個(gè)大對(duì)象,可以減少系統(tǒng)資源的消耗,對(duì)系統(tǒng)進(jìn)行優(yōu)化并提高運(yùn)行速度。
保護(hù)代理:可以控制客戶端對(duì)真實(shí)對(duì)象的使用權(quán)限。
5.參考
《設(shè)計(jì)模式之美》 --- 王爭(zhēng)