SpringCloud 核心组件解析之服务网关

 更新时间:2026年06月13日 09:02:25   作者:我登哥MVP  
这段文章主要SpringCloudGateway作为微服务网接口层的统一入口,替代Nginx,实现动态路由、鉴权与限流等功能,感兴趣的朋友跟随小编一起看看吧

技术栈:Spring Boot 3.2.0 + Spring Cloud 2023.0.0 + Spring Cloud Gateway
已不维护:Netflix Zuul → 替代用 Gateway
前置网关:Nginx(简要介绍,通常作为 Gateway 的前置入口)

6.1 是什么 — 网关的核心概念

6.1.1 生活化类比:小区门卫

          ┌──────────┐
访客 ───→ │ 小区门卫  │ ───→ 各栋楼(微服务)
          │          │
          │ 职责:    │
          │ ① 查身份 │ ← 鉴权/认证
          │ ② 指路    │ ← 路由转发
          │ ③ 登记    │ ← 日志/监控
          │ ④ 限流    │ ← 防止太多人同时涌入
          └──────────┘

网关 = 系统的统一入口,所有外部请求先到网关,再由网关转发到内部微服务。

6.1.2 网关 vs 直连

没有网关:用户 → http://192.168.1.10:8001/pay/get/1  ❌ 暴露内部 IP
有了网关:用户 → http://gateway.company.com/pay/get/1   ✅ 统一入口
                   │
                   ▼
               Gateway :9527 → Provider :8001/:8002

6.2 为什么 — 从 Nginx 到 Zuul 到 Gateway

6.2.1 Nginx(了解即可)

Nginx 是高性能反向代理,通常作为 Gateway 的前置层:

用户 → Nginx(SSL终结/静态资源) → Gateway(动态路由/鉴权) → 微服务
NginxSpring Cloud Gateway
实现C 语言Java(Netty)
动态路由需 reload实时生效 ✅
与注册中心集成需 Lua 扩展原生支持 ✅

6.2.2 Netflix Zuul(已停更,了解即可)

Zuul 1.xGateway
架构同步 Servlet异步 Netty
线程模型一个请求一个线程事件驱动
Spring Cloud 集成停止维护主推 ✅

6.3 怎么做 — Gateway 完整实战

6.3.0 小 Demo:先暴露痛点

假设系统有 10 个微服务,各自不同端口。前端要记住 10 个 IP:Port → 维护噩梦;每个服务都要自己处理跨域、鉴权 → 代码重复;无法统一限流 → 安全风险

引入 Gateway 后:一个端口(9527)→ 统一入口 → 路径路由 → 自动转发

6.3.1 三板斧核心模型

