新聞中心
代理模式相信大家經常聽說,在設計模式中相對而言是比較難理解的。這次指北君來給大家通俗的介紹介紹。

1.什么是代理模式
Provide a surrogate or placeholder for another object to control access to it.
Proxy Pattern:為其他對象提供一種代理以控制對這個對象的訪問。
說人話:在不改變原始類(或叫被代理類)代碼的情況下,通過引入代理類來給原始類附加功能,比如Spring AOP。
2.代理模式定義
①Subject
抽象主題角色,可以是抽象類,可以是接口,是一個最普通的業(yè)務類定義,無特殊要求。
②RealSubject
真實主題角色,也叫被代理角色,是業(yè)務邏輯的具體執(zhí)行者。
③Proxy
代理主題角色,也叫代理類,它負責對真實角色的應用,把所有抽象主題類定義的方法限制委托給真實主題角色實現(xiàn),并在真實主題角色處理前后做一些預處理或善后工作。
通用代碼如下:
/**
* 抽象主題類
*/
public interface Subject {
void doSomething();
}
/**
* 真實主題角色
*/
public class RealSubject implements Subject{
@Override
public void doSomething() {
//TODO 具體執(zhí)行的事
}
}
/**
* 代理主題角色
*/
public class Proxy implements Subject{
//要代理的具體實現(xiàn)類
private Subject realSubject;
public Proxy(Subject realSubject){
this.realSubject = realSubject;
}
@Override
public void doSomething() {
this.before();
realSubject.doSomething();
this.after();
}
// 預處理
private void before(){
// TODO
}
// 善后處理
private void after(){
// TODO
}
}
3.代理模式的兩種實現(xiàn)
比如用代理模式實現(xiàn)統(tǒng)計某個接口的耗時。
3.1 靜態(tài)代理
①基于接口編程
抽象主題類:
public interface IUserController {
// 登錄
String login(String username,String password);
// 注冊
String register(String username,String password);
}具體主題類:
public class UserController implements IUserController{
@Override
public String login(String username, String password) {
// TODO 登錄邏輯
return null;
}
@Override
public String register(String username, String password) {
// TODO 注冊邏輯
return null;
}
}代理主題類:
public class UserControllerProxy implements IUserController{
private IUserController userController;
public UserControllerProxy(IUserController userController){
this.userController = userController;
}
@Override
public String login(String username, String password) {
long startTime = System.currentTimeMillis();
// 登錄邏輯
userController.login("username","password");
long endTime = System.currentTimeMillis();
long responseTime = endTime - startTime;
System.out.println("接口響應時間:"+responseTime);
return null;
}
@Override
public String register(String username, String password) {
long startTime = System.currentTimeMillis();
// 注冊邏輯
userController.register("username","password");
long endTime = System.currentTimeMillis();
long responseTime = endTime - startTime;
System.out.println("接口響應時間:"+responseTime);
return null;
}
}測試:
因為原始類 UserController 和代理類 UserControllerProxy 實現(xiàn)相同的接口,是基于接口而非實現(xiàn)編程,將UserController類對象替換為UserControllerProxy類對象,不需要改動太多代碼
public class StaticProxyTest {
public static void main(String[] args) {
IUserController userController = new UserControllerProxy(new UserController());
userController.login("username","password");
userController.register("username","password");
}
}在上面的代碼中,代理類和具體主題類需要實現(xiàn)相同的接口,假如具體主題類沒有實現(xiàn)接口,并且不是我們開發(fā)維護的(比如來自第三方接口),我們要統(tǒng)計這個第三方接口的耗時,那應該如何實現(xiàn)代理模式呢?
②基于繼承
繼承具體主題類,然后擴展其方法即可,直接看代碼。
public class UserControllerProxy extends UserController {
@Override
public String login(String username, String password) {
long startTime = System.currentTimeMillis();
// 登錄邏輯
super.login("username","password");
long endTime = System.currentTimeMillis();
long responseTime = endTime - startTime;
System.out.println("接口響應時間:"+responseTime);
return null;
}
@Override
public String register(String username, String password) {
long startTime = System.currentTimeMillis();
// 注冊邏輯
super.register("username","password");
long endTime = System.currentTimeMillis();
long responseTime = endTime - startTime;
System.out.println("接口響應時間:"+responseTime);
return null;
}
}3.2 動態(tài)代理
在上面的例子中,有兩個問題:
①我們需要在代理類中,將具體主題類中的所有的方法,都重新實現(xiàn)一遍,并且為每個方法都附加相似的代碼邏輯,如果方法很多,重復代碼也會很多。
②如果要添加的附加功能的類有不止一個,我們需要針對每個類都創(chuàng)建一個代理類。
那該如何解決上面的問題呢?答案就是動態(tài)代理(Dynamic Proxy)。
動態(tài)代理:不事先為每個原始類編寫代理類,而是在運行的時候,動態(tài)地創(chuàng)建原始類對應的代理類,然后在系統(tǒng)中用代理類替換掉原始類。
JDK動態(tài)代理:
public class DynamicProxyHandler implements InvocationHandler {
private Object target;
public DynamicProxyHandler(Object target){
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
long startTime = System.currentTimeMillis();
Object result = method.invoke(this.target, args);
long endTime = System.currentTimeMillis();
long responseTime = endTime - startTime;
System.out.println("接口響應時間:"+responseTime);
return result;
}
}測試:
public class DynamicProxyTest {
public static void main(String[] args) {
// 1、創(chuàng)建具體主題類
IUserController userController = new UserController();
// 2、創(chuàng)建 Handler
DynamicProxyHandler proxyHandler = new DynamicProxyHandler(userController);
// 3、動態(tài)產生代理類
IUserController o = (IUserController)Proxy.newProxyInstance(userController.getClass().getClassLoader(),
userController.getClass().getInterfaces(),
proxyHandler);
o.login("username","password");
o.register("username","password");
}
}這是 JDK 動態(tài)代理,利用反射機制生成一個實現(xiàn)代理接口的匿名類,在調用具體方法前調用InvokeHandler來處理,代理對象是在程序運行時產生的,而不是編譯期,要求是具體主題類必須實現(xiàn)接口。
另外一種方式是 Cglib 動態(tài)代理。CGLIB(Code Generation Library)是一個基于ASM的字節(jié)碼生成庫,它允許我們在運行時對字節(jié)碼進行修改和動態(tài)生成,也就是通過修改字節(jié)碼生成子類來處理。
Cglib 動態(tài)代理:
public class UserController{
public String login(String username, String password) {
// TODO 登錄邏輯
System.out.println("登錄");
return null;
}
public String register(String username, String password) {
// TODO 注冊邏輯
System.out.println("注冊");
return null;
}
}注意:真實主題類是沒有實現(xiàn)接口的。
public class CglibDynamicProxy implements MethodInterceptor {
private Object target;
public CglibDynamicProxy(Object target){
this.target = target;
}
// 給目標創(chuàng)建代理對象
public Object newProxyInstance(){
// 1.工具類
Enhancer enhancer = new Enhancer();
// 2.設置父類
enhancer.setSuperclass(target.getClass());
// 3.設置回調函數(shù)
enhancer.setCallback(this);
// 4.創(chuàng)建子類(代理對象)
return enhancer.create();
}
@Override
public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
long startTime = System.currentTimeMillis();
Object result = method.invoke(this.target, args);
long endTime = System.currentTimeMillis();
long responseTime = endTime - startTime;
System.out.println("接口響應時間:"+responseTime);
return result;
}
}測試:
public class CglibDynamicProxyTest {
public static void main(String[] args) {
UserController userController = new UserController();
CglibDynamicProxy cglibDynamicProxy = new CglibDynamicProxy(userController);
UserController o = (UserController)cglibDynamicProxy.newProxyInstance();
o.login("username","password");
o.register("username","password");
}
}4.代理模式優(yōu)點
①職責清晰
真實的角色就是實現(xiàn)實際的業(yè)務邏輯, 不用關心其他非本職責的事務, 通過后期的代理完成一件事務, 附帶的結果就是編程簡潔清晰。
②高擴展性
具體主題角色是隨時都會發(fā)生變化的, 只要它實現(xiàn)了接口, 甭管它如何變化,代理類完全都可以在不做任何修改的情況下使用。
5.代理模式應用場景
①業(yè)務系統(tǒng)的非功能性需求開發(fā)
這是最常用的一個場景。比如:監(jiān)控、統(tǒng)計、鑒權、限流、事務、冪等、日志。我們將這些附加功能與業(yè)務功能解耦,放到代理類中統(tǒng)一處理,讓程序員只需要關注業(yè)務方面的開發(fā)。
典型例子就是 SpringAOP。
②RPC
RPC(遠程代理) 框架也可以看作一種代理模式,通過遠程代理,將網絡通信、數(shù)據(jù)編解碼等細節(jié)隱藏起來??蛻舳嗽谑褂?RPC 服務的時候,就像使用本地函數(shù)一樣,無需了解跟服務器交互的細節(jié)。除此之外,RPC 服務的開發(fā)者也只需要開發(fā)業(yè)務邏輯,就像開發(fā)本地使用的函數(shù)一樣,不需要關注跟客戶端的交互細節(jié)。
本文標題:面試官:講講SpringAOP的底層代理模式
文章位置:http://fisionsoft.com.cn/article/ccsoihj.html


咨詢
建站咨詢
