文章首發(fā)于我的個人博客,歡迎訪問:https://blog.itzhouq.cn/proxy
在代理模式(Proxy Pattern)中椎木,一個類代表另一個類的功能晒他。這種類型的設(shè)計模式屬于結(jié)構(gòu)型模式秸侣。在代理模式中改淑,我們創(chuàng)建具有現(xiàn)有對象的對象,以便向外界提供功能接口痴施。代理模式的優(yōu)勢是實現(xiàn)了無侵入的代理擴展擎厢,也就是方法的增強究流;讓你可以在不用修改源碼的情況下,增強一些方法动遭。
為什么要學習代理模式呢芬探?因為 Spring AOP 的底層就是代理模式。
現(xiàn)在模擬一個場景:房東需要出租自己的房子厘惦。
實際落地時候?qū)⒊鲎膺@個動作抽象為接口:
/**
* 租房的接口
*/
public interface Rent {
public void rent();
}
房東(Host)實現(xiàn)了這個接口偷仿,執(zhí)行一個租房的方法。
/**
* 房東
*/
public class Host implements Rent {
@Override
public void rent() {
System.out.println("房東要出租房子");
}
}
這個例子在日常生活中很常見宵蕉。但是我們考慮到實際情況酝静,并不是所有的房東都有那么多自由的時間和資源,不上班就為了把自己的房子租出去的羡玛,為了省事他們都會借助于第三方公司做這個事情别智,就是我們常說的中介,這里的中介就是這個代理對象稼稿。
1薄榛、靜態(tài)代理
為什么要加一個代理對象呢?因為代理對象可以做一些額外的操作渺杉。比如這里中介除了可以幫房東租房子以外蛇数,還能帶客戶看房子,簽合同是越,收中介費等。
上面的接口和類不需要變動碌上,現(xiàn)在增加代理的功能倚评。
/**
* 代理類:其作用是幫房東租房子
*/
public class Proxy implements Rent {
private Host host;
public Proxy() {
}
public Proxy(Host host) {
this.host = host;
}
@Override
public void rent() {
seeHouse();
host.rent();
fare();
}
public void seeHouse() {
System.out.println("中介帶你看房子");
}
public void fare() {
System.out.println("中介收中介費");
}
}
模擬租房的過程:
/**
* 模擬租房的過程
*/
public class Client {
public static void main(String[] args) {
// 房東要租房子
Host host = new Host();
// 代理角色:中介,幫房東租房子馏予,除此之外提供一些額外的服務(wù)
Proxy proxy = new Proxy(host);
// 你不用面對房東天梧,直接找中介即可
proxy.rent();
}
}
運行的結(jié)果:
中介帶你看房子
房東要出租房子
中介收中介費
可以看到代理類對被代理的對象進行了增強。
角色分析:
- 抽象角色:一般會使用接口或者抽象類解決
- 真實角色:被代理的對象
- 代理角色:代理真實角色霞丧,代理真實角色后呢岗,我們一般會做一些附屬操作
- 客戶:訪問代理對象的人。
2蛹尝、靜態(tài)代理舉例
再看一個實際開發(fā)中遇到的問題后豫。有個接口UserService
,其實現(xiàn)類 UserServiceimpl
中有很多方法⊥荒牵現(xiàn)在有個需求挫酿,需要在執(zhí)行的方法前面添加日志,知道哪個方法執(zhí)行了愕难,但是不改變原有的UserServiceimpl
實現(xiàn)類早龟。
如果直接修改這個實現(xiàn)類會有以下幾個問題:
- 可能這個實現(xiàn)類不能讓你直接修改
- 這個實現(xiàn)類可以修改惫霸,但是修改的話有增加 BUG 的風險
- 這個實現(xiàn)類可以修改,但是實現(xiàn)類中方法很多葱弟,需要添加大量重復(fù)的代碼
這個時候代理模式就能排上用場了壹店。
接口:
public interface UserService {
public void add();
public void delete();
public void update();
public void query();
}
實現(xiàn)類:
/**
* 真實對象
*/
public class UserServiceImpl implements UserService {
@Override
public void add() {
System.out.println("增加了一個用戶");
}
@Override
public void delete() {
System.out.println("刪除了一個用戶");
}
@Override
public void update() {
System.out.println("更新了一個用戶");
}
@Override
public void query() {
System.out.println("查詢了一個用戶");
}
}
/**
* 模擬用戶的操作
*/
public class UserAction {
public static void main(String[] args) {
UserServiceImpl userService = new UserServiceImpl();
userService.add();
}
}
現(xiàn)在模擬添加日志的需求:
添加一個代理類,實現(xiàn) UserService
接口芝加,注入UserServiceImpl
硅卢,添加日志的方法:
public class UserServiceProxy implements UserService {
private UserServiceImpl userService;
public UserServiceProxy(UserServiceImpl userService) {
this.userService = userService;
}
@Override
public void add() {
log("add");
userService.add();
}
@Override
public void delete() {
log("delete");
userService.delete();
}
@Override
public void update() {
log("update");
userService.update();
}
@Override
public void query() {
log("query");
userService.query();
}
// 日志方法
public void log (String msg) {
System.out.println("執(zhí)行了" + msg + "方法");
}
}
模擬用戶操作:
/**
* 模擬用戶的操作
*/
public class UserAction {
public static void main(String[] args) {
UserServiceImpl userService = new UserServiceImpl();
// userService.add();
UserServiceProxy userServiceProxy = new UserServiceProxy(userService);
userServiceProxy.add();
}
}
運行結(jié)果:
執(zhí)行了add方法
增加了一個用戶
通過這個例子,可以很好的理解靜態(tài)代理模式的使用場景和使用方法妖混。
3老赤、動態(tài)代理
- 動態(tài)代理和靜態(tài)代理角色一樣
- 動態(tài)代理的代理類是動態(tài)生成的,不是我們直接寫好的制市。
- 動態(tài)代理分為兩大類:基于接口的動態(tài)代理和基于類的動態(tài)代理
- 基于接口 --- JDK 動態(tài)代理
- 基于類 --- cglib
- Java 字節(jié)碼 --- javassist
3抬旺、1 JDK 自帶的動態(tài)代理
- java.lang.reflect.Proxy:生成動態(tài)代理類和對象;提供了靜態(tài)方法祥楣,可以創(chuàng)建動態(tài)代理類和實例开财。
- java.lang.reflect.InvocationHandler(處理器接口):可以通過invoke方法實現(xiàn)對真實角色的代理訪問。
每次通過 Proxy 生成的代理類對象都要指定對應(yīng)的處理器對象误褪。
下面的代碼演示了责鳍,如何通過 JDK 的動態(tài)代理生成一個特定代理對象,進而對被代理對象進行增強兽间。
接口:Subject.java
public interface Subject {
public int sellBooks();
public String speak();
}
真實對象:RealSubject.java
public class RealSubject implements Subject {
@Override
public int sellBooks() {
System.out.println("賣書");
return 1;
}
@Override
public String speak() {
System.out.println("說話");
return "張三";
}
}
處理器對象:MyInvocationHandler.java
等下就是使用這個處理器對真實對象的方法進行增強历葛。
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
/**
* 處理器對象
*/
public class MyInvocationHandler implements InvocationHandler {
/**
* 因為需要處理真實對象,需要把真實角色傳進來
*/
Subject realSubject;
public MyInvocationHandler(Subject realSubject) {
this.realSubject = realSubject;
}
/**
* @param proxy 代理類
* @param method 正在調(diào)用的方法
* @param args 方法的參數(shù)
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("調(diào)用代理類");
if (method.getName().equalsIgnoreCase("sellBooks")) {
int invoke = (int) method.invoke(realSubject, args);
System.out.println("調(diào)用的是賣書的方法");
return invoke;
} else {
String string = (String) method.invoke(realSubject, args);
System.out.println("調(diào)用的是說話的方法");
return string;
}
}
}
調(diào)用端進行測試:Main.java
import java.lang.reflect.Proxy;
/**
* 調(diào)用類
*/
public class Main {
public static void main(String[] args) {
// 真實對象
Subject realSubject = new RealSubject();
// 根據(jù) 真實對象 創(chuàng)建一個 處理器對象
MyInvocationHandler myInvocationHandler = new MyInvocationHandler(realSubject);
// 代理對象:這個代理對象是通過 Proxy 類的靜態(tài)方法嘀略,動態(tài)生成的
Subject proxyClass = (Subject) Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(), new Class[]{Subject.class}, myInvocationHandler);
// proxyClass.sellBooks();
proxyClass.speak();
}
}
執(zhí)行結(jié)果:
調(diào)用代理類
說話
調(diào)用的是說話的方法
從結(jié)果可以看到通過生成代理對象恤溶,對真實對象的方法進行了增強。
值得注意的是帜羊,這個處理類對象myInvocationHandler
其實是Proxy.newProxyInstance
方法的一個參數(shù)咒程,可以寫成匿名內(nèi)部類。這個可以看我之前寫的筆記增強一個對象的方法的三種方式帐姻,那里就是這么寫的。
3饥瓷、2 Cglib 動態(tài)代理
Cglib 動態(tài)代理是針對代理的類,動態(tài)生產(chǎn)一個子類扛伍,然后子類覆蓋代理類中的方法。如果是final
或是 private
修飾的刺洒,則不會被重寫鳖宾。Cglib 是一個功能強大逆航,高性能的代碼生成包。它為沒有實現(xiàn)接口的類提供代理因俐,為 JDK 的動態(tài)代理提供了很好的補充拇惋。通常可以使用 Java 的動態(tài)代理創(chuàng)建代理抹剩,但當要代理的類沒有實現(xiàn)接口或為了更好的性能撑帖,Cglib 是一個好的選擇。
Cglib 作為一個開源項目澳眷,其代碼托管在在 GitHub 胡嘿,地址為: https://github.com/cglib/cglib 。
使用 Cglib 需要導(dǎo)入相關(guān)依賴或者 jar 包钳踊。我這里導(dǎo)入 jar 包衷敌,下載地址: https://github.com/cglib/cglib/releases/tag/RELEASE_3_3_0 。除此之外拓瞪,Cglib正常與運行還需要asm.jar
的支持( cglib 底層使用字節(jié)碼處理框架ASM缴罗,來轉(zhuǎn)換字節(jié)碼并生成新的類 ),下載地址: https://mvnrepository.com/artifact/org.ow2.asm/asm/7.2 祭埂。
注意:如果沒有導(dǎo)入asm
的依賴會拋出異常:
Exception in thread "main" java.lang.NoClassDefFoundError: org/objectweb/asm/Type
下面通過一個簡單的例子來說明其使用方式:
需要被代理的類:
package cn.itzhouq.proxy.cglib;
/**
* 被代理類
*/
public class Engineer {
// 可以被代理
public void eat() {
System.out.println("工程師正在吃飯");
}
// final 方法不會被生成的子類覆蓋
public final void work() {
System.out.println("工程師正在工作");
}
// private 方法不會被生成的子類覆蓋
private void play () {
System.out.println("工程師正在玩游戲");
}
}
可以看到這個被代理的類是沒有實現(xiàn)接口的面氓。
Cglib 代理類:
package cn.itzhouq.proxy.cglib;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
/**
* CGLIB 代理類
*/
public class CglibProxy implements MethodInterceptor {
private Object target;
public CglibProxy(Object target) {
this.target = target;
}
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
System.out.println("### before invocation");
Object result = method.invoke(target, objects);
System.out.println("### end invocation");
return result;
}
public static Object getProxy(Object target) {
Enhancer enhancer = new Enhancer();
// 設(shè)置需要代理的對象
enhancer.setSuperclass(target.getClass());
// 設(shè)置代理人
enhancer.setCallback(new CglibProxy(target));
return enhancer.create();
}
}
測試方法:
package cn.itzhouq.proxy.cglib;
/**
* 測試方法
*/
public class CglibMainTest {
public static void main(String[] args) {
// 生成 Cglib 代理類
Engineer engineerProxy = (Engineer) CglibProxy.getProxy(new Engineer());
// 調(diào)用相關(guān)方法
engineerProxy.eat();
engineerProxy.work();
// engineerProxy.play(); // 該代理對象中沒有該方法
}
}
運行結(jié)果:
### before invocation
工程師正在吃飯
### end invocation
工程師正在工作
通過這種代理方式,也可以對方法進行增強蛆橡。被 final
修飾的方法不會被生成的代理類(也是子類)覆蓋侧但,被private
修飾的方法壓根兒不會被代理類繼承。
關(guān)于 Cglib 的原理航罗,可以參考這篇文章: https://www.runoob.com/w3cnote/cglibcode-generation-library-intro.html 。
4屁药、代理模式和裝飾器模式的區(qū)別
兩者都是對類的方法進行擴展粥血,但是裝飾器模式強調(diào)的是增強自身,在被裝飾之后你能夠在被增強的類上使用增強后的功能酿箭。增強后你還是你复亏,只不過能力更強了而已;而代理模式則強調(diào)的要讓別人幫你去做一些本身與你業(yè)務(wù)沒有太多關(guān)系的職責(記錄日志缭嫡、設(shè)置緩存)缔御。代理模式是為了實現(xiàn)對象的控制 ,因為被代理的對象往往難以直接獲得或者其內(nèi)部不想暴露出來妇蛀。
關(guān)于這個細節(jié)耕突,可以這篇知乎回答笤成,作者舉的例子很形象。
Java中“裝飾模式”和“代理模式”有啥區(qū)別眷茁? - 知乎 https://www.zhihu.com/question/41988550/answer/462204684
5炕泳、代理模式的優(yōu)缺點
優(yōu)點:
- 可以使真實角色的操作更加純粹!不用去關(guān)注一些公共的業(yè)務(wù)
- 公共業(yè)務(wù)交給代理角色上祈!實現(xiàn)了業(yè)務(wù)的分工
- 公共業(yè)務(wù)發(fā)生擴展的時候,方便集中管理
缺點:
- 一個真實對象就會產(chǎn)生一個代理對象籽腕,代理量會翻倍。