当前位置: 首页 > news >正文

【 设计模式 | 结构型模式 代理模式 】

摘要:本文介绍设计模式中的代理模式,核心是通过代理在不修改真实对象代码的前提下实现功能增强。含静态代理动态代理(JDK 动态代理,CGLIB 动态代理)实现方式。最后还对比了三种代理的实现基础、限制、性能与适用场景。

思维导图

1. 代理模式

1.1 概述

代理模式是一种旨在控制目标对象访问的设计模式:当外部访问对象因目标对象存在访问限制、需附加额外逻辑(如日志记录、性能监控)等原因,不适合或无法直接引用目标对象时,会通过一个 "代理对象" 作为中介,间接实现对目标对象的交互。

在 Java 技术体系中,代理的实现可根据 "代理类生成时机" 分为两大类别:

1. 静态代理:代理类的代码在项目编译阶段就已生成,与目标类的字节码一同存在于最终产物中;​2. 动态代理:代理类并非提前编译好,而是在 Java 程序运行过程中,根据实际业务需求动态创建并加载,无需手动编写代理类代码。

1.2 结构

代理模式分为三种角色:

1. 抽象主题类: 通过接口或抽象类声明真实主题和代理对象实现的业务方法。

2. 真实主题类: 实现了抽象主题中的具体业务,是代理对象所代表的真实对象,是最终对象。

3. 代理类 : 提供了与真实主题相同的接口,其内部含有对真实主题的引用,它可以访问、控制或扩展真实主题的功能。

1.3 静态代理

例】火车站卖票:如果要买火车票的话,需要去火车站买票,坐车到火车站,排队等一系列的操作,显然比较麻烦。而火车站在多个地方都有代售点,我们去代售点买票就方便很多了。这个例子其实就是典型的代理模式,火车站是目标对象,代售点是代理对象。

代码如下:

//卖票接口
public interface SellTickets {void sell();
}//火车站  火车站具有卖票功能,所以需要实现SellTickets接口
public class TrainStation implements SellTickets {public void sell() {System.out.println("火车站卖票");}
}//代售点
public class ProxyPoint implements SellTickets {private TrainStation station = new TrainStation();public void sell() {System.out.println("代理点收取一些服务费用");station.sell();}
}//测试类
public class Client {public static void main(String[] args) {ProxyPoint pp = new ProxyPoint();pp.sell();}
}

可以看出测试类直接访问的是ProxyPoint类对象,也就是说ProxyPoint作为访问对象和目标对象的中介。同时也对sell方法进行了增强(代理点收取一些服务费用)。

1.4 JDK动态代理

接下来我们使用动态代理实现上面案例,先说说JDK提供的动态代理。Java中提供了一个动态代理类Proxy,Proxy并不是我们上述所说的代理对象的类,而是提供了一个创建代理对象的静态方法(newProxyInstance方法)来获取代理对象。

代码如下:

//卖票接口
public interface SellTickets {void sell();
}//火车站  火车站具有卖票功能,所以需要实现SellTickets接口
public class TrainStation implements SellTickets {public void sell() {System.out.println("火车站卖票");}
}//代理工厂,用来创建代理对象
public class ProxyFactory {private TrainStation station = new TrainStation();public SellTickets getProxyObject() {//使用Proxy获取代理对象/*newProxyInstance()方法参数说明:ClassLoader loader : 类加载器,用于加载代理类,使用真实对象的类加载器即可Class<?>[] interfaces : 真实对象所实现的接口,代理模式真实对象和代理对象实现相同的接口InvocationHandler h : 代理对象的调用处理程序*/SellTickets sellTickets = (SellTickets) Proxy.newProxyInstance(station.getClass().getClassLoader(),station.getClass().getInterfaces(),new InvocationHandler() {/*InvocationHandler中invoke方法参数说明:proxy : 代理对象method : 对应于在代理对象上调用的接口方法的 Method 实例args : 代理对象调用接口方法时传递的实际参数*/public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {System.out.println("代理点收取一些服务费用(JDK动态代理方式)");//执行真实对象Object result = method.invoke(station, args);return result;}});return sellTickets;}
}//测试类
public class Client {public static void main(String[] args) {//获取代理对象ProxyFactory factory = new ProxyFactory();SellTickets proxyObject = factory.getProxyObject();proxyObject.sell();}
}

  核心角色说明

(1)SellTickets 接口

  • 这是一个功能接口,定义了 "卖票" 的规范(只有sell()方法)。
  • 作用:作为真实对象代理对象的共同接口,保证代理对象能以与真实对象相同的方式被调用(符合 "里氏替换原则")。

(2)TrainStation 类(真实主题)

