本文主要串联动态代理中的知识点,并梳理一下代理模式的发展史,进而映出其如何迭代和实现现在开发中常用的代理模式,如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)原则,还增加了维护成本。如果日志格式需要变更,就需要修改所有包含日志代码的类,容易出错且效率低下。
如果通过组合方式引入功能类,会导致耦合入侵。例如,为实现事务管理功能,目标类需要持有事务管理器的引用,并在方法执行前后调用事务管理器的开始和提交方法。这种做法虽然避免了代码重复,但目标类却被迫知晓事务管理的实现细节,破坏了单一职责原则,增加了类之间的依赖关系。
代理模式正是为了解决这一矛盾而诞生的设计模式。它提供了一种既不造成代码冗余,又不引入耦合的方式,实现横切关注点的集中管理。通过代理对象,可以在不修改目标类代码的情况下,为其添加额外功能,使目标类专注于核心业务逻辑。
在开始讨论代理模式的具体实现之前,我们先通过一幅图片来简单抽象地描述代理模式中各个部分:

静态代理是最基础的代理模式实现方式,它通过预先编写代理类来实现代理功能。静态代理的实现需要三个关键组件:
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)
);
}
}
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) | 低(只需修改拦截器) |
| 扩展性 | 差(需手动修改代理类) | 好(支持多接口,可动态代理) | 更好(无接口限制,可动态代理) |
| 性能开销 | 无 | 高(反射调用) | 中(字节码生成) |
| 代码量 | 大(每个接口需一个代理类) | 小(一个处理器代理多个接口) | 最小(一个拦截器代理多个类) |
到最后我们再重新从一个整体的视角去对代理模式意义进行一个审视总结:
1. 解耦与复用:通过代理对象,可以将核心业务逻辑与横切关注点(如日志、事务、安全等)分离,避免代码冗余和入侵式修改。例如,一个系统中有多个服务类需要添加日志功能,通过代理模式,只需在代理对象中实现日志逻辑,而无需在每个服务类中重复编写。
2. 灵活性:动态代理支持运行时生成代理对象,可以根据需要动态决定是否代理某个对象,以及代理哪些方法。例如,在Spring框架中,可以通过配置决定哪些Bean需要事务管理,而无需修改这些Bean的代码。
3. 扩展性:动态代理使得系统更容易扩展新功能。例如,当需要为系统添加新的横切关注点(如性能监控)时,只需修改代理逻辑,而无需修改所有目标类。
4. 框架基石:动态代理是许多主流框架的核心技术。例如:
总之,动态代理是代理模式的重要演进。它通过将代理类的生成从编译时转移到运行时,解决了静态代理的维护困难和扩展性差的问题,使代理模式成为解决横切关注点的标准方案。在现代软件开发中,动态代理已成为实现松耦合、高内聚系统架构的关键技术之一。