Gateway(网关)模式在 Java 设计模式中的实现:以统一接口集成外部服务(java-design-patterns 仓库详解)
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—— 服务接口,统一契约;ExternalServiceA/B/C—— 外部服务的具体实现;GatewayFactory—— 网关注册表/工厂,按 key 管理网关实例;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 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 模式
综合 西语文档的 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
进一步阅读
- Gateway 模式英文 README(更详尽的意图、时序图、权衡与相关模式)
- Gateway 模式西班牙语本地化文档(本指南的主体骨架)
- API Gateway 微服务示例
- Backends for Frontends 模式示例
- 相关模式对照:Facade、Adapter、Proxy
说明:原文档"Tutorials / Créditos"一节列出的 Martin Fowler《Gateway》文章、microservices.io 的 API Gateway 模式页等外部资料,因其为仓库外链接,此处不再输出 URL;其内容要点(统一接口、策略执行、请求转发)已在上文结合仓库源码完整展开。