  • 实现了SellTickets接口,是实际执行业务逻辑的类。
  • sell()方法的实现是 "火车站卖票" 的核心功能(打印 "火车站卖票")。
  • 角色:被代理的对象,专注于核心业务逻辑。

(3)ProxyFactory 类(代理工厂)

  • 用于创建代理对象的工厂类,内部持有一个真实对象(TrainStation实例)。
  • 核心方法getProxyObject()通过Proxy.newProxyInstance()生成代理对象,需要 3 个参数:
    • ClassLoader loader:类加载器,直接使用真实对象的类加载器。
    • Class<?>[] interfaces:真实对象实现的接口。
    • InvocationHandler h:代理对象的 "方法调用处理器",是实现功能增强的核心。

(4)InvocationHandler 接口(方法调用处理器)

  • 这是一个匿名内部类实现,定义了代理对象调用方法时的 "增强逻辑"。
  • 核心方法invoke()在代理对象调用任何方法时都会被自动触发,包含 3 个参数:
    • proxy:当前的代理对象(一般不用)。
    • method:被调用的方法(这里是sell()方法的Method实例)。
    • args:调用方法时传递的参数(这里sell()无参数,所以为null)。
  • 逻辑:先执行增强操作,再通过method.invoke(station, args)调用真实对象的sell()方法,实现 "增强 + 核心业务" 的组合执行。

1.5 CGLIB动态代理

当没有定义 SellTickets 接口,仅存在 TrainStation 类时,由于 JDK 动态代理依赖接口实现,无法直接使用,此时可采用 CGLIB 代理来解决。

CGLIB 作为一个高性能的代码生成包,能为没有实现接口的类创建代理,它通过继承目标类(TrainStation)生成代理子类的方式实现功能增强,很好地补充了 JDK 动态代理在无接口场景下的不足,从而实现在不修改 TrainStation 类代码的前提下,对其卖票功能进行扩展(如添加服务费用收取等操作)。

引入依赖:

<dependency><groupId>cglib</groupId><artifactId>cglib</artifactId><version>2.2.2</version>
</dependency>

代码如下:

//火车站
public class TrainStation {public void sell() {System.out.println("火车站卖票");}
}//代理工厂
public class ProxyFactory implements MethodInterceptor {private TrainStation target = new TrainStation();public TrainStation getProxyObject() {//创建Enhancer对象,类似于JDK动态代理的Proxy类,下一步就是设置几个参数Enhancer enhancer =new Enhancer();//设置父类的字节码对象enhancer.setSuperclass(target.getClass());//设置回调函数enhancer.setCallback(this);//创建代理对象TrainStation obj = (TrainStation) enhancer.create();return obj;}/*intercept方法参数说明:o : 代理对象method : 真实对象中的方法的Method实例args : 实际参数methodProxy :代理对象中的方法的method实例*/public TrainStation intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {System.out.println("代理点收取一些服务费用(CGLIB动态代理方式)");TrainStation result = (TrainStation) methodProxy.invokeSuper(o, args);return result;}
}//测试类
public class Client {public static void main(String[] args) {//创建代理工厂对象ProxyFactory factory = new ProxyFactory();//获取代理对象TrainStation proxyObject = factory.getProxyObject();proxyObject.sell();}
}

1.6 三种代理的对比

JDK 动态代理 vs CGLIB 代理

  • 实现基础:JDK 动态代理基于接口实现,要求目标类必须实现接口,代理对象是接口的实现类;CGLIB 代理基于继承实现,无需目标类实现接口,通过生成目标类的子类作为代理类。
  • 限制:JDK 代理仅能代理接口中的方法;CGLIB 不能代理 final 类或 final 方法。
  • 性能:JDK 1.6 及之前,CGLIB 因基于字节码生成,效率高于 JDK 代理;JDK 1.8 优化后,JDK 代理在多数场景(调用次数较少时)效率更高,仅在大量调用时与 CGLIB 接近或略优。
  • 使用场景有接口时优先用 JDK 代理,无接口时只能用 CGLIB 代理。

动态代理(JDK/CGLIB)vs 静态代理

