文章首發于我的個人博客,歡迎訪問:https://blog.itzhouq.cn/proxy
在代理模式(Proxy Pattern)中,一個類代表另一個類的功能,這種型別的設計模式屬于結構型模式,在代理模式中,我們創建具有現有物件的物件,以便向外界提供功能介面,代理模式的優勢是實作了無侵入的代理擴展,也就是方法的增強;讓你可以在不用修改原始碼的情況下,增強一些方法,
為什么要學習代理模式呢?因為 Spring AOP 的底層就是代理模式,
現在模擬一個場景:房東需要出租自己的房子,
實際落地時候將出租這個動作抽象為介面:
/**
* 租房的介面
*/
public interface Rent {
public void rent();
}
房東(Host)實作了這個介面,執行一個租房的方法,
/**
* 房東
*/
public class Host implements Rent {
@Override
public void rent() {
System.out.println("房東要出租房子");
}
}
這個例子在日常生活中很常見,但是我們考慮到實際情況,并不是所有的房東都有那么多自由的時間和資源,不上班就為了把自己的房子租出去的,為了省事他們都會借助于第三方公司做這個事情,就是我們常說的中介,這里的中介就是這個代理物件,
1、靜態代理
為什么要加一個代理物件呢?因為代理物件可以做一些額外的操作,比如這里中介除了可以幫房東租房子以外,還能帶客戶看房子,簽合同,收中介費等,

上面的介面和類不需要變動,現在增加代理的功能,
/**
* 代理類:其作用是幫房東租房子
*/
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();
// 代理角色:中介,幫房東租房子,除此之外提供一些額外的服務
Proxy proxy = new Proxy(host);
// 你不用面對房東,直接找中介即可
proxy.rent();
}
}
運行的結果:
中介帶你看房子
房東要出租房子
中介收中介費
可以看到代理類對被代理的物件進行了增強,
角色分析:
- 抽象角色:一般會使用介面或者抽象類解決
- 真實角色:被代理的物件
- 代理角色:代理真實角色,代理真實角色后,我們一般會做一些附屬操作
- 客戶:訪問代理物件的人,
2、靜態代理舉例
再看一個實際開發中遇到的問題,有個介面UserService,其實作類 UserServiceimpl中有很多方法,現在有個需求,需要在執行的方法前面添加日志,知道哪個方法執行了,但是不改變原有的UserServiceimpl實作類,
如果直接修改這個實作類會有以下幾個問題:
- 可能這個實作類不能讓你直接修改
- 這個實作類可以修改,但是修改的話有增加 BUG 的風險
- 這個實作類可以修改,但是實作類中方法很多,需要添加大量重復的代碼
這個時候代理模式就能排上用場了,
介面:
public interface UserService {
public void add();
public void delete();
public void update();
public void query();
}
實作類:
/**
* 真實物件
*/
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();
}
}
現在模擬添加日志的需求:
添加一個代理類,實作 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("執行了" + msg + "方法");
}
}
模擬用戶操作:
/**
* 模擬用戶的操作
*/
public class UserAction {
public static void main(String[] args) {
UserServiceImpl userService = new UserServiceImpl();
// userService.add();
UserServiceProxy userServiceProxy = new UserServiceProxy(userService);
userServiceProxy.add();
}
}
運行結果:
執行了add方法
增加了一個用戶
通過這個例子,可以很好的理解靜態代理模式的使用場景和使用方法,

