编辑
2025-08-28
技术文章
00

目录

思路
知识点串联
概要
代理模式的由来,有什么用?
代理模式的实现
静态代理
小结
动态代理
JDK动态代理——基于接口实现
JDK 动态代理小结
CGLIB动态代理——基于继承实现
CGLIB 动态代理小结
静态代理与动态代理的对比
实战改造:给业务系统装上“监控探头”
改造前:痛苦的“硬编码”
改造后:优雅的动态代理
代理模式的意义

本文主要串联动态代理中的知识点,并梳理一下代理模式的发展史,进而映出其如何迭代实现现在开发中常用的代理模式,如Spring AOP、MyBatis等框架。

思路

知识点串联

这里面主要涉及到了这几个技术:字节码操作、类加载、反射、动态代理。

JVM 基于类加载器方法区支撑实现了java.lang.reflect反射API

通过使用反射,对设计原则代理模式的实现静态代理进行改进,基于反射实现了动态代理

  • JVM 角色:JVM 的类加载器和方法区(元空间)为 Java 反射提供了必要的运行时数据基础,但反射本身是通过 java.lang.reflect 这套 API 实现的。
  • 反射与动态代理JDK 动态代理的实现核心依赖于 Java 反射机制(特别是 Proxy 类的生成和 Method.invoke() 的调用)。反射使得在运行时动态创建代理类和动态调用目标方法成为可能,从而克服了静态代理的局限性,实现了更灵活的代理模式。
  • 改进关系:可以说,利用反射技术,对传统的静态代理模式进行了改进,实现了更加灵活和通用的动态代理

概要

本质上动态代理只是对静态代理中,代理对象的生成从编写固定的代理类代码实现到采取的动态生成代理对象的改进。从而实现一个类似的代理对象,来对目标对象包装进行内部调用。

根据实现动态代理的方式不同,主要分为基于接口实现的JDK动态代理和基于子类实现的CGLIB。

JDK 动态代理,通过代理对象实现目标对象的相同接口,用多态实现代理,对目标对象进行包裹然后内部调用,通过接口可以保证代理对象和目标对象的类同;

CGLIB则是通过继承代理,通过字节码生成代理子类对象包装目标对象,重写和super调用父类原方法进行代理)

代理模式的由来,有什么用?

在我们平常的代码中,会有一些重复的功能(比如日志记录、事务管理、权限验证等)。在传统的编程方式中,如果我们要为一个类添加这些功能,通常有两种选择:一种是在类代码中直接添加before()after()这样的函数调用,并在这些函数中实现相应的逻辑;另一种是通过组合方式引入功能类,将这些功能作为参数传递给目标类。但无论采用哪种方式,都会导致代码冗余或耦合度增加的问题。

如果在类中直接实现这些功能,会导致代码冗余。例如,当多个类都需要添加日志功能时,每个类都需要重复编写相同的日志记录代码,这不仅违反了"不要重复自己"(DRY)原则,还增加了维护成本。如果日志格式需要变更,就需要修改所有包含日志代码的类,容易出错且效率低下。