  • 代码维护:静态代理需为每个接口方法编写代理逻辑,接口新增方法时,所有实现类和代理类都需修改,维护成本高;动态代理通过集中处理器(如 InvocationHandler)统一处理所有方法,接口变更时无需修改代理逻辑,灵活性更高。
  • 生成时机:静态代理的代理类在编译期手动编写完成;动态代理的代理类在运行时动态生成。
  • 适用场景:静态代理适用于接口方法少、变更少的简单场景;动态代理适用于接口方法多、频繁变更的复杂场景,可大幅减少重复代码。

综上,动态代理在灵活性和维护性上优于静态代理,而 JDK 代理与 CGLIB 代理的选择主要取决于是否有接口及 JDK 版本。

1.7 应用场景

静态代理代理类在编译期手动编写,与目标类实现同一接口,一对一代理

常见场景

  • 简单功能增强:如对单个类的特定方法添加日志计时等简单增强(方法数量少且稳定)。
  • 访问控制:如对敏感接口(如支付接口)设置临时访问权限校验,代理类可在调用真实方法前拦截非法请求
  • 测试环境模拟:在单元测试中,用静态代理模拟第三方服务(如模拟支付接口返回固定结果,避免真实调用)。

JDK 动态代理基于接口动态生成代理类,运行时创建,需目标类实现接口。

常见场景

  • 框架级通用增强:如 Spring AOP 的默认代理方式(对实现接口的 Bean),用于事务管理、日志记录、异常处理等横切逻辑。
  • RPC 框架:如 Dubbo 中对服务接口的代理,通过代理类实现远程调用(序列化、网络传输等逻辑封装)。
  • 权限校验:在分布式系统中,对接口统一添加权限拦截(如登录态校验、接口访问限流)。
  • 数据源路由:在多数据源场景中,通过代理动态切换数据源(如读写分离,根据方法名路由到主库或从库)。

CGLIB 代理基于继承动态生成代理子类,无需目标类实现接口,可代理非 final 类 / 方法。

常见场景

  • 无接口类的增强:如对 POJO 类、工具类等未实现接口的类进行增强(如添加缓存逻辑)。
  • 第三方库适配:当依赖的第三方类未实现接口,但需要增强其方法时(如对第三方 HTTP 客户端添加超时重试逻辑)。
  • Spring 框架补充:Spring 中对未实现接口的 Bean 默认使用 CGLIB 代理。
  • ORM 框架:如 MyBatis 中对 Mapper 接口的代理(虽然 Mapper 是接口,但底层结合 CGLIB 实现动态 SQL 执行逻辑)。

大功告成!

http://www.dtcms.com/a/394770.html

相关文章:

  • 小杰机器学习高级(five)——分类算法的评估标准
  • IS-IS 中同时收到 L1 和 L2 的 LSP 时,是否优选 L1
  • 【开源】基于STM32的智能车尾灯
  • 电子电气架构 --- 软件开发与产品系统集成流程(下)
  • Ubuntu系统目录架构是怎么样的
  • 自动驾驶仿真之“场景交互”技术研究
  • 《AI管家还是数字化身?—— 一种面向未来的个人智能架构构想》
  • AI提升工业生产制造安全,基于YOLOv9全系列【yolov9/t/s/m/c/e】参数模型开发构建工业生产制造加工场景下工业设备泄漏智能化检测识别预警系统
  • 深度学习(十一):深度神经网络和前向传播
  • js立即执行函数的几种写法
  • RecyclerView里更新列表数是不想让header也刷新,怎么处理
  • C#/.NET/.NET Core技术前沿周刊 | 第 55 期(2025年9.15-9.21)
  • 减少实验烦恼,革新实验效率——PFA塑料容量瓶降低实验成本与风险
  • 留给石头科技的赛道不多了
  • 基于卷积神经网络的人车识别技术:从原理突破到场景重构的深度探索
  • 信用免押租赁服务:重构消费信任体系的全球增长引擎
  • Redis数据迁移实战:从自建到云托管(阿里云/腾讯云)的平滑过渡
  • 从梵高到赛博格:我用4K模型重构艺术史的未来可能性-Seedream 4.0 实测
  • Mysql DBA学习笔记(Redo Log/Undo Log)
  • 买卖T平台如何以分红+排队免单重构零售生态?
  • 2025 年前端工具全景解析:从框架到 AI,重构开发效率的 N 种可能
  • 重构ruoyi前后端分离版
  • AI + 制造:AI 如何重构制造业的质检与排产流程
  • 卡尔曼滤波
  • Django安全完全指南:构建坚不可摧的Web应用
  • Mysql DBA学习笔记(MVCC)
  • 【论文阅读】GR-1:释放大规模视频生成式预训练用于视觉机器人操控
  • 分布式光伏阴影轨迹模拟
  • 【Java.数据结构】初识集合框架
  • 人工智能的推理方法实验-用归结原理解决机器人搬盒子问题