写点什么

这篇 SpringCloud GateWay 详解,你用的到

作者:牧小农
  • 2022 年 6 月 11 日
  • 本文字数:9487 字

    阅读完需:约 31 分钟

这篇SpringCloud GateWay 详解,你用的到

点赞再看,养成习惯,微信搜索【牧小农】关注我获取更多资讯,风里雨里,小农等你,很高兴能够成为你的朋友。项目源码地址:公众号回复 sentinel,即可免费获取源码

背景

在微服务架构中,通常一个系统会被拆分为多个微服务,面对这么多微服务客户端应该如何去调用呢?如果没有其他更优方法,我们只能记录每个微服务对应的地址,分别去调用,但是这样会有很多的问题和潜在因素。


  1. 客户端多次请求不同的微服务,会增加客户端代码和配置的复杂性,维护成本比价高。

  2. 认证复杂,每个微服务可能存在不同的认证方式,客户端去调用,要去适配不同的认证,

  3. 存在跨域的请求,调用链有一定的相对复杂性(防火墙 / 浏览器不友好的协议)。

  4. 难以重构,随着项目的迭代,可能需要重新划分微服务


为了解决上面的问题,微服务引入了 网关 的概念,网关为微服务架构的系统提供简单、有效且统一的 API 路由管理,作为系统的统一入口,提供内部服务的路由中转,给客户端提供统一的服务,可以实现一些和业务没有耦合的公用逻辑,主要功能包含认证、鉴权、路由转发、安全策略、防刷、流量控制、监控日志等。


网关在微服务中的位置:



网关对比

  • Zuul 1.0 : Netflix 开源的网关,使用 Java 开发,基于 Servlet 架构构建,便于二次开发。因为基于 Servlet 内部延迟严重,并发场景不友好,一个线程只能处理一次连接请求。

  • Zuul 2.0 : 采用 Netty 实现异步非阻塞编程模型,一个 CPU 一个线程,能够处理所有的请求和响应,请求响应的生命周期通过事件和回调进行处理,减少线程数量,开销较小



  • GateWay : 是 Spring Cloud 的一个全新的 API 网关项目,替换 Zuul 开发的网关服务,基于 Spring5.0 + SpringBoot2.0 + WebFlux(基于⾼性能的 Reactor 模式响应式通信框架 Netty,异步⾮阻塞模型)等技术开发,性能高于 Zuul

  • Nginx+lua : 性能要比上面的强很多,使用 Nginx 的反向代码和负载均衡实现对 API 服务器的负载均衡以及高可用,lua 作为一款脚本语言,可以编写一些简单的逻辑,但是无法嵌入到微服务架构中

  • Kong : 基于 OpenResty(Nginx + Lua 模块)编写的高可用、易扩展的,性能高效且稳定,支持多个可用插件(限流、鉴权)等,开箱即可用,只支持 HTTP 协议,且二次开发扩展难,缺乏更易用的管理和配置方式

GateWay

官方文档:https://docs.spring.io/spring-cloud-gateway/docs/current/reference/html/#gateway-starter


Spring Cloud Gateway 是 Spring Cloud 的一个全新的 API 网关项目,目的是为了替换掉 Zuul1,它基于 Spring5.0 + SpringBoot2.0 + WebFlux(基于⾼性能的 Reactor 模式响应式通信框架 Netty,异步⾮阻塞模型)等技术开发,性能⾼于 Zuul,官⽅测试,Spring Cloud GateWay 是 Zuul 的 1.6 倍 ,旨在为微服务架构提供⼀种简单有效的统⼀的 API 路由管理⽅式。


  1. 可以与 Spring Cloud Discovery Client(如 Eureka)、Ribbon、Hystrix 等组件配合使用,实现路由转发、负载均衡、熔断、鉴权、路径重写、⽇志监控等

  2. Gateway 还内置了限流过滤器,实现了限流的功能。

  3. 设计优雅,容易拓展

基本概念

路由(Route)是 GateWay 中最基本的组件之一,表示一个具体的路由信息载体,主要由下面几个部分组成:


  1. id:路由唯一标识,区别于其他的 route

  2. url: 路由指向的目的地 URL,客户端请求最终被转发到的微服务

  3. order: 用于多个 Route 之间的排序,数值越小越靠前,匹配优先级越高

  4. predicate:断言的作用是进行条件判断,只有断言为 true,才执行路由

  5. filter: 过滤器用于修改请求和响应信息