java
public class UserServiceImpl { public void saveUser() { // 代码冗余:每个方法都要写一遍日志记录 System.out.println("[Log] 开始执行 saveUser 操作"); long startTime = System.currentTimeMillis(); // 核心业务逻辑(只有这一行是干正事的) System.out.println("正在将用户数据写入数据库..."); // 维护困难:如果需要修改日志格式,所有类都要改 long endTime = System.currentTimeMillis(); System.out.println("[Log] 执行结束,耗时:" + (endTime - startTime) + "ms"); } }

如果通过组合方式引入功能类,会导致耦合入侵。例如,为实现事务管理功能,目标类需要持有事务管理器的引用,并在方法执行前后调用事务管理器的开始和提交方法。这种做法虽然避免了代码重复,但目标类却被迫知晓事务管理的实现细节,破坏了单一职责原则,增加了类之间的依赖关系。

java
public class OrderServiceImpl { // 强耦合:业务类依赖了具体的“基础设施”类 private TransactionManager txManager = new TransactionManager(); public void createOrder() { try { // 侵入式代码:业务类不得不亲自管理事务生命周期 txManager.beginTransaction(); // 核心业务逻辑 System.out.println("正在创建订单..."); txManager.commit(); } catch (Exception e) { txManager.rollback(); throw e; } } }

代理模式正是为了解决这一矛盾而诞生的设计模式。它提供了一种既不造成代码冗余,又不引入耦合的方式,实现横切关注点的集中管理。通过代理对象,可以在不修改目标类代码的情况下,为其添加额外功能,使目标类专注于核心业务逻辑。

代理模式的实现

在开始讨论代理模式的具体实现之前,我们先通过一幅图片来简单抽象地描述代理模式中各个部分: ![[assets/漫谈代理模式,静态代理到 JDK 和 CGLIB 动态代理/file-20250828012154590.png]]

静态代理

静态代理是最基础的代理模式实现方式,它通过预先编写代理类来实现代理功能。静态代理的实现需要三个关键组件:

1. 接口定义:定义客户端与目标对象交互的契约。

java
public interface Service { void execute(); }

2. 被代理类实现:实现接口,提供核心业务逻辑。

java
public class RealService implements Service { @Override public void execute() { System.out.println("执行核心业务逻辑"); } }

3. 代理类实现:实现同一接口,内部持有目标对象引用,并在方法调用前后添加额外逻辑。

java
public class ProxyService implements Service { private Service realService; public ProxyService(Service realService) { this.realService = realService; } @Override public void execute() { before(); // 添加额外逻辑(如日志记录) realService.execute(); // 调用目标对象方法 after(); // 添加额外逻辑(如事务提交) } private void before() { System.out.println("代理方法前的额外操作"); } private void after() { System.out.println("代理方法后的额外操作"); } }

小结

静态代理的核心优势在于简单直观。它通过接口保证类型兼容性,使客户端无需修改即可使用代理对象。代理类通过持有目标对象的引用,可以在调用目标方法前后添加额外功能。

但静态代理也存在明显的局限性

  • 代码冗余:每个需要代理的接口都需要单独编写代理类,当系统中存在大量接口时,代码量会急剧增加。
  • 维护困难:如果接口方法发生变化,所有相关的代理类都需要同步修改,容易遗漏或出错。
  • 扩展性差:无法在运行时动态决定是否代理某个对象,也无法灵活地添加或移除代理功能。

动态代理

动态代理是对静态代理的革命性改进,它通过运行时自动生成代理类的方式,解决了静态代理的上述局限性。动态代理主要有两种实现方式:

多态实现(JDK 动态代理)和子类实现(CGLIB/Byte Buddy)这两种技术路径,其核心目标是为了解决动态代理模式中的一个根本性问题类型兼容性 (Type Compatibility)无缝替换 (Seamless Substitution)

JDK动态代理——基于接口实现

JDK动态代理基于接口实现,利用反射机制在运行时动态生成代理类。其核心组件包括:

1. 接口定义:与静态代理相同,定义客户端与目标对象交互的契约。

java
public interface Service { void execute(); }

2. 被代理类实现:实现接口,提供核心业务逻辑。

java
public class RealService implements Service { @Override public void execute() { System.out.println("执行核心业务逻辑"); } }

3. 代理处理器:实现InvocationHandler接口,定义代理逻辑。

java
public class ServiceProxy implementsInvocationHandler { private Object target; public ServiceProxy(Object target) { this.target = target; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { before(); // 添加额外逻辑(如日志记录) Object result = method.invoke(target, args); // 反射调用目标方法 after(); // 添加额外逻辑(如事务提交) return result; } private void before() { System.out.println("代理方法前的额外操作"); } private void after() { System.out.println("代理方法后的额外操作"); } public static Object getProxy(Object target) { return Proxy.newProxyInstance( target.getClass().getClassLoader(), target.getClass().getInterfaces(), new ServiceProxy(target) ); } }

[!tip] JDK 动态代理为什么必须依赖接口? 为什么必须是接口? 深入字节码会发现,JDK 生成的代理类(如 $Proxy0)继承自 java.lang.reflect.Proxy。由于 Java 的单继承机制,它无法再继承业务目标类,因此只能通过实现接口(Interface)的方式来与目标类保持类型一致。


JDK 动态代理小结

JDK动态代理的核心原理

  • 在运行时,通过Proxy.newProxyInstance()方法动态生成代理类。
  • 生成的代理类实现了目标对象的所有接口。
  • 代理对象通过InvocationHandler接口的invoke()方法实现对目标方法的拦截和增强。

JDK动态代理的优势

