设计模式综合实战五:基于Spring Cloud实现微服务网关(代理+责任链+适配器+外观)

微服务网关,说白了就是系统的“总入口”。

我记得刚做微服务那会儿,团队里每个服务都要自己处理鉴权、限流、日志。结果呢?代码到处重复,改个鉴权逻辑要通知七八个团队。后来我意识到——网关不是可有可无的,它是微服务架构的标配

这一章,我们就把代理模式、责任链模式、适配器模式、外观模式揉到一起,用Spring Cloud Gateway来实现一个真正的微服务网关。你想想看,这四个模式各自解决什么问题?

1. 整体架构:四种模式如何协作

先画一张图,让你一眼看明白它们的关系:

微服务网关四种设计模式协作架构 客户端(Client) 外观模式:统一入口 GatewayHandler 封装所有子模块 责任链模式:过滤器链 鉴权Filter → 限流Filter → 日志Filter → 路由Filter 每个Filter只关注自己的职责 代理模式:路由转发 隐藏后端服务细节 适配器模式:协议转换 HTTP ↔ WebSocket 等 后端微服务集群 后端微服务集群

这张图我画了好几次才满意。你看,外观模式在最上层,给客户端一个干净的入口;责任链模式在中间,把各种横切关注点串起来;代理模式负责转发,隐藏后端细节;适配器模式处理协议差异。各司其职,互不干扰。

2. 代理模式:路由转发与负载均衡

代理模式在网关里是最直观的。客户端不需要知道请求最终落到哪个服务实例上,网关帮你搞定。

核心思想:代理对象控制对真实对象的访问。在网关场景下,代理就是路由表。

Spring Cloud Gateway 底层用的是 RouteLocator,我习惯这样配置:

@Bean
public RouteLocator customRouteLocator(RouteLocatorBuilder builder) {
    return builder.routes()
        .route("user-service", r -> r
            .path("/api/users/**")
            .filters(f -> f.stripPrefix(1))
            .uri("lb://user-service"))
        .route("order-service", r -> r
            .path("/api/orders/**")
            .filters(f -> f.stripPrefix(1))
            .uri("lb://order-service"))
        .build();
}

这里有个细节:lb:// 前缀表示使用负载均衡。我曾经见过有人直接写死IP地址,结果服务扩容后全乱套了。记住,代理模式一定要和注册中心配合使用,否则就是伪代理。

3. 责任链模式:过滤器链的设计

网关里最头疼的就是各种过滤器。鉴权、限流、日志、参数校验……如果写在一个类里,那代码就成了一锅粥。

责任链模式正好解决这个问题。每个过滤器只做一件事,然后传给下一个。

我一般这样定义过滤器接口:

public interface GatewayFilter {
    void filter(ServerWebExchange exchange, GatewayFilterChain chain);
}

public class AuthFilter implements GatewayFilter {
    @Override
    public void filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        // 检查token
        String token = exchange.getRequest().getHeaders().getFirst("Authorization");
        if (token == null || !token.startsWith("Bearer ")) {
            exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
            return; // 直接终止链
        }
        chain.filter(exchange); // 传给下一个
    }
}

public class RateLimitFilter implements GatewayFilter {
    @Override
    public void filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        // 基于令牌桶的限流逻辑
        if (!tryAcquire()) {
            exchange.getResponse().setStatusCode(HttpStatus.TOO_MANY_REQUESTS);
            return;
        }
        chain.filter(exchange);
    }
}

个人经验:过滤器链的顺序很重要。我习惯把鉴权放在最前面,因为如果身份都不合法,后面的限流、日志就没必要执行了。这能节省不少资源。

Spring Cloud Gateway 内置了 GatewayFilterChain,我们只需要实现 GlobalFilter 接口,框架会自动把它们串成链。你想想看,这比自己在代码里写 if-else 优雅了多少?

4. 适配器模式:协议转换与数据适配

微服务之间通信协议不统一,这是常态。有的服务暴露 REST API,有的走 gRPC,还有的用 WebSocket。网关作为中间人,必须能适配各种协议。

适配器模式在这里就派上用场了。我定义一个统一的内部协议,然后为每种外部协议写一个适配器:

// 统一内部请求模型
public class InternalRequest {
    private String serviceName;
    private String method;
    private Map<String, Object> params;
    // getter/setter 省略
}

// 适配器接口
public interface ProtocolAdapter {
    InternalRequest adapt(ServerWebExchange exchange);
}

