设计模式综合实战五:基于Spring Cloud实现微服务网关(代理+责任链+适配器+外观)
微服务网关,说白了就是系统的“总入口”。
我记得刚做微服务那会儿,团队里每个服务都要自己处理鉴权、限流、日志。结果呢?代码到处重复,改个鉴权逻辑要通知七八个团队。后来我意识到——网关不是可有可无的,它是微服务架构的标配。
这一章,我们就把代理模式、责任链模式、适配器模式、外观模式揉到一起,用Spring Cloud Gateway来实现一个真正的微服务网关。你想想看,这四个模式各自解决什么问题?
1. 整体架构:四种模式如何协作
先画一张图,让你一眼看明白它们的关系:
这张图我画了好几次才满意。你看,外观模式在最上层,给客户端一个干净的入口;责任链模式在中间,把各种横切关注点串起来;代理模式负责转发,隐藏后端细节;适配器模式处理协议差异。各司其职,互不干扰。
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