Gateway(网关)模式在 Java 设计模式中的实现:以统一接口集成外部服务(java-design-patterns 仓库详解)

原创2026-10-01 20:07:431,172 阅读
文章标签:示例工程教程

Gateway(网关)模式在 Java 设计模式中的实现:以统一接口集成外部服务(java-design-patterns 仓库详解)

本技术指南以 java-design-patterns 仓库中 gateway 模块(及其 西班牙语本地化版本)为骨架,系统讲解 Gateway 设计模式的核心思想、代码实现与适用场景。你将看到:如何用一个极简的 Gateway 接口把多个外部服务统一封装,如何通过 GatewayFactory 注册与按需获取网关实例,以及该模式与 Facade、Adapter、Proxy、API Gateway 等模式的异同。读完即可在本仓库的源码与测试佐证下,独立复现并评估这一模式。

模式意图:为外部系统提供统一、简化的访问接口

Gateway 是一种结构型(Structural)设计模式,其核心意图是:为应用内部提供一套统一、简单的视图,去访问一组外部系统或外部功能(参见 gateway 模块英文 README 的 Intent 一节)。它把内部系统与外部资源之间的交互封装起来,屏蔽协议翻译、数据格式转换、路由分发等细节,从而保证内外组件之间的松散耦合(Decoupling),提升应用的可维护性与可扩展性。

正如 gateway 模块的西班牙语文档 所概括的:

  • 目的:为访问一组外部系统或功能提供入口接口;Gateway 为应用的内部资源提供对外部资源的简单、统一视图。
  • 一句话总结:网关可以提供一个接口,让内部系统能够使用外部服务。

从源码角度看,App.java 的类注释 明确描述了该模式在本仓库中的落地形态:创建一个 Gateway 接口作为与多个服务交互的公共入口,并为不同外部服务提供该接口的具体实现;GatewayFactory 是工厂类,ExternalServiceA/B/C 是外部服务的模拟实现,App 是应用入口,通过 Gateway 接口以远低于直接调用的复杂度去调用各个服务。

现实类比:城门、物流公司与 API 前端

城门口的比喻

Gateway 就像一座真实城市的大门。城内的居民是"内部系统",城外不同的城市是"外部服务"。城门的作用,就是为内部系统提供通往不同外部服务的访问通道。—— 西语文档 Real World Example

内部系统只需要认识"城门"这一个出入口,而不需要分别了解每座外部城市的道路、语言与规则。

物流公司的例子

英文版 README 给出了更贴近企业级应用的场景(见 gateway/README.md 的 Real-World Example):

一家物流公司同时使用多家第三方服务:运输、库存管理、客户通知。每个服务都有自己的 API、协议和数据格式。若直接让内部系统逐一对接,耦合度极高。引入 Gateway 后,所有第三方交互都汇聚到一个统一接口上:网关负责协议翻译、数据转换与请求路由,内部系统与每个外部服务的具体实现细节彻底解耦,同时对外通信有了单一控制点,可维护性与可扩展性都随之改善。

维基百科的定义

网关是一台充当 API 前端的服务器:接收 API 请求,执行限流(throttling)与安全策略,将请求转发给后端服务,再把响应返回给请求方。—— 西语文档 Wikipedia 引述

这个定义点出了网关在现实基础设施中的关键职责:统一入口 + 策略执行(限流、安全)+ 请求转发/响应回传。

架构概览:类图

下面是该模式在本仓库中的类图(来源于 gateway/etc/gateway.urm.puml,渲染图为 gateway.urm.png,原文档亦引用此图):

Gateway 模式类图:Gateway 接口由 ExternalServiceA/B/C 实现,GatewayFactory 维护注册表,App 通过工厂获取并执行服务

从类图可以看出整个模式只有四类角色:

  1. Gateway —— 服务接口,统一契约;
  2. ExternalServiceA/B/C —— 外部服务的具体实现;
  3. GatewayFactory —— 网关注册表/工厂,按 key 管理网关实例;
  4. App —— 客户端入口,只依赖 Gateway 接口编程。

编程示例:从接口到应用入口的完整链路

本仓库 gateway/src/main/java/com/iluwatar/gateway 目录下共 6 个源文件,完整对应上述四类角色。下面按依赖层次逐层展开。

第一步:定义统一契约 Gateway 接口

Gateway.java 是全部外部服务必须遵循的契约,其"极其简单"正是该模式的精髓——把复杂性全部收敛到实现内部:

/** Service interface. */
interface Gateway {
  void execute() throws Exception;
}