// HTTP适配器
public class HttpAdapter implements ProtocolAdapter {
    @Override
    public InternalRequest adapt(ServerWebExchange exchange) {
        InternalRequest req = new InternalRequest();
        req.setMethod(exchange.getRequest().getMethod().name());
        // 从HTTP请求中提取参数
        return req;
    }
}

// WebSocket适配器
public class WebSocketAdapter implements ProtocolAdapter {
    @Override
    public InternalRequest adapt(ServerWebExchange exchange) {
        // 处理WebSocket升级请求
        InternalRequest req = new InternalRequest();
        req.setMethod("WS");
        return req;
    }
}

避坑指南:我曾经在适配器里直接修改原始请求对象,结果导致后续过滤器拿到的是脏数据。记住,适配器应该返回新对象,而不是修改原对象。这是适配器模式的基本原则。

5. 外观模式:统一入口与简化调用

外观模式是这四种模式里最“上层”的。它给客户端一个简单的接口,背后却隐藏了复杂的子系统。

在 Spring Cloud Gateway 中,GatewayHandler 就是外观类。客户端只需要发一个 HTTP 请求,剩下的路由、过滤、适配、转发全由它搞定。

我习惯这样封装外观类:

@Component
public class GatewayFacade {
    private final RouteLocator routeLocator;       // 代理
    private final List<GlobalFilter> filters;      // 责任链
    private final Map<String, ProtocolAdapter> adapters; // 适配器
    
    public Mono<Void> handle(ServerWebExchange exchange) {
        // 1. 选择适配器
        ProtocolAdapter adapter = selectAdapter(exchange);
        InternalRequest req = adapter.adapt(exchange);
        
        // 2. 执行过滤器链(责任链)
        GatewayFilterChain chain = new DefaultGatewayFilterChain(filters);
        return chain.filter(exchange);
        
        // 3. 路由转发(代理)由框架自动完成
    }
    
    private ProtocolAdapter selectAdapter(ServerWebExchange exchange) {
        String protocol = exchange.getRequest().getHeaders()
            .getFirst("Upgrade");
        if ("websocket".equalsIgnoreCase(protocol)) {
            return adapters.get("websocket");
        }
        return adapters.get("http");
    }
}

你看,客户端根本不需要知道内部有这么多模式在协作。它只看到 GatewayFacade.handle() 这一个方法。这就是外观模式的魅力——复杂留给自己,简单留给别人

6. 四种模式的整合要点

设计模式 在网关中的角色 关键类/接口 注意事项
代理模式 路由转发、负载均衡 RouteLocator, LoadBalancerClient 必须配合注册中心,不要硬编码地址
责任链模式 过滤器链(鉴权、限流、日志) GlobalFilter, GatewayFilterChain 注意过滤器顺序,鉴权放最前
适配器模式 协议转换(HTTP ↔ WebSocket 等) ProtocolAdapter 接口 返回新对象,不要修改原请求
外观模式 统一入口,封装子系统 GatewayFacade 对外只暴露一个方法

嗯,这里要注意一点:不要为了用模式而用模式。我见过有人硬把单例模式塞进网关里,结果因为网关是多线程并发环境,单例对象的状态被搞乱了。每种模式都有它的适用场景,用对了是利器,用错了是灾难。

7. 实战中的避坑指南

最后,分享几个我在实际项目中踩过的坑:

  • 过滤器链的超时问题:如果某个过滤器执行了耗时操作(比如调用外部鉴权服务),整个链都会被阻塞。我建议把耗时操作异步化,或者设置超时时间。
  • 适配器的异常处理:协议转换失败时,一定要返回明确的错误码。我曾经因为适配器抛了个空指针,结果客户端收到500却不知道哪里错了。
  • 代理模式的缓存:路由表不是一成不变的。服务实例上下线时,路由信息要动态更新。Spring Cloud Gateway 默认支持,但如果你自己实现代理,别忘了加缓存刷新机制。
  • 外观模式的日志:在外观类里统一打印请求日志,比在每个过滤器里单独打印要清晰得多。我习惯在 handle() 方法开始和结束各打一条日志,记录请求耗时。

说实话,这四种模式单独拿出来都不难,难的是把它们有机地整合在一起。但一旦整合好了,你会发现网关变得异常清晰——每个模式各司其职,代码可读性极高,出了问题也能快速定位。这就是设计模式的终极价值。


公众号:蓝海资料掘金营,微信deep3321