3、動態代理
- 動態代理和靜態代理角色一樣
- 動態代理的代理類是動態生成的,不是我們直接寫好的,
- 動態代理分為兩大類:基于介面的動態代理和基于類的動態代理
- 基于介面 --- JDK 動態代理
- 基于類 --- cglib
- Java 位元組碼 --- javassist
3、1 JDK 自帶的動態代理
- java.lang.reflect.Proxy:生成動態代理類和物件;提供了靜態方法,可以創建動態代理類和實體,
- java.lang.reflect.InvocationHandler(處理器介面):可以通過invoke方法實作對真實角色的代理訪問,
每次通過 Proxy 生成的代理類物件都要指定對應的處理器物件,
下面的代碼演示了,如何通過 JDK 的動態代理生成一個特定代理物件,進而對被代理物件進行增強,
介面: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 正在呼叫的方法
* @param args 方法的引數
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("呼叫代理類");
if (method.getName().equalsIgnoreCase("sellBooks")) {
int invoke = (int) method.invoke(realSubject, args);
System.out.println("呼叫的是賣書的方法");
return invoke;
} else {
String string = (String) method.invoke(realSubject, args);
System.out.println("呼叫的是說話的方法");
return string;
}
}
}
呼叫端進行測驗:Main.java
import java.lang.reflect.Proxy;
/**
* 呼叫類
*/
public class Main {
public static void main(String[] args) {
// 真實物件
Subject realSubject = new RealSubject();
// 根據 真實物件 創建一個 處理器物件
MyInvocationHandler myInvocationHandler = new MyInvocationHandler(realSubject);
// 代理物件:這個代理物件是通過 Proxy 類的靜態方法,動態生成的
Subject proxyClass = (Subject) Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(), new Class[]{Subject.class}, myInvocationHandler);
// proxyClass.sellBooks();
proxyClass.speak();
}
}
執行結果:
呼叫代理類
說話
呼叫的是說話的方法
從結果可以看到通過生成代理物件,對真實物件的方法進行了增強,
值得注意的是,這個處理類物件myInvocationHandler 其實是Proxy.newProxyInstance方法的一個引數,可以寫成匿名內部類,這個可以看我之前寫的筆記增強一個物件的方法的三種方式,那里就是這么寫的,
3、2 Cglib 動態代理
Cglib 動態代理是針對代理的類,動態生產一個子類,然后子類覆寫代理類中的方法,如果是final 或是 private 修飾的,則不會被重寫,Cglib 是一個功能強大,高性能的代碼生成包,它為沒有實作介面的類提供代理,為 JDK 的動態代理提供了很好的補充,通常可以使用 Java 的動態代理創建代理,但當要代理的類沒有實作介面或為了更好的性能,Cglib 是一個好的選擇,
Cglib 作為一個開源專案,其代碼托管在在 GitHub ,地址為: https://github.com/cglib/cglib ,
使用 Cglib 需要匯入相關依賴或者 jar 包,我這里匯入 jar 包,下載地址: https://github.com/cglib/cglib/releases/tag/RELEASE_3_3_0 ,除此之外,Cglib正常與運行還需要asm.jar的支持( cglib 底層使用位元組碼處理框架ASM,來轉換位元組碼并生成新的類 ),下載地址: https://mvnrepository.com/artifact/org.ow2.asm/asm/7.2 ,
注意:如果沒有匯入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("工程師正在玩游戲");
}
}
可以看到這個被代理的類是沒有實作介面的,
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();
// 設定需要代理的物件
enhancer.setSuperclass(target.getClass());
// 設定代理人
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());
// 呼叫相關方法
engineerProxy.eat();
engineerProxy.work();
// engineerProxy.play(); // 該代理物件中沒有該方法
}
}
運行結果:
### before invocation
工程師正在吃飯
### end invocation
工程師正在作業
通過這種代理方式,也可以對方法進行增強,被 final修飾的方法不會被生成的代理類(也是子類)覆寫,被private修飾的方法壓根兒不會被代理類繼承,
關于 Cglib 的原理,可以參考這篇文章: https://www.runoob.com/w3cnote/cglibcode-generation-library-intro.html ,
4、代理模式和裝飾器模式的區別
兩者都是對類的方法進行擴展,但是裝飾器模式強調的是增強自身,在被裝飾之后你能夠在被增強的類上使用增強后的功能,增強后你還是你,只不過能力更強了而已;而代理模式則強調的要讓別人幫你去做一些本身與你業務沒有太多關系的職責(記錄日志、設定快取),代理模式是為了實作物件的控制 ,因為被代理的物件往往難以直接獲得或者其內部不想暴露出來,
關于這個細節,可以這篇知憾訓答,作者舉的例子很形象,
Java中“裝飾模式”和“代理模式”有啥區別? - 知乎 https://www.zhihu.com/question/41988550/answer/462204684
5、代理模式的優缺點
優點:
- 可以使真實角色的操作更加純粹!不用去關注一些公共的業務
- 公共業務交給代理角色!實作了業務的分工
- 公共業務發生擴展的時候,方便集中管理
缺點:
- 一個真實物件就會產生一個代理物件,代理量會翻倍,
參考文章
轉載請註明出處,本文鏈接:https://www.uj5u.com/ruanti/12577.html
標籤:設計模式