execute() 是唯一的方法,所有需要接入的外部能力最终都落到这一入口上。声明抛出 Exception,为服务实现保留了向调用方上报错误的空间(比如网络异常、服务不可用)。

第二步:实现外部服务

三个外部服务实现类都位于同一包内,均实现 Gateway 接口,用 Thread.sleep(1000) 模拟耗时的外部调用,用 SLF4J 记录日志(@Slf4j 由 Lombok 生成 LOGGER):

ExternalServiceA.java:

/** ExternalServiceA is one of external services. */
@Slf4j
class ExternalServiceA implements Gateway {
  @Override
  public void execute() throws Exception {
    LOGGER.info("Executing Service A");
    // Simulate a time-consuming task
    Thread.sleep(1000);
  }
}

ExternalServiceB.java 与 A 完全同构,日志改为 "Executing Service B";而 ExternalServiceC.java 额外带了一个 error() 方法,用于模拟异常场景:

/** ExternalServiceC is one of external services. */
@Slf4j
class ExternalServiceC implements Gateway {
  @Override
  public void execute() throws Exception {
    LOGGER.info("Executing Service C");
    // Simulate a time-consuming task
    Thread.sleep(1000);
  }

  public void error() {
    // Simulate an exception
    throw new RuntimeException("Service C encountered an error");
  }
}

值得注意的是:execute() 是接口契约内的方法,而 error() 是 ExternalServiceC 特有的扩展行为,并不属于 Gateway 契约——这恰好演示了"接口只暴露最小公共能力、具体服务的专属能力留在实现内部"的设计取向。

第三步:用 GatewayFactory 管理网关注册表

GatewayFactory.java 是整个模式的"注册中心":内部持有一个 Map<String, Gateway>,提供注册与按 key 获取两个方法:

public class GatewayFactory {
  private Map<String, Gateway> gateways = new HashMap<>();

  public void registerGateway(String key, Gateway gateway) {
    gateways.put(key, gateway);
  }

  public Gateway getGateway(String key) {
    return gateways.get(key);
  }
}
  • registerGateway(String key, Gateway gateway):把实现实例与业务 key(如 "ServiceA")绑定,写入 HashMap;
  • getGateway(String key):按 key 取出网关实例;若 key 不存在,HashMap.get 返回 null(测试中对此有明确断言,见下文)。

这里的注册表模式让"新增一个外部服务"的成本降到最低:注册新 key 即可,客户端代码无需改动。

第四步:App 入口——客户端如何消费

App.java 是演示入口(同时也是 pom.xml 中 maven-assembly-plugin 配置的可执行主类):

@Slf4j
public class App {
  /** Simulate an application calling external services. */
  public static void main(String[] args) throws Exception {
    GatewayFactory gatewayFactory = new GatewayFactory();

    // Register different gateways
    gatewayFactory.registerGateway("ServiceA", new ExternalServiceA());
    gatewayFactory.registerGateway("ServiceB", new ExternalServiceB());
    gatewayFactory.registerGateway("ServiceC", new ExternalServiceC());

    // Use an executor service for execution
    Gateway serviceA = gatewayFactory.getGateway("ServiceA");
    Gateway serviceB = gatewayFactory.getGateway("ServiceB");
    Gateway serviceC = gatewayFactory.getGateway("ServiceC");

    // Execute external services
    try {
      serviceA.execute();
      serviceB.execute();
      serviceC.execute();
    } catch (ThreadDeath e) {
      LOGGER.info("Interrupted!" + e);
      throw e;
    }
  }
}

调用链清晰可循:注册 → 按 key 获取 Gateway 引用 → 通过统一接口 execute() 执行。客户端自始至终只与 Gateway 接口和 GatewayFactory 打交道,对三个服务的具体实现零感知——这正是"降低耦合、易于维护和扩展"的直观体现。

运行与输出

按照上述代码运行后,控制台输出如下(西语文档 与英文 README 均给出):

Executing Service A
Executing Service B
Executing Service C

英文 README 中给出了更完整的日志形态(gateway/README.md 的 Running the example),可以看到三个服务串行执行、各间隔约 1 秒(对应各自 Thread.sleep(1000)):

09:24:44.030 [main] INFO com.iluwatar.gateway.ExternalServiceA -- Executing Service A
09:24:45.038 [main] INFO com.iluwatar.gateway.ExternalServiceB -- Executing Service B
09:24:46.043 [main] INFO com.iluwatar.gateway.ExternalServiceC -- Executing Service C

时序图:一次调用如何穿过网关

英文 README 提供了本模块的时序图(渲染图 gateway-sequence-diagram.png,源码 gateway-sequence-diagram.puml):