核心流程


核心概念:


  1. Gateway ClientSpring Cloud Gateway 发送请求

  2. 请求首先会被 HttpWebHandlerAdapter 进行提取组装成网关上下文

  3. 然后网关的上下文会传递到 DispatcherHandler ,它负责将请求分发给 RoutePredicateHandlerMapping

  4. RoutePredicateHandlerMapping 负责路由查找,并根据路由断言判断路由是否可用

  5. 如果过断言成功,由 FilteringWebHandler 创建过滤器链并调用

  6. 通过特定于请求的 Fliter 链运行请求,Filter 被虚线分隔的原因是 Filter 可以在发送代理请求之前(pre)和之后(post)运行逻辑

  7. 执行所有 pre 过滤器逻辑。然后进行代理请求。发出代理请求后,将运行“post”过滤器逻辑。

  8. 处理完毕之后将 Response 返回到 Gateway 客户端


Filter 过滤器:


  • Filter 在 pre 类型的过滤器可以做参数效验、权限效验、流量监控、日志输出、协议转换等。

  • Filter 在 post 类型的过滤器可以做响应内容、响应头的修改、日志输出、流量监控等

核心思想

当用户发出请求达到 GateWay 之后,会通过一些匹配条件,定位到真正的服务节点,并且在这个转发过程前后,进行一些细粒度的控制,其中 Predicate(断言) 是我们的匹配条件,Filter 是一个拦截器,有了这两点,再加上 URL,就可以实现一个具体的路由,核心思想:路由转发+执行过滤器链


这个过程就好比考试,我们考试首先要找到对应的考场,我们需要知道考场的地址和名称(id 和 url),然后我们进入考场之前会有考官查看我们的准考证是否匹配(断言),如果匹配才会进入考场,我们进入考场之后,(路由之前)会进行身份的登记和考试的科目,填写考试信息,当我们考试完成之后(路由之后)会进行签字交卷,走出考场,这个就类似我们的过滤器



Route(路由) :构建网关的基础模块,由 ID、目标 URL、过滤器等组成


Predicate(断言) :开发人员可以匹配 HTTP 请求中的内容(请求头和请求参数),如果请求断言匹配贼进行路由


Filter(过滤) :GateWayFilter 的实例,使用过滤器,可以在请求被路由之前或者之后对请求进行修改

框架搭建

通过上述讲解已经了解了基础概念,我们来动手搭建一个GateWay项目,来看看它到底是如何运行的新建项目:cloud-alibaba-gateway-9006


版本对应:


GateWay 属于 SprinigCloud 且有 web 依赖,在我们导入对应依赖时,要注意版本关系,我们这里使用的版本是 2.2.x 的版本,所以配合使用的Hoxton.SR5版本


在这里我们要注意的是引入 GateWay 一定要删除 spring-boot-starter-web 依赖,否则会有冲突无法启动



父类 pom 引用:


<spring-cloud-gateway-varsion>Hoxton.SR5</spring-cloud-gateway-varsion>
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>${spring-cloud-gateway-varsion}</version> <type>pom</type> <scope>import</scope></dependency>
复制代码


子类 POM 引用:


<dependencies>    <dependency>        <groupId>com.alibaba.cloud</groupId>        <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>    </dependency>    <dependency>        <groupId>org.springframework.cloud</groupId>        <artifactId>spring-cloud-starter-gateway</artifactId>        <version>2.2.5.RELEASE</version>    </dependency>    <dependency>        <groupId>org.projectlombok</groupId>        <artifactId>lombok</artifactId>    </dependency></dependencies>
复制代码


yml 配置


server:  port: 9006spring:  application:    name: cloud-gateway-service  cloud:    nacos:      discovery:        server-addr: localhost:8848    gateway:      discovery:        locator:          enabled: false #开启注册中心路由功能      routes:  # 路由        - id: nacos-provider #路由ID,没有固定要求,但是要保证唯一,建议配合服务名          uri: http://localhost:9001/nacos-provider # 匹配提供服务的路由地址 lb://表示开启负载均衡          predicates: # 断言            - Path=/mxn/** # 断言,路径相匹配进行路由
复制代码