Route(路由)
  ├── id:唯一标识
  ├── uri:目标地址(lb://服务名)
  ├── predicates:断言(匹配规则)
  └── filters:过滤器(请求修改)

6.3.2 步骤 ①:引入依赖

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-consul-discovery</artifactId>
</dependency>

⚠️ Gateway 基于 WebFlux,不能引入 spring-boot-starter-web,两者冲突!

6.3.3 步骤 ②:YAML 配置路由

server:
  port: 9527
spring:
  application:
    name: cloud-gateway
  cloud:
    consul:
      host: localhost
      port: 8500
      discovery:
        prefer-ip-address: true
        service-name: ${spring.application.name}
    gateway:
      routes:
        # 路由1:基础路由 + 自定义断言
        - id: pay_routh1
          uri: lb://cloud-payment-service
          predicates:
            - Path=/pay/gateway/get/**
            - MyRoutePredicatedFactory="3"
        # 路由2:另一个路径
        - id: pay_routh2
          uri: lb://cloud-payment-service
          predicates:
            - Path=/pay/gateway/info/**
        # 路由3:带过滤器
        - id: pay_routh3
          uri: lb://cloud-payment-service
          predicates:
            - Path=/pay/gateway/filter/**
          filters:
            - My=atguigu

关键配置解读

配置项说明
lb://cloud-payment-servicelb:// 是 Gateway 内置的负载均衡协议
Path=/pay/gateway/get/**通配符匹配路径(Ant 风格)
filters只对当前路由生效的局部过滤器

6.3.4 步骤 ③:11 种内置断言

断言示例说明
After- After=2025-01-01T00:00:00+08:00在此时间之后的请求
Before- Before=2025-12-31T23:59:59+08:00在此时间之前的请求
Between- Between=...,...两个时间之间的请求
Cookie- Cookie=username,zzyyCookie 包含 username=zzyy
Header- Header=X-Request-Id, \\d+Header 包含 X-Request-Id 且值为数字
Host- Host=**.atguigu.comHost 以 .atguigu.com 结尾
Method- Method=GET指定 HTTP 方法
Path- Path=/api/v1/**按路径匹配(最常用)
Query- Query=username, \\w+查询参数包含 username 且值为字母
RemoteAddr- RemoteAddr=192.168.1.1/24按来源 IP 匹配
Weight- Weight=group1, 8按权重分流(灰度发布)

6.3.5 步骤 ④:自定义断言 — 会员等级限制

// cloud-gateway-9527/.../mygateway/MyRoutePredicatedFactory.java
@Component
public class MyRoutePredicatedFactory
        extends AbstractRoutePredicateFactory<MyRoutePredicatedFactory.Config> {
    public MyRoutePredicatedFactory() {
        super(Config.class);
    }
    @Override
    public Predicate<ServerWebExchange> apply(Config config) {
        return exchange -> {
            String usertype = exchange.getRequest()
                .getQueryParams().getFirst("userType");
            return "3".equals(usertype);  // 只有三级会员能访问
        };
    }
    @Validated
    public static class Config {
        @Getter @Setter @NotEmpty
        private String usertype;
    }
    @Override
    public List<String> shortcutFieldOrder() {
        return Collections.singletonList("userType");
    }
}

YAML 中:- MyRoutePredicatedFactory="3" → 请求必须带 ?userType=3

6.3.6 步骤 ⑤:自定义局部过滤器 — 参数校验

// cloud-gateway-9527/.../mygateway/MyGatewayFilterFactory.java
@Component
public class MyGatewayFilterFactory
        extends AbstractGatewayFilterFactory<MyGatewayFilterFactory.Config> {
    public MyGatewayFilterFactory() { super(Config.class); }
    @Override
    public GatewayFilter apply(Config config) {
        return (exchange, chain) -> {
            // 请求必须包含参数 atguigu
            if (exchange.getRequest().getQueryParams().containsKey("atguigu")) {
                return chain.filter(exchange);  // ✅ 放行
            } else {
                exchange.getResponse().setStatusCode(HttpStatus.BAD_REQUEST);
                return exchange.getResponse().setComplete();  // ❌ 拦截
            }
        };
    }
    public static class Config {
        @Setter @Getter
        private String status;
    }
    @Override
    public List<String> shortcutFieldOrder() {
        return Arrays.asList("status");
    }
}

6.3.7 步骤 ⑥:全局过滤器 — 请求日志

// cloud-gateway-9527/.../mygateway/MyGlobalFilter.java
@Component
@Slf4j
public class MyGlobalFilter implements GlobalFilter, Ordered {
    private static final String BEGIN_VISIT_TIME = "begin_visit_time";
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        // Pre-filter:记录进入时间
        exchange.getAttributes().put(BEGIN_VISIT_TIME, System.currentTimeMillis());
        // Post-filter:打印日志
        return chain.filter(exchange).then(Mono.fromRunnable(() -> {
            Long beginTime = exchange.getAttribute(BEGIN_VISIT_TIME);
            if (beginTime != null) {
                log.info("访问接口: {}:{}{}  参数: {}  耗时: {}ms",
                    exchange.getRequest().getURI().getHost(),
                    exchange.getRequest().getURI().getPort(),
                    exchange.getRequest().getURI().getPath(),
                    exchange.getRequest().getURI().getRawQuery(),
                    System.currentTimeMillis() - beginTime);
            }
        }));
    }
    @Override
    public int getOrder() { return 0; }
}

Pre Filter vs Post Filter

请求 → [Pre Filter: 鉴权/日志] → 业务处理 → [Post Filter: 修改响应/记日志] → 响应

chain.filter(exchange).then(...) 实现了 Post Filter 的效果。

6.3.8 Provider 端网关专用 Controller

// cloud-provider-payment8001/.../controller/PayGateWayController.java
@RestController
public class PayGateWayController {
    @Resource
    private PayService payService;
    @GetMapping("/pay/gateway/get/{id}")
    public ResultData<Pay> getById(@PathVariable("id") Integer id) {
        return ResultData.success(payService.getById(id));
    }
    @GetMapping("/pay/gateway/get/info")
    public ResultData<String> getInfo() {
        return ResultData.success("gateway info test " + IdUtil.simpleUUID());
    }
    // 测试过滤器效果
    @GetMapping("/pay/gateway/filter")
    public ResultData<String> getFilter(HttpServletRequest request) {
        String result = "";
        Enumeration<String> names = request.getHeaderNames();
        while (names.hasMoreElements()) {
            String name = names.nextElement();
            String value = request.getHeader(name);
            if (name.equalsIgnoreCase("X-Request-atguigu1")
                || name.equalsIgnoreCase("X-Request-atguigu2")) {
                result += name + "\t" + value + " ";
            }
        }
        return ResultData.success("gateWayFilter 过滤器test:" + result
            + "\t" + DateUtil.now());
    }
}

6.4 深入原理 — Gateway 内部架构

┌──────────────────────────────────────────┐
│           Spring Cloud Gateway            │
│  ┌─────────────────────────────────────┐ │
│  │     RoutePredicateHandlerMapping    │ │ ← 匹配路由(遍历所有路由规则)
│  └──────────────┬──────────────────────┘ │
│                 ▼                         │
│  ┌─────────────────────────────────────┐ │
│  │     FilteringWebHandler             │ │ ← 执行过滤器链
│  │     (Pre Filter → Proxy → Post)     │ │
│  └──────────────┬──────────────────────┘ │
│                 ▼                         │
│  ┌─────────────────────────────────────┐ │
│  │     NettyRoutingFilter              │ │ ← 基于 Netty 异步非阻塞
│  └─────────────────────────────────────┘ │
└──────────────────────────────────────────┘

6.5 内置过滤器速查

过滤器示例说明
PrefixPath- PrefixPath=/pay给路径加前缀
SetPath- SetPath=/pay/gateway/{segment}重写路径
RedirectTo- RedirectTo=302, https://baidu.com重定向
AddRequestHeader- AddRequestHeader=X-Custom, value1添加请求头
RemoveRequestHeader- RemoveRequestHeader=sec-fetch-site删除请求头
AddRequestParameter- AddRequestParameter=foo, bar添加查询参数
AddResponseHeader- AddResponseHeader=X-Response, Blue添加响应头
RemoveResponseHeader- RemoveResponseHeader=Content-Type删除响应头
RequestRateLimiter配合 Redis 限流请求限流

6.6 面试题

Q1:Gateway 和 Nginx 有什么区别?如何配合使用?

  • Nginx 是 C 语言高性能反向代理,适合静态资源、SSL 卸载、最外层入口
  • Gateway 是 Java 实现,适合动态路由、鉴权、与注册中心原生集成
  • 配合方式:用户 → Nginx(SSL, 静态资源) → Gateway(动态路由, 鉴权) → 微服务

Q2:Gateway 的 Pre Filter 和 Post Filter 有什么区别?

:Pre Filter 在转发下游前执行(鉴权、限流、加请求头);Post Filter 在下游返回响应后执行(加响应头、记日志)。Post Filter 通过 chain.filter(exchange).then(...) 实现。

Q3:如何实现网关层面的灰度发布?

:使用 Gateway 的 Weight 路由,同一个路径按权重分流:

- id: service_v1
  uri: lb://service
  predicates: [Path=/api/**, Weight=group1, 80]  # 80% 流量
- id: service_v2
  uri: lb://service-v2
  predicates: [Path=/api/**, Weight=group1, 20]  # 20% 流量

6.7 踩坑指南

现象原因解决
🔴 Gateway 与 Web 冲突启动报错Gateway 基于 WebFlux,不能引入 spring-boot-starter-web移除 web starter
🔴 路由不生效请求 404predicates 路径与请求路径不匹配检查 /** vs /* 的区别
🔴 lb:// 不生效UnknownHostException未注册到 Consul 或服务名拼写错误检查 Provider 注册状态
🔴 自定义过滤器不生效return 了 Mono.empty()正确的拦截姿势是 exchange.getResponse().setComplete()参考标准写法
🔴 Spring Boot 3.xNoClassDefFoundError: javaxJakarta 迁移所有 javax.* 改为 jakarta.*

6.8 章节总结

要点说明
三板斧Route(路由规则)+ Predicate(匹配条件)+ Filter(请求处理)
核心配置uri: lb://服务名 + predicates: Path=/xxx/**
11 种内置断言After/Before/Cookie/Header/Host/Method/Path/Query/RemoteAddr/Weight 等
自定义组件继承 AbstractRoutePredicateFactory(断言)/AbstractGatewayFilterFactory(局部过滤器)/ 实现 GlobalFilter(全局过滤器)
Pre/Post FilterPre 在转发前执行,Post 用 chain.filter().then() 实现
Zuul已停更,Gateway 是官方替代(异步非阻塞 Netty)

到此这篇关于SpringCloud 核心组件解析之服务网关的文章就介绍到这了,更多相关SpringCloud 服务网关内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • Spring MVC中日期格式转换的两种实用方法

    Spring MVC中日期格式转换的两种实用方法

    在开发基于 Spring MVC 的 Web 应用时,日期格式的转换是一个常见的需求,本文将详细介绍 Spring MVC 中两种日期格式转换的方法,包括创建过程和最终的运行结果,需要的朋友可以参考下
    2025-08-08
  • Java多线程连续打印abc实现方法详解

    Java多线程连续打印abc实现方法详解

    这篇文章主要介绍了Java多线程连续打印abc实现方法详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2020-03-03
  • Java类和对象的设计原理

    Java类和对象的设计原理

    这篇文章主要介绍了Java类和对象的设计原理,文章围绕主题展开详细的内容介绍,具有一定的参考价值,需要的朋友可以参考一下
    2022-07-07
  • 浅析scala中map与flatMap的区别

    浅析scala中map与flatMap的区别

    这篇文章主要介绍了浅析scala中map与flatMap的区别,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2019-06-06
  • 解决swaggerUI页面没有显示Controller方法的坑

    解决swaggerUI页面没有显示Controller方法的坑

    这篇文章主要介绍了解决swaggerUI页面没有显示Controller方法的坑,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2021-06-06
  • 深入理解Java中的注解Annotation

    深入理解Java中的注解Annotation

    这篇文章主要介绍了深入理解Java中的注解Annotation,注解在Java中确实也很常见,但是人们常常不会自己定义一个注解拿来用,我们虽然很少去自定义注解,但是学会注解的写法,注解的定义,学会利用反射解析注解中的信息,在开发中能够使用到,这是很关键的,需要的朋友可以参考下
    2023-10-10
  • Java MCP 实战之如何构建跨进程与远程的工具服务

    Java MCP 实战之如何构建跨进程与远程的工具服务

    MCP协议由Anthropic推出,支持stdio/SSE/Streaming通讯,提供工具、提示、资源原语,适用于AI与外部服务集成,兼容Java及多框架,具备鉴权、断线重连等生产特性,助力构建分布式系统,本文给大家介绍Java MCP实战之如何构建跨进程与远程的工具服务,感兴趣的朋友一起看看吧
    2025-07-07
  • mybatis双重foreach如何实现遍历map中的两个list数组

    mybatis双重foreach如何实现遍历map中的两个list数组

    本文介绍了如何解析前端传递的JSON字符串并在Java后台动态构建SQL查询条件,首先,通过JSONArray.fromObject()将JSON字符串转化为JSONArray对象,遍历JSONArray,从中提取name和infos,构建成Map对象用于Mybatis SQL映射
    2024-09-09
  • Java线程池详细解读

    Java线程池详细解读

    这篇文章主要给大家介绍了关于Java中方法使用的相关资料,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2021-08-08
  • Java校验银行卡是否正确的核心代码

    Java校验银行卡是否正确的核心代码

    这篇文章主要介绍了Java校验银行卡是否正确的核心代码,需要的朋友可以参考下
    2017-01-01

最新评论