  • 简洁性:只需编写一个InvocationHandler实现类,即可代理所有实现特定接口的类。
  • 类型安全:通过接口保证类型兼容性,确保代理对象可以无缝替换目标对象。
  • 支持多接口:可以代理实现多个接口的目标对象。

JDK动态代理的局限性

  • 依赖接口:必须要有接口才能使用JDK动态代理,无法代理没有接口的普通类。
  • 性能开销:反射调用比直接方法调用有更高的性能开销。
  • 功能受限:无法代理final方法。

CGLIB动态代理——基于继承实现

CGLIB(Code Generation Library)是一种基于继承的动态代理技术,它直接通过字节码操作生成目标类的子类实现代理功能。其核心原理如下:

1. 目标类定义:普通Java类,无需实现特定接口。

java
public class RealService { public void execute() { System.out.println("执行核心业务逻辑"); } }

2. 方法拦截器:实现MethodInterceptor接口,定义代理逻辑。

java
public class ServiceInterceptor implements MethodInterceptor { @Override public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable { before(); // 添加额外逻辑(如日志记录) Object result = methodProxy.invokeSuper(proxy, args); // 调用父类方法 after(); // 添加额外逻辑(如事务提交) return result; } private void before() { System.out.println("代理方法前的额外操作"); } private void after() { System.out.println("代理方法后的额外操作"); } }

3. 代理对象创建:使用Enhancer类动态生成代理对象。

java
public static RealService getProxy() { Enhancer enhancer = new Enhancer(); enhancer.set Superclass(RealService.class); enhancer.setCallback(new ServiceInterceptor()); return (RealService) enhancer.create(); }

CGLIB 动态代理小结

CGLIB动态代理的核心原理

  • 在运行时,通过字节码操作技术(基于ASM框架)动态生成目标类的子类。
  • 生成的子类重写目标类的所有非final方法。
  • 在重写的方法中,插入对拦截器的调用,实现对目标方法的拦截和增强。

CGLIB动态代理的优势

  • 无接口限制:可以代理没有实现接口的普通类。
  • 性能更好:基于字节码生成的代理,性能优于JDK的反射机制。
  • 灵活性更高:可以代理类的方法,而不仅仅是接口方法。

CGLIB动态代理的局限性

