GoF 23 种设计模式之一:代理模式(Proxy Pattern)
代理模式(Proxy Pattern)是 GoF(《设计模式:可复用面向对象软件的基础》)23 种设计模式之一,属于结构型设计模式。它的核心思想是:为其他对象提供一种代理,以控制对这个对象的访问。当客户端不想或不能直接引用某个对象时,可通过一个“代理”(Proxy)提供间接访问。代理对象既可以在客户端与真实对象(目标对象)之间起到中介作用,还可以对客户端不可见的部分进行过滤,或为客户端添加额外的功能。引入代理的动机在于,通过新的对象来实现对真实对象的操作,或让新的对象作为真实对象的替身,从而达到保护、增强、优化等目的。
1.1 生活场景示例
婚恋介绍(媒婆场景) 牛村的牛二看上了隔壁村的小花,但他不好意思直接接触,于是请媒婆王妈妈作媒。在此场景中:
牛二:目标角色(真实主题)
小花:另一真实主题
王妈妈:代理类(代理主题)
王妈妈代表牛二先行与小花见面,使得牛二无需直接与小花打交道。在程序设计中,当对象 A 与对象 B 无法直接交互时,可引入代理对象完成中介功能。
房产中介(链家场景) 刚到北京的你想租房,可以自己去跑,也可以找链家中介。你(目标角色)与链家(代理类)都具备“找房子”这一行为,但链家在满足你找房需求的基础上,还会收取一定的服务费。在程序中,当需要在执行某个功能时附加额外逻辑(如权限校验、记录日志等),就可以通过代理模式实现功能增强。
西游记场景(保护机制) 在《西游记》中,猪八戒想强抢高翠兰,孙悟空得知后化身为高翠兰与猪八戒见面。
猪八戒:客户端
高翠兰:真实目标
悟空:代理类
对猪八戒而言,他以为与的真高翠兰互动,却不知眼前的人是孙悟空。悟空保护了高翠兰,并在不暴露真实身份的情况下完成了干预。代理模式的一个重要特点在于,客户端对代理对象的调用与对目标对象的调用几乎无差别。
编程场景(统一登录验证) 系统中存在 A、B、C 三个模块,访问这些模块之前都要判断用户是否已登录。若将判断逻辑分散写在每个模块中,不仅代码冗余,而且可读性差。此时可为 A、B、C 模块提供一个统一的“登录检查”代理:
客户端发起请求
代理先判断用户是否登录
如果已登录,则调用对应目标模块;否则跳转到登录页面
这样,代理不仅保护了目标模块,还实现了登录判断代码的复用。
1.2 代理模式的角色
在代理模式中,通常包含以下三类角色:
抽象主题(Subject) 定义了真实主题和代理主题的公共接口,客户端通过该接口与实际对象或代理对象进行交互。
真实主题(Real Subject) 即目标类,是真正完成具体业务逻辑的类。例如某个服务的具体实现类。
代理主题(Proxy) 持有对真实主题的引用,实现与真实主题相同的接口。客户端调用代理主题的方法时,代理可对请求进行预处理(如权限校验、日志记录、性能监控等),然后再调用真实主题的方法。代理还可以根据需要对真实主题的返回值进行后处理。
1.3 代理模式的分类
静态代理(Static Proxy) 在代码编译阶段就已经确定好代理类,通常需要为每个真实主题编写一个对应的代理类,这会导致类数量剧增。静态代理的优点是结构简单,开发成本较低;缺点是耦合度高、可维护性差。
动态代理(Dynamic Proxy) 在运行时动态生成代理类,无需手动编写代理类。常见实现方式包括:
JDK 动态代理:只能为实现了接口的类创建代理
CGLIB 动态代理:既可以代理接口,也可以代理普通类,基于字节码生成技术(ASM)
Javassist 动态代理:同样基于字节码操作,可用于实现 AOP 等功能
二、静态代理
静态代理的实现思路是:为某个接口或类编写一个代理类,代理类持有真实主题对象的引用,在代理方法中添加增强逻辑,再调用真实主题的方法。下面以 OrderService
为例,展示静态代理的三种解决方案。
2.1 接口定义
首先定义一个 OrderService
接口,包含生成订单、修改订单、查看订单明细三个方法:
package com.sangui.mall.service;
/**
* @Author: sangui
* @CreateTime: 2025-05-31
* @Description: 订单服务接口
* @Version: 1.0
*/
public interface OrderService {
/** 生成订单 */
void generate();
/** 修改订单信息 */
void modify();
/** 查看订单明细 */
void detail();
}
2.2 真实主题实现
在没有性能监控需求之前,OrderServiceImpl
类直接实现业务逻辑:
package com.sangui.mall.service.impl;
import com.sangui.mall.service.OrderService;
/**
* @Author: sangui
* @CreateTime: 2025-05-31
* @Description: 订单服务实现类
* @Version: 1.0
*/
public class OrderServiceImpl implements OrderService {
public void generate() {
// 假设这是业务逻辑,模拟生成订单耗时
try {
Thread.sleep(1234);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("订单已生成!!");
}
public void modify() {
// 模拟修改订单耗时
try {
Thread.sleep(456);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("订单已修改!!");
}
public void detail() {
// 模拟查询订单明细耗时
try {
Thread.sleep(111);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("订单请查看!!.........展开");
}
}
2.3 继承方式(子类继承真实主题)
项目上线一年后,产品经理提出要统计每个业务方法的耗时。最直接的做法是:在每个业务方法内部添加计时代码,但这会改变原有代码且重复度极高,因此不符合开闭原则(OCP)。
一种改进方案是:通过继承 OrderServiceImpl
,在子类中重写业务方法,添加计时代码。示例见 OrderServiceImplSub
:
package com.sangui.mall.service.impl;
/**
* @Author: sangui
* @CreateTime: 2025-05-31
* @Description: 继承自 OrderServiceImpl 的子类,用于添加计时代码
* @Version: 1.0
*/
public class OrderServiceImplSub extends OrderServiceImpl {
public void generate() {
long begin = System.currentTimeMillis();
super.generate();
long end = System.currentTimeMillis();
System.out.println("生成订单耗时:" + (end - begin) + "ms");
}
public void modify() {
long begin = System.currentTimeMillis();
super.modify();
long end = System.currentTimeMillis();
System.out.println("修改订单耗时:" + (end - begin) + "ms");
}
public void detail() {
long begin = System.currentTimeMillis();
super.detail();
long end = System.currentTimeMillis();
System.out.println("查看订单明细耗时:" + (end - begin) + "ms");
}
}
缺点
继承关系耦合度高,不利于后续维护;
仍未解决多处重复逻辑的问题,如果有多个服务都需要计时,就要为每个服务都写子类,代码复用性差。
2.4 静态代理实现
静态代理通过独立的代理类实现对原有业务的增强。下面给出 OrderServiceProxy
示例。该代理类实现了同样的 OrderService
接口,持有目标对象的引用,并在调用真实方法之前后做计时操作:
package com.sangui.mall.service.impl;
import com.sangui.mall.service.OrderService;
/**
* @Author: sangui
* @CreateTime: 2025-05-31
* @Description: 静态代理类
* @Version: 1.0
*/
public class OrderServiceProxy implements OrderService {
/** 目标对象(真实主题) */
private OrderService target;
/** 构造方法:传入真实主题对象 */
public OrderServiceProxy(OrderService target) {
this.target = target;
}
public OrderServiceProxy() {
}
public void generate() {
long begin = System.currentTimeMillis();
target.generate();
long end = System.currentTimeMillis();
System.out.println("生成订单耗时:" + (end - begin) + "ms");
}
public void modify() {
long begin = System.currentTimeMillis();
target.modify();
long end = System.currentTimeMillis();
System.out.println("修改订单耗时:" + (end - begin) + "ms");
}
public void detail() {
long begin = System.currentTimeMillis();
target.detail();
long end = System.currentTimeMillis();
System.out.println("查看订单明细耗时:" + (end - begin) + "ms");
}
}
客户端调用示例
package com.sangui.mall.client;
import com.sangui.mall.service.OrderService;
import com.sangui.mall.service.impl.OrderServiceImpl;
import com.sangui.mall.service.impl.OrderServiceProxy;
/**
* @Author: sangui
* @CreateTime: 2025-05-31
* @Description: 客户端测试静态代理
* @Version: 1.0
*/
public class Test {
public static void main(String[] args) {
// 创建目标对象
OrderService target = new OrderServiceImpl();
// 创建代理对象,将目标对象传入
OrderService proxy = new OrderServiceProxy(target);
// 通过代理对象调用方法(真实逻辑 + 计时代码)
proxy.generate();
proxy.detail();
proxy.modify();
}
}
优点
符合开闭原则:在扩展新功能时不修改原有代码,只需要新增代理类;
将公共的增强逻辑集中到代理类,避免重复代码。
缺点
每个真实主题都需要编写一个对应的代理类,当服务数量较多时,会导致类数量爆炸,不易维护。
代理类与真实主题通过接口或继承关联,但仍存在一定耦合。
三、动态代理
动态代理的核心在于:在程序运行阶段动态生成代理类,而无需手动编写每一个代理类,从而减少了类数量。常见技术有 JDK 动态代理、CGLIB 动态代理、Javassist 等。
3.1 JDK 动态代理
JDK 动态代理要求真实主题必须实现接口,通过 java.lang.reflect.Proxy
在运行时创建代理对象。下面继续使用 OrderService
接口与 OrderServiceImpl
类进行演示。
3.1.1 真实主题、接口定义
package com.sangui.mall.service;
/**
* @Author: sangui
* @CreateTime: 2025-05-31
* @Description: 订单服务接口
* @Version: 1.0
*/
public interface OrderService {
/** 测试有返回值的方法 */
String getName();
/** 生成订单 */
void generate();
/** 修改订单信息 */
void modify();
/** 查看订单明细 */
void detail();
}
package com.sangui.mall.service.impl;
import com.sangui.mall.service.OrderService;
/**
* @Author: sangui
* @CreateTime: 2025-05-31
* @Description: 订单服务实现类
* @Version: 1.0
*/
public class OrderServiceImpl implements OrderService {
public String getName() {
System.out.println("getName 方法执行!");
return "zhangsan";
}
public void generate() {
// 模拟生成订单耗时
try {
Thread.sleep(1234);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("订单已生成!!");
}
public void modify() {
// 模拟修改订单耗时
try {
Thread.sleep(456);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("订单已修改!!");
}
public void detail() {
// 模拟查询订单明细耗时
try {
Thread.sleep(111);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("订单请查看!!.........展开");
}
}
3.1.2 调用处理器(InvocationHandler)
创建一个实现了 InvocationHandler
接口的调用处理器 TimerInvocationHandler
,用于在调用任意方法时,打印方法执行耗时:
package com.sangui.mall.service;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
/**
* @Author: sangui
* @CreateTime: 2025-06-01
* @Description: 专门负责计时的调用处理器
* @Version: 1.0
*/
public class TimerInvocationHandler implements InvocationHandler {
/** 目标对象 */
private OrderService target;
public TimerInvocationHandler() {
}
public TimerInvocationHandler(OrderService target) {
this.target = target;
}
/**
* 当代理对象调用方法时,会触发 invoke 方法
*
* @param proxy 代理对象
* @param method 要执行的方法
* @param args 方法参数
* @return 返回值
* @throws Throwable 可能抛出的异常
*/
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
long begin = System.currentTimeMillis();
// 执行真实主题方法
Object returnValue = method.invoke(target, args);
long end = System.currentTimeMillis();
System.out.println("耗时:" + (end - begin) + "ms");
return returnValue;
}
}
3.1.3 客户端使用示例
客户端代码无需手动编写代理类,只需通过 Proxy.newProxyInstance
动态生成代理对象:
package com.sangui.mall.client;
import com.sangui.mall.service.OrderService;
import com.sangui.mall.service.TimerInvocationHandler;
import com.sangui.mall.service.impl.OrderServiceImpl;
import java.lang.reflect.Proxy;
/**
* @Author: sangui
* @CreateTime: 2025-06-01
* @Description: 客户端测试 JDK 动态代理
* @Version: 1.0
*/
public class Client {
public static void main(String[] args) {
// 创建真实主题对象
OrderService target = new OrderServiceImpl();
// 动态生成代理对象
OrderService proxy = (OrderService) Proxy.newProxyInstance(
target.getClass().getClassLoader(), // 类加载器:与目标类保持一致
target.getClass().getInterfaces(), // 代理类要实现的接口
new TimerInvocationHandler(target) // 调用处理器:实现增强逻辑
);
// 通过代理对象调用方法,既执行真实逻辑,也打印耗时
proxy.detail();
proxy.modify();
proxy.generate();
String name = proxy.getName();
System.out.println("返回值:" + name);
}
}
3.1.4 Proxy 工具类封装
为了减少客户端中 Proxy.newProxyInstance(…)
的冗长语句,可对其进行封装。例如在 ProxyUtil
中提供一个静态方法,使客户端调用更简洁:
package com.sangui.mall.utils;
import com.sangui.mall.service.TimerInvocationHandler;
import java.lang.reflect.Proxy;
/**
* @Author: sangui
* @CreateTime: 2025-06-01
* @Description: 对 JDK 动态代理的封装工具类
* @Version: 1.0
*/
public class ProxyUtil {
private ProxyUtil() {
// 私有化构造函数,防止实例化
}
/**
* 返回代理对象
* @param target 真实主题对象
* @return 代理对象
*/
public static Object newProxyInstance(Object target) {
return Proxy.newProxyInstance(
target.getClass().getClassLoader(),
target.getClass().getInterfaces(),
new TimerInvocationHandler((com.sangui.mall.service.OrderService) target)
);
}
}
客户端代码示例:
// 原本需要写:
// OrderService proxy = (OrderService) Proxy.newProxyInstance(
// target.getClass().getClassLoader(),
// target.getClass().getInterfaces(),
// new TimerInvocationHandler(target)
// );
OrderService proxy = (OrderService) ProxyUtil.newProxyInstance(target);
3.2 CGLIB 动态代理
CGLIB(Code Generation Library)是一种基于字节码生成的代理技术,既可以代理接口,也可以直接代理普通类(通过继承方式)。它底层依赖 ASM 框架,使得生成的代理类性能优于 JDK 动态代理。需要注意的是,被代理的目标类不能使用 final
修饰,否则无法生成子类。
3.2.1 引入依赖
在 pom.xml
中添加 CGLIB 依赖:
<dependency>
<groupId>cglibgroupId>
<artifactId>cglibartifactId>
<version>3.3.0version>
dependency>
3.2.2 目标类示例
下面以一个用户服务 UserService
为例,不再需要特定接口,直接编写普通类即可:
package com.sangui.mall.service;
/**
* @Author: sangui
* @CreateTime: 2025-06-01
* @Description: 用户服务类(无接口)
* @Version: 1.0
*/
public class UserService {
/**
* 登录方法
* @param username 用户名
* @param password 密码
* @return 验证结果
*/
public boolean login(String username, String password) {
System.out.println("正在验证身份……");
return "admin".equals(username) && "123".equals(password);
}
/** 退出方法 */
public void logout() {
System.out.println("退出系统中……");
}
}
3.2.3 方法拦截器(MethodInterceptor)
CGLIB 的代理通过 MethodInterceptor
拦截器实现增强逻辑。在 intercept
方法中,可在调用真实方法前后完成计时或其他操作:
package com.sangui.mall.service;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
/**
* @Author: sangui
* @CreateTime: 2025-06-01
* @Description: CGLIB 方法拦截器,用于添加计时功能
* @Version: 1.0
*/
public class TimerMethodInterceptor implements MethodInterceptor {
/**
* @param obj 代理对象
* @param method 即将调用的方法
* @param args 方法参数
* @param methodProxy CGLIB 方法代理,用于调用父类方法
* @return 方法返回值
* @throws Throwable 可能抛出的异常
*/
public Object intercept(Object obj, Method method, Object[] args, MethodProxy methodProxy)
throws Throwable {
long begin = System.currentTimeMillis();
// 调用父类(真实主题)的方法
Object returnValue = methodProxy.invokeSuper(obj, args);
long end = System.currentTimeMillis();
System.out.println("耗时:" + (end - begin) + "ms");
return returnValue;
}
}
3.2.4 客户端使用示例
客户端通过 CGLIB 的 Enhancer
类生成代理对象,无需手动编写任何代理类:
package com.sangui.mall.client;
import com.sangui.mall.service.TimerMethodInterceptor;
import com.sangui.mall.service.UserService;
import net.sf.cglib.proxy.Enhancer;
/**
* @Author: sangui
* @CreateTime: 2025-06-01
* @Description: 客户端测试 CGLIB 动态代理
* @Version: 1.0
*/
public class Client {
public static void main(String[] args) {
// 创建 CGLIB 增强器对象
Enhancer enhancer = new Enhancer();
// 指定父类,即真实主题类
enhancer.setSuperclass(UserService.class);
// 设置拦截器(相当于 JDK 的 InvocationHandler)
enhancer.setCallback(new TimerMethodInterceptor());
// 创建代理对象:Step1 生成子类字节码,Step2 实例化
UserService proxy = (UserService) enhancer.create();
// 通过代理对象调用方法,将打印计时信息
proxy.login("admin", "123");
proxy.login("admin", "123456");
proxy.logout();
}
}
注意 在 JDK 9 及以上版本使用 CGLIB 时,可能需要在启动参数中添加以下配置:
--add-opens java.base/java.lang=ALL-UNNAMED
--add-opens java.base/sun.net.util=ALL-UNNAMED以便开放相关模块,允许 CGLIB 的字节码生成和反射操作。
四、代理模式适用场景与优缺点
4.1 适用场景
远程代理(Remote Proxy) 客户端通过代理对象访问远程主机上的资源,如 RMI 中的远程服务访问。
虚拟代理(Virtual Proxy) 当真实对象体积过大、创建开销较高时,可用代理对象延迟创建真实对象。例如:图像加载时,先显示占位图,点击或达到一定条件后再加载真实图像。
安全代理/防护代理(Protection Proxy) 为真实对象提供访问控制,根据权限判断是否允许访问。例如:在敏感业务前进行登录校验、权限检查。
缓存代理(Cache Proxy) 对一些耗时操作的结果进行缓存,下次相同请求直接返回缓存结果,避免重复计算。
日志/计时代理(Logging/Timing Proxy) 用于记录方法调用日志或统计执行时间,可在不修改真实业务代码的前提下,实现同样效果。
4.2 优缺点
优点
代理对象可以在客户端与真实对象之间起到中介作用,并在不修改真实对象代码的前提下进行功能扩展;
客户端与代理对象交互时,不需要区分代理或真实对象,易于维护;
动态代理技术大大简化了代理类的编写,提高了代码复用性。
缺点
静态代理会导致类数量剧增,不易维护;
动态代理在首次生成代理类时会有一定性能损耗(如 JDK 代理创建较慢);
CGLIB 代理需要依赖字节码生成库,对启动时机和运行环境有一定要求。
五、总结
- 微信
- 赶快加我聊天吧
- 赶快加我聊天吧
2025年06月02日 21:23:32 1楼
您真牛逼