动态代理
1. 这是什么
动态代理是指在运行时生成代理对象,并在方法调用前后织入额外逻辑。
Java 里常见的实现方式有 JDK 动态代理和基于字节码增强的方式。
一句话理解:
- 代理对象不负责替代业务逻辑
- 它负责在“调用目标对象之前、之后、异常时”统一做增强
2. 为什么重要
很多框架功能都基于代理实现,比如:
- 事务
- 日志
- 权限校验
- 方法耗时统计
- 缓存
- 监控埋点
理解动态代理,有助于理解 Spring AOP 和很多“无侵入增强”能力为什么能生效。
3. 先建立直觉:代理模式到底在解决什么问题
假设你有一个业务类:
orderService.createOrder();现在你想在每次调用前后都加上:
- 日志
- 权限检查
- 事务控制
- 异常记录
最笨的方式是把这些逻辑写进每个业务方法里。
问题是:
- 业务代码会被污染
- 相同逻辑会大量重复
- 后期统一修改很麻烦
代理模式的思路是:
- 不直接调用目标对象
- 而是先调用代理对象
- 由代理对象决定什么时候转发给目标对象,以及在前后做什么增强
4. 核心内容
4.1 静态代理和动态代理的区别
静态代理
静态代理通常是你手写一个代理类:
class OrderServiceProxy implements OrderService {
private final OrderService target;
public void createOrder() {
System.out.println("before");
target.createOrder();
System.out.println("after");
}
}优点:
- 直观
- 类型清晰
缺点:
- 每个接口都可能要写一堆代理代码
- 扩展性差
动态代理
动态代理不手写具体代理类,而是在运行时生成代理对象。
这样就能把公共增强逻辑集中在一个地方处理。
4.2 JDK 动态代理的前提条件
JDK 动态代理最重要的前提是:
- 目标对象必须实现接口
因为 JDK 动态代理生成的是“实现了同一组接口的代理类”,而不是直接继承你的目标类。
这也是为什么:
- 如果只有接口,JDK 动态代理很方便
- 如果没有接口,通常会考虑基于子类增强的方案,例如 CGLIB 一类机制
4.3 JDK 动态代理的核心角色
JDK 动态代理里你至少要认识这两个核心角色:
ProxyInvocationHandler
其中:
Proxy.newProxyInstance(...):负责生成代理对象InvocationHandler:负责统一拦截代理方法调用
4.4 InvocationHandler 到底做了什么
它的核心方法是:
Object invoke(Object proxy, Method method, Object[] args)每次你调用代理对象的方法时,最终都会先进入这里。
在这里你可以做:
- 前置日志
- 权限校验
- 参数检查
- 事务开启
- 调用真实目标对象
- 结果增强
- 异常处理
- 收尾动作
这就是“统一拦截”的本质。
4.5 调用链路怎么理解
JDK 动态代理的调用链可以先这样理解:
调用方
-> 代理对象
-> InvocationHandler.invoke(...)
-> 目标对象真实方法
-> 返回结果 / 抛出异常
-> InvocationHandler 做后置处理
-> 返回给调用方这条链非常重要,因为它解释了为什么很多框架可以在业务方法外层织入横切逻辑。
4.6 动态代理和 AOP 的关系
可以先用一句不容易混淆的话来理解:
- AOP 是思想
- 动态代理是实现 AOP 的常见技术手段之一
也就是说:
- AOP 关注的是“把横切关注点从业务里抽出来”
- 动态代理关注的是“怎么在运行时统一拦截方法调用”
4.7 代理对象和目标对象不是同一个对象
虽然它们对外暴露的接口很像,但它们不是同一个实例。
这意味着:
- 代理对象负责拦截和增强
- 目标对象负责真正业务执行
你在调试时经常会看到:
- 变量类型是接口
- 实际运行对象却是一个代理类
这在框架里非常常见。
5. 学习重点
这一章真正要掌握的,不是只会背 Proxy.newProxyInstance,而是理解:
- 代理的本质是“控制访问 + 增强调用”
- JDK 动态代理依赖接口
InvocationHandler是统一拦截入口- 方法前置、后置、异常、收尾逻辑都可以在代理中集中处理
- AOP 的很多能力背后都能还原为“代理 + 拦截”
6. 常见问题
6.1 只会用框架,不理解代理为何生效
一旦你知道调用其实先进入代理对象和 InvocationHandler,很多框架行为就不再神秘。
6.2 分不清静态代理和动态代理
静态代理是手写代理类,动态代理是运行时生成代理对象。
两者思想一致,但动态代理更通用。
6.3 不了解接口代理的使用限制
如果目标对象没有接口,JDK 动态代理就无法直接使用。
6.4 误以为代理会替代业务类
代理不是把业务逻辑变没,而是在调用边界上做增强。
真正执行业务的,仍然是目标对象。
7. 动手验证
这一节可以直接复制运行,边看边验证。
7.1 准备一个可运行示例
新建文件 DynamicProxyDemo.java,内容如下:
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class DynamicProxyDemo {
interface OrderService {
String createOrder(String user, String item);
void cancelOrder(String orderId);
}
static class OrderServiceImpl implements OrderService {
@Override
public String createOrder(String user, String item) {
return "ORDER-" + user + "-" + item;
}
@Override
public void cancelOrder(String orderId) {
throw new IllegalStateException("order not cancellable: " + orderId);
}
}
static class LoggingInvocationHandler implements InvocationHandler {
private final Object target;
LoggingInvocationHandler(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("beforeMethod=" + method.getName());
try {
Object result = method.invoke(target, args);
System.out.println("afterMethod=" + method.getName());
return result;
} catch (InvocationTargetException e) {
Throwable targetException = e.getTargetException();
System.out.println("methodException=" + targetException.getMessage());
throw targetException;
} finally {
System.out.println("finallyMethod=" + method.getName());
}
}
}
public static void main(String[] args) {
OrderService target = new OrderServiceImpl();
OrderService proxy = (OrderService) Proxy.newProxyInstance(
OrderService.class.getClassLoader(),
new Class<?>[] {OrderService.class},
new LoggingInvocationHandler(target));
System.out.println("isProxyClass=" + Proxy.isProxyClass(proxy.getClass()));
System.out.println("targetClass=" + target.getClass().getSimpleName());
System.out.println("proxyImplementsOrderService=" + (proxy instanceof OrderService));
String orderId = proxy.createOrder("Alice", "Book");
System.out.println("createOrderResult=" + orderId);
try {
proxy.cancelOrder("ORDER-404");
} catch (IllegalStateException e) {
System.out.println("clientCaught=" + e.getMessage());
}
}
}7.2 编译并运行
javac DynamicProxyDemo.java
java DynamicProxyDemo如果你在 PowerShell 中操作,也直接执行同样两条命令即可。
7.3 你应该观察到什么
输出不一定逐字完全一致,但应包含这些关键信息:
isProxyClass=true
targetClass=OrderServiceImpl
proxyImplementsOrderService=true
beforeMethod=createOrder
afterMethod=createOrder
finallyMethod=createOrder
createOrderResult=ORDER-Alice-Book
beforeMethod=cancelOrder
methodException=order not cancellable: ORDER-404
finallyMethod=cancelOrder
clientCaught=order not cancellable: ORDER-4047.4 每一行在验证什么
isProxyClass=true:说明运行时生成出来的确实是代理类proxyImplementsOrderService=true:说明 JDK 动态代理是基于接口工作的beforeMethod=...、afterMethod=...:说明代理可以在目标方法前后统一增强finallyMethod=...:说明收尾逻辑也能集中放在代理层methodException=...:说明代理层可以统一拦截和处理目标方法抛出的异常clientCaught=...:说明代理增强后,异常仍然会按语义继续抛给调用方
7.5 再做两个延伸验证
你可以继续做下面两个实验:
- 去掉
OrderService接口,只保留实现类 - 在
invoke中注释掉method.invoke(target, args)前后的日志
你可以观察:
- 没有接口时,JDK 动态代理无法直接代理目标类
- 代理层逻辑本质上就是围绕真实调用做统一增强
8. 练习建议
下面这些练习做完,这一章会更扎实:
- 写一个 JDK 动态代理 demo,为服务方法增加日志
- 给一个方法统一增加耗时统计
- 在代理中统一做参数打印和异常记录
- 对比动态代理和手写静态代理在代码量和扩展性上的差异
- 总结动态代理与装饰器模式、AOP 的联系和区别
9. 自测问题
- JDK 动态代理的核心流程是什么?
InvocationHandler在代理链路中承担什么职责?- 为什么很多框架功能都依赖代理?
- 接口代理和类代理各有什么特点?
- 为什么说代理是在方法调用边界上做增强,而不是替代业务对象?
10. 自测核对要点
如果你的回答能覆盖下面这些点,说明这一章基本掌握到位了:
- 动态代理本质上是运行时生成代理对象并统一拦截方法调用
- JDK 动态代理依赖接口
InvocationHandler.invoke是统一增强入口- 前置、后置、异常、收尾逻辑都可以在代理层集中处理
- AOP 是思想,动态代理是实现横切增强的常见技术手段之一