我们在之前的cloud-alibaba-nacos-9001项目中添加下面测试代码


@RestController@RequestMapping("/mxn")public class DemoController {
@Value("${server.port}") private String serverPort;
@GetMapping(value = "/hello") public String hello(){ return "hello world ,my port is :"+serverPort; }
}
复制代码


启动 Nacos、cloud-alibaba-nacos-9001cloud-alibaba-gateway-9006通过 gateway 网关去访问 9001 的 mxn/order 看看。


首先我们在 Nacos 中看到我们服务是注册到 Nacos 中了



然后我们访问http://localhost:9001/mxn/hello,确保是成功的,在通过http://localhost:9006/mxn/hello去访问,也是 OK,说明我们 GateWay 搭建成功,我们进入下一步




在上述方法中我们是通过 YML 去完成的配置,GateWay还提供了另外一种配置方式,就是通过代码的方式进行配置,@Bean 注入一个 RouteLocator


import org.springframework.cloud.gateway.route.RouteLocator;import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;
@Configurationpublic class GateWayConfig {
/* 配置了一个id为path_mxn的路由规则 当访问地址http://localhost:9999/mxn/** 就会转发到http://localhost:9001/nacos-provider/mxn/任何地址 */ @Bean public RouteLocator gateWayConfigInfo(RouteLocatorBuilder routeLocatorBuilder){ // 构建多个路由routes RouteLocatorBuilder.Builder routes = routeLocatorBuilder.routes(); // 具体路由地址 routes.route("path_mxn",r -> r.path("/mxn/**").uri("http://localhost:9001/nacos-provider")).build(); // 返回所有路由规则 return routes.build(); }}
复制代码


我们可以将路由注释掉之后看一下,重启 9006 服务,访问地址http://localhost:9006/mxn/hello 就可以转发到 9001 中具体的接口中




这里并不推荐,使用代码的方式来进行配置gateWay,大家有个了解就可以,因为代码的配置维护的成本比较高,而且对于一些需要修改的项,需要改代码才可以完成,这样不利于维护和拓展,所以还是推荐大家使用 yml 进行配置。

GateWay 负载均衡

在上述的讲解中,我们已经掌握了 GateWay 的一些基本配置和两种使用方式,下面我们就来讲解一下 GateWay 如何实现负载均衡


我们只需要在 9006 中添加lb://nacos-provider就可以显示负载均衡。



当我们去访问http://localhost:9006/mxn/hello的时候,就可以看到 9001 和 9002 不停的切换



Predicate 断言

在这一篇中我们来研究一下 断言 ,我们可以理解为:当满足条件后才会进行转发路由,如果是多个,那么多个条件需要同时满足


在官方提供的断言种类有 11 种(最新的有 12 种类型):


具体地址:https://docs.spring.io/spring-cloud-gateway/docs/current/reference/html/#gateway-request-predicates-factories



  1. After:匹配在指定日期时间之后发生的请求。

  2. Before:匹配在指定日期之前发生的请求。

  3. Between:需要指定两个日期参数,设定一个时间区间,匹配此时间区间内的请求。

  4. Cookie:需要指定两个参数,分别为 name 和 regexp(正则表达式),也可以理解 Key 和 Value,匹配具有给定名称且其值与正则表达式匹配的 Cookie。

  5. Header:需要两个参数 header 和 regexp(正则表达式),也可以理解为 Key 和 Value,匹配请求携带信息。

  6. Host:匹配当前请求是否来自于设置的主机。

  7. Method:可以设置一个或多个参数,匹配 HTTP 请求,比如 GET、POST

  8. Path:匹配指定路径下的请求,可以是多个用逗号分隔

  9. Query:需要指定一个或者多个参数,一个必须参数和一个可选的正则表达式,匹配请求中是否包含第一个参数,如果有两个参数,则匹配请求中第一个参数的值是否符合正则表达式。

  10. RemoteAddr:匹配指定 IP 或 IP 段,符合条件转发。

  11. Weight:需要两个参数 group 和 weight(int),实现了路由权重功能,按照路由权重选择同一个分组中的路由


1. After : 表示配置时间之后才进行转发


时间戳获取代码,用于时间代码的获取:


    public static void main(String[] args) {        ZonedDateTime zbj = ZonedDateTime.now();//默认时区        System.out.println(zbj);    }
复制代码


spring:  application:    name: cloud-gateway-service  cloud:    nacos:      discovery:        server-addr: localhost:8848    gateway:      discovery:        locator:          enabled: true #开启注册中心路由功能      routes:  # 路由        - id: nacos-provider #路由ID,没有固定要求,但是要保证唯一,建议配合服务名          uri: lb://nacos-provider # 匹配提供服务的路由地址 lb://表示开启负载均衡          predicates: # 断言            - Path=/mxn/** # 断言,路径相匹配进行路由            - After=2022-06-11T16:30:40.785+08:00[Asia/Shanghai] #在这个时间之后的请求够可以进行通过,之前的则不能进行访问
复制代码


如果在时间段之前访问则 404



Before


匹配ZonedDateTime类型的时间,表示匹配在指定日期时间之前的请求,之后的请求则拒绝 404 错误


predicates: # 断言  - Path=/mxn/** # 断言,路径相匹配进行路由# - After=2022-06-11T16:30:40.785+08:00[Asia/Shanghai] #在这个时间之后的请求够可以进行通过,之前的则不能进行访问  - Before=2022-06-11T15:30:40.785+08:00[Asia/Shanghai]
复制代码


BetweenBetween 可以匹配ZonedDateTime类型的时间,由两个ZonedDateTime参数组成,第一个参数为开始时间,第二参数为结束时间,逗号进行分隔,匹配在指定的开始时间与结束时间之内的请求,配置如下:


predicates: # 断言  - Path=/mxn/** # 断言,路径相匹配进行路由# - After=2022-06-11T16:30:40.785+08:00[Asia/Shanghai] #在这个时间之后的请求够可以进行通过,之前的则不能进行访问#  - Before=2022-06-11T15:30:40.785+08:00[Asia/Shanghai]  - Between=2022-06-11T15:30:40.785+08:00[Asia/Shanghai],2022-06-11T16:30:40.785+08:00[Asia/Shanghai]
复制代码


Cookie


由两个参数组成,分别为name(Key)regexp(正则表达式)(Value),匹配具有给定名称且其值与正则表达式匹配的 Cookie。


路由规则会通过获取 Cookie name 值和正则表达式去匹配,如果匹配上就会执行路由,如果匹配不上则不执行。


predicates: # 断言  - Path=/mxn/** # 断言,路径相匹配进行路由# - After=2022-06-11T16:30:40.785+08:00[Asia/Shanghai] #在这个时间之后的请求够可以进行通过,之前的则不能进行访问#  - Before=2022-06-11T15:30:40.785+08:00[Asia/Shanghai]#  - Between=2022-06-11T15:30:40.785+08:00[Asia/Shanghai],2022-06-11T16:30:40.785+08:00[Asia/Shanghai]   - Cookie=muxiaonong,[a-z]+ # 匹配Cookie的key和value(正则表达式)表示任意字母
复制代码


小写字母匹配成功:




数字匹配不成功:




Header


由两个参数组成,第一个参数为Header名称,第二参数为Header的Value值,指定名称的其值和正则表达式相匹配的 Header 的请求


predicates: # 断言  - Path=/mxn/** # 断言,路径相匹配进行路由# - After=2022-06-11T16:30:40.785+08:00[Asia/Shanghai] #在这个时间之后的请求够可以进行通过,之前的则不能进行访问#  - Before=2022-06-11T15:30:40.785+08:00[Asia/Shanghai]#  - Between=2022-06-11T15:30:40.785+08:00[Asia/Shanghai],2022-06-11T16:30:40.785+08:00[Asia/Shanghai]#  - Cookie=muxiaonong,[a-z]+ # 匹配Cookie的key和value(正则表达式)表示任意字母  - Header=headerName, \d+ # \d表示数字
复制代码


请求头携带数字断言请求成功,



断言字母匹配失败:



Host


匹配当前请求是否来自于设置的主机。


predicates: # 断言  - Path=/mxn/** # 断言,路径相匹配进行路由# - After=2022-06-11T16:30:40.785+08:00[Asia/Shanghai] #在这个时间之后的请求够可以进行通过,之前的则不能进行访问#  - Before=2022-06-11T15:30:40.785+08:00[Asia/Shanghai]#  - Between=2022-06-11T15:30:40.785+08:00[Asia/Shanghai],2022-06-11T16:30:40.785+08:00[Asia/Shanghai]#  - Cookie=muxiaonong,[a-z]+ # 匹配Cookie的key和value(正则表达式)表示任意字母#  - Header=headerName, \d+ # \d表示数字  - Host=**.muxiaonong.com #匹配当前的主机地址发出的请求
复制代码


满足 Host 断言,请求成功



不满足 Host 断言失败



**Method **


可以设置一个或多个参数,匹配 HTTP 请求,比如POST,PUT,GET,DELETE


predicates: # 断言  - Path=/mxn/** # 断言,路径相匹配进行路由# - After=2022-06-11T16:30:40.785+08:00[Asia/Shanghai] #在这个时间之后的请求够可以进行通过,之前的则不能进行访问#  - Before=2022-06-11T15:30:40.785+08:00[Asia/Shanghai]#  - Between=2022-06-11T15:30:40.785+08:00[Asia/Shanghai],2022-06-11T16:30:40.785+08:00[Asia/Shanghai]#  - Cookie=muxiaonong,[a-z]+ # 匹配Cookie的key和value(正则表达式)表示任意字母#  - Header=headerName, \d+ # \d表示数字#  - Host=**.muxiaonong.com #匹配当前的主机地址发出的请求  - Method=POST,GET
复制代码


GET 断言成功



PUT 断言请求失败



Query


由两个参数组成,第一个为参数名称(必须),第二个为参数值(可选-正则表达式),匹配请求中是否包含第一个参数,如果有两个参数,则匹配请求中第一个参数的值是否符合第二个正则表达式。


predicates: # 断言  - Path=/mxn/** # 断言,路径相匹配进行路由# - After=2022-06-11T16:30:40.785+08:00[Asia/Shanghai] #在这个时间之后的请求够可以进行通过,之前的则不能进行访问#  - Before=2022-06-11T15:30:40.785+08:00[Asia/Shanghai]#  - Between=2022-06-11T15:30:40.785+08:00[Asia/Shanghai],2022-06-11T16:30:40.785+08:00[Asia/Shanghai]#  - Cookie=muxiaonong,[a-z]+ # 匹配Cookie的key和value(正则表达式)表示任意字母#  - Header=headerName, \d+ # \d表示数字#  - Host=**.muxiaonong.com #匹配当前的主机地址发出的请求#  - Method=POST,GET  - Query=id,.+ # 匹配任意请求参数,这里如果需要匹配多个参数,可以写多个- Query=
复制代码


断言匹配 请求成功



RemoteAddr


参数由 CIDR 表示法(IPv4 或 IPv6)字符串组成,也就是匹配的 ID 地址,配置如下:


predicates: # 断言  - Path=/mxn/** # 断言,路径相匹配进行路由# - After=2022-06-11T16:30:40.785+08:00[Asia/Shanghai] #在这个时间之后的请求够可以进行通过,之前的则不能进行访问#  - Before=2022-06-11T15:30:40.785+08:00[Asia/Shanghai]#  - Between=2022-06-11T15:30:40.785+08:00[Asia/Shanghai],2022-06-11T16:30:40.785+08:00[Asia/Shanghai]#  - Cookie=muxiaonong,[a-z]+ # 匹配Cookie的key和value(正则表达式)表示任意字母#  - Header=headerName, \d+ # \d表示数字#  - Host=**.muxiaonong.com #匹配当前的主机地址发出的请求#  - Method=POST,GET#  - Query=id,.+ # 匹配任意请求参数,这里如果需要匹配多个参数,可以写多个Query  - RemoteAddr=192.168.1.1/24
复制代码


RemoteAddr


需要两个参数 group 和 weight(int)权重数值,实现了路由权重功能,表示将相同的请求根据权重跳转到不同的 uri 地址,要求 group 的名称必须一致


routes:  # 路由  - id: weight_high #路由ID,没有固定要求,但是要保证唯一,建议配合服务名    uri: https://blog.csdn.net/qq_14996421    predicates: # 断言      - Weight=groupName,8  - id: weight_low #路由ID,没有固定要求,但是要保证唯一,建议配合服务名    uri: https://juejin.cn/user/2700056290405815    predicates: # 断言      - Weight=groupName,2
复制代码


直接访问http://localhost:9006/可以看到我们请求的地址成 8/2 比例交替显示, 80% 的流量转发到https://blog.csdn.net/qq_14996421,将约 20% 的流量转发https://juejin.cn/user/2700056290405815


Predicate 就是为了实现一组匹配规则,让请求过来找到对应的 Route 进行处理。如果有多个断言则全部命中后进行处理

GateWay Filter

路由过滤器允许修改传入的 HTTP 请求或者返回的 HTTP 响应,路由过滤器的范围是特定的路由.


Spring Cloud GateWay 内置的 Filter 生命周期有两种:pre(业务逻辑之前)、post(业务逻辑之后)


GateWay 本身自带的 Filter 分为两种: GateWayFilter(单一)、GlobalFilter(全局)


GateWay Filter 提供了丰富的过滤器的使用,单一的有 32 种,全局的有 9 种,有兴趣的小伙伴可以了解一下。


官方参考网址:https://docs.spring.io/spring-cloud-gateway/docs/current/reference/html/#global-filters

StripPrefix

StripPrefix 在我们当前请求中,通过规则值去掉某一部分地址,比如我们有一台服务中加入了一个前端nacos-provider想要通过这个去访问,我们在项目cloud-alibaba-nacos-9001中加入 context-path


server:  port: 9001  servlet:    context-path: /nacos-provider
复制代码


现在 9001 的访问路径变为http://localhost:9001/nacos-provider/mxn/hello,但是如果我们通过网关去访问路径就会变成http://localhost:9006/mxn/nacos-provider/mxn/hello 这个时候我们通过这个路径去访问是访问不成功的,想要解决这个方法,这个就用到了我们FIlter 中的 StripPrefix


routes:  # 路由  - id: nacos-provider #路由ID,没有固定要求,但是要保证唯一,建议配合服务名    uri: lb://nacos-provider    predicates: # 断言      - Path=/mxn/** # 匹配对应地址    filters:      - StripPrefix=1 # 去掉地址中的第一部分
复制代码


我们重新启动 9006 项目,再去访问


自定义 Filter

虽然 Gateway 给我们提供了丰富的内置 Filter,但是实际项目中,自定义 Filter 的场景非常常见,因此单独介绍下自定义 FIlter 的使用。


想要实现 GateWay 自定义过滤器,那么我们需要实现 GatewayFilter 接口和 Ordered 接口


@Slf4j@Componentpublic class MyFilter implements Ordered, GlobalFilter {    /**     * @param exchange 可以拿到对应的request和response     * @param chain 过滤器链     * @return 是否放行     */    @Override    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {        //获取第一个参数        String id = exchange.getRequest().getQueryParams().getFirst("id");        //打印当前时间        log.info("MyFilter 当前请求时间为:"+new Date());        //判断用户是否存在        if(StringUtils.isEmpty(id)){            log.info("用户名不存在,非法请求!");            //如果username为空,返回状态码为407,需要代理身份验证            exchange.getResponse().setStatusCode(HttpStatus.PROXY_AUTHENTICATION_REQUIRED);            // 后置过滤器            return exchange.getResponse().setComplete();        }        return chain.filter(exchange);    }
/** * 设定过滤器的优先级,值越小则优先级越高 * @return */ @Override public int getOrder() { return 0; }}
复制代码


当我们访问http://localhost:9006/mxn/nacos-provider/mxn/hello请求,没有携带 ID 参数,请求失败



当我们访问http://localhost:9006/mxn/nacos-provider/mxn/hello?id=1请求,请求成功


总结

到这里我们的GateWay就讲解完了,对于 GateWay 的核心点主要有三个Route\Predicate\Filter,我们搞懂了这三点,基本上对于GateWay的知识就掌握的差不多了,GateWay 核心的流程就是:路由转发+执行过滤器链,如果对文中有疑问的小伙伴,欢迎留言讨论。


创作不易,如果文中对你有帮助,记得点赞关注,您的支持是我创作的最大动力。


我是牧小农,怕什么真理无穷,进一步有进一步的欢喜~

发布于: 刚刚阅读数: 4
用户头像

牧小农

关注

业精于勤荒于嬉,行成于思毁于随。 2019.02.13 加入

公众号【牧小农】

评论

发布
暂无评论
这篇SpringCloud GateWay 详解,你用的到_SpringCloud_牧小农_InfoQ写作社区