理解代理模式:Java 中静态代理与动态代理的原理与实现

发布: (2025年12月29日 GMT+8 18:49)
10 min read
原文: Dev.to

Source: Dev.to

Introduction

上一篇文章中,我们讨论了 Spring 的 @Transactional 注解,并看到它是如何借助 Spring AOP 的魔法实现的,这要归功于在幕后默默工作的动态代理

这让我思考:为什么只能止步于此?代理在 Spring 中随处可见,那为什么不更深入地探究它们的内部工作原理呢?

所以今天,我很高兴开启一系列专注于解锁动态代理威力的文章!把它们想象成你编写更简洁代码的秘密武器。你可以把所有重复的样板代码一次性封装起来,然后通过简单的注解把功能“撒”到任何需要的地方。这就像写了一个超级能力,然后把它分发给整个代码库。

我们将从《设计模式》一书中经典的 代理模式(Gang of Four)开始,连接这个模式与像 Spring 这样框架每天使用的动态代理之间的联系。为了确保大家都在同一层次上,我们甚至会先一起实现一个简单的 静态代理

而且,最好的学习方式是动手实践,我们将在结尾完成一个顶点项目:自己实现一个注解 @MyTransactional,模拟 Spring 的 @Transactional 功能。

所以,无论你是完全新手还是想熟练掌握 ByteBuddy 等高级工具的开发者,快拉个椅子坐下吧!希望本系列能为你提供友好且实用的指南,让你在结束时对动态代理有更深入的理解。

Source:

让我们从代理模式开始

当你想在调用方法的客户端和实际执行该方法的对象(真实主题)之间添加一层控制时,就会使用代理。代理模式的核心是提供一个代表另一个对象的对象。

如果这听起来有点抽象,别担心——我们用一个例子来拆解它。

  1. 客户端 想使用一个服务,我们称之为 主题
  2. 主题 是一个接口。真正的工作由实现该接口的类 真实主题 完成。
  3. 代理 介于客户端和真实主题之间。当客户端在主题上调用方法时,代理会在请求到达真实主题之前拦截该调用。这让代理有机会在传递请求之前获取结果之后进行额外的工作。

代理可以做什么?

用例示例
访问控制(充当门卫)“等一下,你有权限进行此调用吗?”
延迟初始化(懒惰加载)“我不会创建这个重量级对象(例如巨大的文件或数据库连接),除非真的必须要用到它。”
远程调用(充当信使)“真实对象在另一台机器上?我来处理网络通信。”
横切关注点(处理烦人的事)“我会自动完成日志记录、缓存或事务开启,这样主对象就保持干净。”

所有这些的美妙之处在于:你的 真实主题 可以专注于业务逻辑,而代理负责那些重复但重要的任务。它就像一个专职助理,负责所有的准备工作和收尾工作!

构建静态代理

好的,我们已经了解了代理是什么。现在让我们卷起袖子,用 Java 一起构建一个吧!

1️⃣ 定义 Subject(合同)

interface Subject {
    void execute();
}

2️⃣ 实现 RealSubject(真实工作)

public class RealSubject implements Subject {

    @Override
    public void execute() {
        System.out.println("Performing an expensive operation.");
        // ... an operation
    }
}

3️⃣ 创建 Proxy(中间人)

public class SubjectProxy implements Subject {

    private final RealSubject realSubject = new RealSubject();

    @Override
    public void execute() {
        System.out.println("Proxy intercepting RealSubject's operation.");
        // logging the method call
        // ... any extra work
        realSubject.execute();   // delegate to the real subject
    }
}

4️⃣ 在 Client 代码中使用代理

public class Client {

    private final Subject subject = new SubjectProxy();

    public void call() {
        subject.execute();
    }
}

如您所见,代理可以在调用真实方法的 无缝添加自己的功能。只需几行代码,您就创建了一个可以在不触及真实对象的情况下管理、保护或监控访问的助手!

Source:

手动实现的问题

我们的 SubjectProxy 在一个简单示例中运行良好,但想象一下,你有数十个服务,每个服务又有数十个方法。为每个接口编写静态代理会迅速变成:

  • 繁琐 – 大量样板代码。
  • 易出错 – 容易忘记委托某个方法或保持代理与接口同步。
  • 难维护 – 接口的任何更改都迫使你更新所有代理。

这就是像 Spring 这样的框架在运行时生成 动态代理 的原因。它们让你只需编写一次横切关注点(例如日志、事务管理),并自动将其应用到匹配切点的任何 bean,而无需为每个接口手动编写代理类。

N+1 类问题

想象一下,你正在开发一个拥有数百个服务的大型企业应用。如果你想使用 静态 方法为每一个服务添加日志或事务管理,就必须为每个服务接口编写一个单独的代理类。

这会产生大量样板代码!既繁琐、易出错,又——说实话——不够“工程化”。这就是我们所说的 N+1 类问题:每写一个业务类,就被迫写一个对应的代理类。

必须有更好的办法,对吧?

Source:

进入动态代理:自动化的中间人

如果说静态代理就像为每一个你遇到的人手写一份定制合同,动态代理 则像拥有一个智能模板,在需要时自动生成。

核心思路很简单:我们不再手动编写 SubjectProxy.java,而是在运行时告诉 Java 虚拟机(JVM):

“嘿,我需要一个看起来像这个接口的对象,但每当有人调用它的方法时,就把调用转发给我写好的这个 Handler 类。”

快速示例

// 我们唯一的 “Handler”,处理所有接口的所有方法调用
InvocationHandler handler = (proxy, method, args) -> {
    System.out.println("Dynamic Proxy intercepting: " + method.getName());
    return method.invoke(realSubject, args);
};

// 魔法时刻:即时创建代理实例
Subject dynamicProxy = (Subject) Proxy.newProxyInstance(
    Subject.class.getClassLoader(),
    new Class[] { Subject.class },
    handler
);

dynamicProxy.execute(); // 这个调用会被我们的 handler 拦截!

注意: 如果这段代码看起来陌生也不要紧。我们将在本系列后续深入探讨 JDK 动态代理。关键点在于我们没有编写名为 SubjectProxy 的类,而是在程序运行时生成它。如果我们有 100 个不同的接口,同样的逻辑就能处理所有接口——不再出现 N+1 问题

摘要与下一步

我们已经从经典的设计模式转向了使用静态代理的“手工劳动”现实,并且已经瞥见了 Java 如何动态生成这些中间人,以免我们在样板代码中溺水。

但这只是懒惰开发者的巧妙技巧吗?并非如此。

在下一篇文章中,我们将走出“Hello World”示例,看看 真实世界的魔法。我们将深入探讨 Java 生态系统中的巨头——SpringHibernateMockito——如何使用动态代理来驱动我们每天使用的功能。具体来说,我们将检查:

  • @Transactional 注解是如何在底层通过代理实现的。
  • Hibernate 如何管理数据的懒加载。
  • Mockito 如何 模拟 方法调用并返回模拟数据。

敬请期待 第 2 部分——它将变得非常有趣!

Back to Blog

相关文章

阅读更多 »

Java简介

人类编写的 Java 代码是如何被机器执行的?它使用一个翻译系统 JDK → JRE → JVM → JIT 将编程语言转换为机器……