Skip to content

动态代理

1. 这是什么

动态代理是指在运行时生成代理对象,并在方法调用前后织入额外逻辑。
Java 里常见的实现方式有 JDK 动态代理和基于字节码增强的方式。

一句话理解:

  • 代理对象不负责替代业务逻辑
  • 它负责在“调用目标对象之前、之后、异常时”统一做增强

2. 为什么重要

很多框架功能都基于代理实现,比如:

  • 事务
  • 日志
  • 权限校验
  • 方法耗时统计
  • 缓存
  • 监控埋点

理解动态代理,有助于理解 Spring AOP 和很多“无侵入增强”能力为什么能生效。

3. 先建立直觉:代理模式到底在解决什么问题

假设你有一个业务类:

java
orderService.createOrder();

现在你想在每次调用前后都加上:

  • 日志
  • 权限检查
  • 事务控制
  • 异常记录

最笨的方式是把这些逻辑写进每个业务方法里。
问题是:

  • 业务代码会被污染
  • 相同逻辑会大量重复
  • 后期统一修改很麻烦

代理模式的思路是:

  • 不直接调用目标对象
  • 而是先调用代理对象
  • 由代理对象决定什么时候转发给目标对象,以及在前后做什么增强

4. 核心内容

4.1 静态代理和动态代理的区别

静态代理

静态代理通常是你手写一个代理类:

java
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 动态代理里你至少要认识这两个核心角色:

  • Proxy
  • InvocationHandler

其中:

  • Proxy.newProxyInstance(...):负责生成代理对象
  • InvocationHandler:负责统一拦截代理方法调用

4.4 InvocationHandler 到底做了什么

它的核心方法是:

java
Object invoke(Object proxy, Method method, Object[] args)

每次你调用代理对象的方法时,最终都会先进入这里。
在这里你可以做:

  • 前置日志
  • 权限校验
  • 参数检查
  • 事务开启
  • 调用真实目标对象
  • 结果增强
  • 异常处理
  • 收尾动作

这就是“统一拦截”的本质。

4.5 调用链路怎么理解

JDK 动态代理的调用链可以先这样理解:

text
调用方
 -> 代理对象
 -> 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,内容如下:

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 编译并运行

bash
javac DynamicProxyDemo.java
java DynamicProxyDemo

如果你在 PowerShell 中操作,也直接执行同样两条命令即可。

7.3 你应该观察到什么

输出不一定逐字完全一致,但应包含这些关键信息:

text
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-404

7.4 每一行在验证什么

  • isProxyClass=true:说明运行时生成出来的确实是代理类
  • proxyImplementsOrderService=true:说明 JDK 动态代理是基于接口工作的
  • beforeMethod=...afterMethod=...:说明代理可以在目标方法前后统一增强
  • finallyMethod=...:说明收尾逻辑也能集中放在代理层
  • methodException=...:说明代理层可以统一拦截和处理目标方法抛出的异常
  • clientCaught=...:说明代理增强后,异常仍然会按语义继续抛给调用方

7.5 再做两个延伸验证

你可以继续做下面两个实验:

  1. 去掉 OrderService 接口,只保留实现类
  2. invoke 中注释掉 method.invoke(target, args) 前后的日志

你可以观察:

  • 没有接口时,JDK 动态代理无法直接代理目标类
  • 代理层逻辑本质上就是围绕真实调用做统一增强

8. 练习建议

下面这些练习做完,这一章会更扎实:

  • 写一个 JDK 动态代理 demo,为服务方法增加日志
  • 给一个方法统一增加耗时统计
  • 在代理中统一做参数打印和异常记录
  • 对比动态代理和手写静态代理在代码量和扩展性上的差异
  • 总结动态代理与装饰器模式、AOP 的联系和区别

9. 自测问题

  • JDK 动态代理的核心流程是什么?
  • InvocationHandler 在代理链路中承担什么职责?
  • 为什么很多框架功能都依赖代理?
  • 接口代理和类代理各有什么特点?
  • 为什么说代理是在方法调用边界上做增强,而不是替代业务对象?

10. 自测核对要点

如果你的回答能覆盖下面这些点,说明这一章基本掌握到位了:

  • 动态代理本质上是运行时生成代理对象并统一拦截方法调用
  • JDK 动态代理依赖接口
  • InvocationHandler.invoke 是统一增强入口
  • 前置、后置、异常、收尾逻辑都可以在代理层集中处理
  • AOP 是思想,动态代理是实现横切增强的常见技术手段之一