本文主要串联动态代理中的知识点,并梳理一下代理模式的发展史,进而映出其如何迭代和实现现在开发中常用的代理模式,如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)原则,还增加了维护成本。如果日志格式需要变更,就需要修改所有包含日志代码的类,容易出错且效率低下。
javapublic 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");
}
}
如果通过组合方式引入功能类,会导致耦合入侵。例如,为实现事务管理功能,目标类需要持有事务管理器的引用,并在方法执行前后调用事务管理器的开始和提交方法。这种做法虽然避免了代码重复,但目标类却被迫知晓事务管理的实现细节,破坏了单一职责原则,增加了类之间的依赖关系。
javapublic 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. 接口定义:定义客户端与目标对象交互的契约。
javapublic interface Service {
void execute();
}
2. 被代理类实现:实现接口,提供核心业务逻辑。
javapublic class RealService implements Service {
@Override
public void execute() {
System.out.println("执行核心业务逻辑");
}
}
3. 代理类实现:实现同一接口,内部持有目标对象引用,并在方法调用前后添加额外逻辑。
javapublic 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动态代理基于接口实现,利用反射机制在运行时动态生成代理类。其核心组件包括:
1. 接口定义:与静态代理相同,定义客户端与目标对象交互的契约。
javapublic interface Service {
void execute();
}
2. 被代理类实现:实现接口,提供核心业务逻辑。
javapublic class RealService implements Service {
@Override
public void execute() {
System.out.println("执行核心业务逻辑");
}
}
3. 代理处理器:实现InvocationHandler接口,定义代理逻辑。
javapublic 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动态代理的核心原理:
Proxy.newProxyInstance()方法动态生成代理类。InvocationHandler接口的invoke()方法实现对目标方法的拦截和增强。JDK动态代理的优势:
InvocationHandler实现类,即可代理所有实现特定接口的类。JDK动态代理的局限性:
final方法。CGLIB(Code Generation Library)是一种基于继承的动态代理技术,它直接通过字节码操作生成目标类的子类实现代理功能。其核心原理如下:
1. 目标类定义:普通Java类,无需实现特定接口。
javapublic class RealService {
public void execute() {
System.out.println("执行核心业务逻辑");
}
}
2. 方法拦截器:实现MethodInterceptor接口,定义代理逻辑。
javapublic 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类动态生成代理对象。
javapublic static RealService getProxy() {
Enhancer enhancer = new Enhancer();
enhancer.set Superclass(RealService.class);
enhancer.setCallback(new ServiceInterceptor());
return (RealService) enhancer.create();
}
CGLIB动态代理的核心原理:
final方法。CGLIB动态代理的优势:
CGLIB动态代理的局限性:
final关键字限制了类或方法的继承和重写,因此CGLIB无法代理这些内容。| 对比维度 | 静态代理 | JDK动态代理 | CGLIB动态代理 |
|---|---|---|---|
| 生成方式 | 手动编写代理类 | 运行时基于接口生成代理类 | 运行时基于类生成子类 |
| 依赖要求 | 依赖接口或继承 | 必须依赖接口 | 可代理普通类,但不能代理final类 |
| 维护成本 | 高(接口变更需同步修改所有代理类) | 低(只需修改InvocationHandler) | 低(只需修改拦截器) |
| 扩展性 | 差(需手动修改代理类) | 好(支持多接口,可动态代理) | 更好(无接口限制,可动态代理) |
| 性能开销 | 无 | 高(反射调用) | 中(字节码生成) |
| 代码量 | 大(每个接口需一个代理类) | 小(一个处理器代理多个接口) | 最小(一个拦截器代理多个类) |
光说不练假把式,这里我们模拟一个真实的业务场景:系统性能监控。
假设我们有一个核心的订单服务 OrderService,为了排查线上性能问题,领导要求我们统计每个方法的执行耗时,如果超过 100ms 则打印“慢接口告警”日志。
如果不使用代理模式,我们不得不修改每个业务方法,把计时逻辑硬塞进去。代码会变成这样:
javapublic 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) 这个处理器是通用的,它可以监控任何服务,而不仅仅是订单服务。
javapublic 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 瞬间恢复了它该有的样子,只关注业务,没有任何杂质:
javapublic 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 初始化阶段),我们将代理对象动态生成出来:
javapublic 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");
}
}
通过这个例子,我们可以清晰地看到代理模式是如何实现**“业务与基础设施分离”**的。
createOrder,不需要关心监控。PerformanceMonitorHandler,修改告警阈值只需改一处地方。到最后我们再重新从一个整体的视角去对代理模式意义进行一个审视总结:
1. 解耦与复用:通过代理对象,可以将核心业务逻辑与横切关注点(如日志、事务、安全等)分离,避免代码冗余和入侵式修改。例如,一个系统中有多个服务类需要添加日志功能,通过代理模式,只需在代理对象中实现日志逻辑,而无需在每个服务类中重复编写。
2. 灵活性:动态代理支持运行时生成代理对象,可以根据需要动态决定是否代理某个对象,以及代理哪些方法。例如,在Spring框架中,可以通过配置决定哪些Bean需要事务管理,而无需修改这些Bean的代码。
3. 扩展性:动态代理使得系统更容易扩展新功能。例如,当需要为系统添加新的横切关注点(如性能监控)时,只需修改代理逻辑,而无需修改所有目标类。
4. 框架基石:动态代理是许多主流框架的核心技术。例如:
总之,动态代理是代理模式的重要演进。它通过将代理类的生成从编译时转移到运行时,解决了静态代理的维护困难和扩展性差的问题,使代理模式成为解决横切关注点的标准方案。在现代软件开发中,动态代理已成为实现松耦合、高内聚系统架构的关键技术之一。