  • 无法代理final类或方法:Java的final关键字限制了类或方法的继承和重写,因此CGLIB无法代理这些内容。
  • 依赖字节码操作:需要引入额外的依赖库(如ASM)。
  • 复杂性更高:实现和理解比JDK动态代理更复杂。

静态代理与动态代理的对比

对比维度静态代理JDK动态代理CGLIB动态代理
生成方式手动编写代理类运行时基于接口生成代理类运行时基于类生成子类
依赖要求依赖接口或继承必须依赖接口可代理普通类,但不能代理final
维护成本高(接口变更需同步修改所有代理类)低(只需修改InvocationHandler低(只需修改拦截器)
扩展性差(需手动修改代理类)好(支持多接口,可动态代理)更好(无接口限制,可动态代理)
性能开销高(反射调用)中(字节码生成)
代码量大(每个接口需一个代理类)小(一个处理器代理多个接口)最小(一个拦截器代理多个类)

实战改造:给业务系统装上“监控探头”

光说不练假把式,这里我们模拟一个真实的业务场景:系统性能监控

假设我们有一个核心的订单服务 OrderService,为了排查线上性能问题,领导要求我们统计每个方法的执行耗时,如果超过 100ms 则打印“慢接口告警”日志。

改造前:痛苦的“硬编码”

如果不使用代理模式,我们不得不修改每个业务方法,把计时逻辑硬塞进去。代码会变成这样:

java
public class OrderServiceImpl implements OrderService { @Override public void createOrder(String orderId) { // 【重复代码】开始计时 long start = System.currentTimeMillis(); // 核心业务逻辑 try { System.out.println("正在创建订单: " + orderId); Thread.sleep(150); // 模拟业务耗时 } catch (InterruptedException e) { e.printStackTrace(); } // 【重复代码】结束计时与逻辑判断 long end = System.currentTimeMillis(); long cost = end - start; if (cost > 100) { System.err.println("【慢接口告警】Method: createOrder, Cost: " + cost + "ms"); } } @Override public void queryOrder(String orderId) { // 【重复代码】又是同样的逻辑,复制粘贴导致满屏都是非业务代码... long start = System.currentTimeMillis(); // ...业务逻辑... // ...结束计时... } }

痛点显而易见:业务逻辑被监控代码淹没,如果以后要改为“超过 200ms 告警”,我们要修改几十个文件,简直是维护灾难。

改造后:优雅的动态代理

现在,我们使用 JDK 动态代理 来重构。我们要做的仅仅是把“计时逻辑”抽取到一个独立的处理器中。

第一步:定义性能检测处理器(InvocationHandler) 这个处理器是通用的,它可以监控任何服务,而不仅仅是订单服务。

java
public class PerformanceMonitorHandler implements InvocationHandler { private Object target; // 真实业务对象 public PerformanceMonitorHandler(Object target) { this.target = target; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { // 1. 前置增强:记录开始时间 long start = System.currentTimeMillis(); // 2. 调用真实业务逻辑 Object result = method.invoke(target, args); // 3. 后置增强:计算耗时与告警 long end = System.currentTimeMillis(); long cost = end - start; System.out.println("接口 " + method.getName() + " 耗时: " + cost + "ms"); if (cost > 100) { System.err.println("【慢接口告警】检测到性能问题!Method: " + method.getName()); } return result; } }

第二步:清爽的业务类 OrderServiceImpl 瞬间恢复了它该有的样子,只关注业务,没有任何杂质:

java
public class OrderServiceImpl implements OrderService { @Override public void createOrder(String orderId) { // 只写核心业务,无需关心监控逻辑 try { System.out.println("正在创建订单: " + orderId); Thread.sleep(150); } catch (InterruptedException e) { e.printStackTrace(); } } }

第三步:组装与运行 在实际使用处(或者 Spring 的 Bean 初始化阶段),我们将代理对象动态生成出来:

java
public class Main { public static void main(String[] args) { // 1. 创建真实业务对象 OrderService realService = new OrderServiceImpl(); // 2. 动态生成代理对象(挂载了性能监控功能) OrderService proxyService = (OrderService) Proxy.newProxyInstance( realService.getClass().getClassLoader(), realService.getClass().getInterfaces(), new PerformanceMonitorHandler(realService) ); // 3. 调用代理对象 // 客户端完全感知不到代理的存在,但监控功能已经生效 proxyService.createOrder("CN-8888"); } }

通过这个例子,我们可以清晰地看到代理模式是如何实现**“业务与基础设施分离”**的。

  1. 业务开发者只负责写 createOrder,不需要关心监控。
  2. 架构师统一维护 PerformanceMonitorHandler,修改告警阈值只需改一处地方。
  3. 系统实现了高内聚(业务纯粹)和低耦合(功能分离)

代理模式的意义

到最后我们再重新从一个整体的视角去对代理模式意义进行一个审视总结:

1. 解耦与复用:通过代理对象,可以将核心业务逻辑与横切关注点(如日志、事务、安全等)分离,避免代码冗余和入侵式修改。例如,一个系统中有多个服务类需要添加日志功能,通过代理模式,只需在代理对象中实现日志逻辑,而无需在每个服务类中重复编写。

2. 灵活性:动态代理支持运行时生成代理对象,可以根据需要动态决定是否代理某个对象,以及代理哪些方法。例如,在Spring框架中,可以通过配置决定哪些Bean需要事务管理,而无需修改这些Bean的代码。

3. 扩展性:动态代理使得系统更容易扩展新功能。例如,当需要为系统添加新的横切关注点(如性能监控)时,只需修改代理逻辑,而无需修改所有目标类。

4. 框架基石:动态代理是许多主流框架的核心技术。例如:

  • Spring AOP:通过动态代理实现面向切面编程,支持声明式事务管理、安全控制等功能。
  • Hibernate:使用动态代理实现延迟加载、属性访问控制等功能。
  • RPC框架:通过动态代理实现远程方法调用,隐藏网络通信细节。

总之,动态代理是代理模式的重要演进。它通过将代理类的生成从编译时转移到运行时,解决了静态代理的维护困难和扩展性差的问题,使代理模式成为解决横切关注点的标准方案。在现代软件开发中,动态代理已成为实现松耦合、高内聚系统架构的关键技术之一。