Gateway 模式时序图:App 通过 GatewayFactory 获取网关,再通过统一 execute() 方法调用外部服务

时序图直观呈现了"客户端 → 工厂取实例 → 统一接口执行"的消息流转,是理解该模式运行时行为的辅助材料。

何时使用 Gateway 模式

综合 西语文档的 Aplicabilidad 与 英文 README 的 When to Use,以下场景适合引入 Gateway:

  • 需要访问聚合对象/外部能力而不暴露其内部表示时;
  • 与多个外部服务或 API 集成时,需要一个统一的访问门面;
  • 需要为遍历/调用不同聚合结构提供统一接口时;
  • 在微服务架构中,需要通过良好定义的接口管理服务间通信时(这是 Gateway 模式尤其受益的场景)。

真实应用场景与相关模式

现实中的落地形态

英文 README 的 Real-World Applications(gateway/README.md)给出两类典型形态:

  • 微服务中的 API 网关:作为中介处理来自客户端的请求,将其路由到微服务架构中合适的服务。本仓库有专门的 microservices-api-gateway 模块 演示这一形态,可视为 Gateway 模式在"请求路由"维度上的特化;
  • 数据库网关:为访问不同数据库系统提供统一接口,隐藏各类数据库查询与数据检索的细节。

此外,与"后端专用前端(Backends for Frontends)"的思想也高度相关,可参考仓库中的 backends-for-frontends 模块。

与相近模式的辨析

英文 README 的 Related Java Design Patterns(gateway/README.md)给出了精确的边界:

  • Facade 模式:两者都用于抽象复杂子系统,但 Gateway 专门面向外部/远程接口,Facade 更侧重于本地子系统;
  • Adapter 模式:两者都向子系统提供不同接口,但 Gateway 更聚焦于网络化数据源与服务;
  • Proxy 模式:两者常配合使用,都能控制并管理对另一个对象的访问,但 Gateway 特指对外部服务的访问;
  • API Gateway:常被视为 Gateway 模式的特化,专门管理 API 请求并将其路由到后端系统的对应服务。

收益与权衡

收益(Benefits)

  • 将外部 API 与服务的细节抽象到更简单的接口之后,显著降低系统复杂度;
  • 促进应用与其外部依赖之间的松散耦合;
  • 由于依赖被统一收敛,系统更易于测试和维护。

代价(Trade-offs)

  • 引入额外的一层,可能对性能产生潜在影响(多一跳转发);
  • 需要审慎设计,避免形成"单体网关"而成为性能瓶颈或单点故障源。

测试与工程质量:测试用例如何验证模式行为

ServiceFactoryTest.java 用 JUnit 5 覆盖了工厂的核心行为,可从测试角度印证模式的设计契约:

  • testGatewayFactoryRegistrationAndRetrieval:注册后按 key 获取,断言返回实例的类型分别为 ExternalServiceA/B/C——验证"注册-取回"链路的正确性;
  • testGatewayFactoryRegistrationWithNonExistingKey:对不存在的 key("NonExistingService")调用 getGateway,断言返回 null——明确了工厂对未知 key 的语义;
  • testGatewayFactoryConcurrency:用固定线程池(Executors.newFixedThreadPool(2))提交 10 个并发任务,通过 CountDownLatch 同步等待,断言并发执行 execute() 不失败——验证 HashMap 注册表在只读并发访问下的可用性(注册在启动阶段完成、运行期只读)。

此外 AppTest.java 负责对演示入口做冒烟级验证。这些测试共同构成了该模式"可运行、可回归"的证据链。

如何运行本模块示例

本模块是独立 Maven 工程(artifactId 为 gateway,pom.xml),父工程为 com.iluwatar:java-design-patterns,依赖 slf4j-api 与 logback-classic 提供日志能力,测试依赖 junit-jupiter-engine。从仓库根目录可执行:

./mvnw -pl gateway -am package

构建产物为可执行 JAR(maven-assembly-plugin 已将 主类 com.iluwatar.gateway.App 写入 manifest),运行:

java -jar gateway/target/gateway-*.jar

即可看到 Executing Service A/B/C 的串行输出。测试可单独执行:

./mvnw -pl gateway test

进一步阅读

说明:原文档"Tutorials / Créditos"一节列出的 Martin Fowler《Gateway》文章、microservices.io 的 API Gateway 模式页等外部资料,因其为仓库外链接,此处不再输出 URL;其内容要点(统一接口、策略执行、请求转发)已在上文结合仓库源码完整展开。

登录后查看全文
java-design-patterns