springCloud/Alibaba常用中间件之GateWay网关

文章目录


SpringCloud:

微服务的中间件介绍与使用

微服务架构体系图:

依赖版本补充

下面所有代码中的依赖版本如下:

xml 复制代码
<properties>
  <maven.compiler.source>17</maven.compiler.source>
  <maven.compiler.target>17</maven.compiler.target>
  <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
  <hutool.version>5.8.22</hutool.version>
  <lombok.version>1.18.30</lombok.version>
  <druid.version>1.2.18</druid.version>
  <mybatis.springboot.version>3.0.3</mybatis.springboot.version>
  <mysql.version>8.0.33</mysql.version>
  <fastjson2.version>2.0.48</fastjson2.version>
  <swagger3.version>2.2.0</swagger3.version>
  <mapper.version>4.2.3</mapper.version>
  <persistence-api.version>1.0.2</persistence-api.version>
  <spring.boot.test.version>3.1.5</spring.boot.test.version>
  <spring.boot.version>3.2.0</spring.boot.version>
  <spring.cloud.version>2023.0.0</spring.cloud.version>
  <spring.cloud.alibaba.version>2023.0.0.0-RC1</spring.cloud.alibaba.version>
  <knife4j-openapi3.version>4.4.0</knife4j-openapi3.version>
</properties>

GateWay:网关

什么是网关网关 简单的理解就是在请求和服务之间加了一个 中间层
作用是

一、管控 路由的请求(反向代理、鉴权、流量控制、熔断、日志监控)

二、在每次路由中进行 增删内容

图形化展示网关位置:


工作原理

简单总结就是:路由转发+断言判断+执行过滤链列表

参考官网:https://docs.spring.io/spring-cloud-gateway/reference/spring-cloud-gateway/how-it-works.html

三大核心之Router:路由

概念:路由是构建网管的基本模块,它有ID、目标URI、一系列断言和过滤器组成,如果断言为true则匹配该条路由

接下来直接开始配置一个基本的路由

1、导入基础依赖
html 复制代码
<!--gateway依赖-->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<!--服务注册发现consul discovery,网关也要注册进服务注册中心统一管控-->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-consul-discovery</artifactId>
</dependency>
<!-- 指标监控健康检查的actuator,网关是响应式编程删除掉spring-boot-starter-web dependency-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
2、进行服务注册
yml 复制代码
server:
  port: 9567
spring:
  application:
    name: cloud-gateway
  cloud:
    consul:
      port: 8500 #consul的端口号
      host: localhost #consul的网址
      discovery:
        service-name: ${spring.application.name} #配置服务名

启动类添加注册注解

java 复制代码
@SpringBootApplication
@EnableDiscoveryClient/*服务组注册*/
public class Main9567 {
    public static void main(String[] args) {
        SpringApplication.run(Main9567.class, args);
    }
}

启动查看是否完成了服务注册

3、路由映射

这里可以先创建一个简单的业务[可以不需要数据库就返回一个值即可]做映射地址(别忘了要进行服务注册)

这里为了方便我就修改一下上面的业务的进行测试了。配置好测试业务之后,在路由服务的配置文件中进行配置:

yml 复制代码
server:
  port: 9567

spring:
  application:
    name: cloud-gateway
  cloud:
    consul:
      port: 8500 #consul的端口号
      host: localhost #consul的网址
      discovery:
        service-name: ${spring.application.name} #配置服务名

    gateway:
      routes:
        - id: gateway_router_1  #路由的ID(类似mysql主键ID),没有固定规则但要求唯一,建议配合服务名
          #配置地址方法一(不推荐):
          #          uri: localhost:8001 #填写业务服务的地址和端口号(缺点:灵活性差)
          #方法二(推荐):动态路由
          uri: lb://cloud-payment-service #填写业务服务名(优点:可动态的配置路由,lb:可以支持负载均衡)
          predicates: # 配置一个简单的断言,以便于演示。也就是访问此路由的条件
            - Path=/pay/gateway/get/** #满足此路由的才可以访问,**:通配符
        - id: gateway_router_2
          uri: lb://cloud-payment-service
          predicates:
            - Path=/pay/gateway/info/**
4、测试访问GateWay的端口是否可以访问

访问成功,说明映射成功了!

这里可以也试一下当有多个服务(yourServerName),下使用lb://yourServerName是否有负载均衡的效果

三大核心之Predicate:断言

概念:开发人员可以匹配HTTP请求中的所有内容,如果请求与断言相匹配则进行路由

配置文件
yml 复制代码
    gateway:
      routes:
        - id: gateway_router_1  #路由的ID(类似mysql主键ID),没有固定规则但要求唯一,建议配合服务名
          #配置地址方法一(不推荐):
          #          uri: localhost:8001 #填写业务服务的地址和端口号(缺点:灵活性差)
          #方法二(推荐):动态路由
          uri: lb://cloud-payment-service #填写业务服务名(优点:可动态的配置路由,lb:可以支持负载均衡)
          predicates: # 配置一个简单的断言,以便于演示。也就是访问此路由的条件
            - Path=/pay/gateway/get/** #满足此路由的才可以访问,**:通配符
        - id: gateway_router_2
          uri: lb://cloud-payment-service
          predicates:
            - Path=/pay/gateway/get/**              # 断言,路径相匹配的进行路由
            - After=2025-04-20T16:04:22.672173200+08:00[Asia/Shanghai]   #配置指定的时间之后才开启此路由
            - Before=2025-04-20T16:08:13.979939200+08:00[Asia/Shanghai]   #与after相反,没有达到此时间之后都可以访问,过了之后便会直接关闭
            - Between=2025-04-20T16:09:38.676463500+08:00[Asia/Shanghai],2025-04-20T16:11:38.676463500+08:00[Asia/Shanghai] #区间
            - Cookie=phone,^1(3[0-9]|4[57]|5[0-35-9]|7[0678]|8[0-9])\d{8}$    #根据cookie参数进行验证,第一个是key值,第二个是规定value值正则
            - Header=X-Request-Id, \d+    #根据请求头的参数名和值进行判断,第一个值是name,第二个值是正则表达式
            - Host=**.chyb.com,**.chyb.org  #根据Host这个参数进行模糊查询,**:通配符
            - Query=num, \d+ #必须要有num这个参数,且值为整数
            - RemoteAddr=192.168.235.0/24 #根据请求人的ip进行拦截,/24:是指前面的24位是不可以变的,24:前面的192.168.43是不可以变得,注意:这里请求的时候不要在使用localhost而是要输入自己的IP地址
            - Method=GET,POST #按照请求方式进行匹配

这里就相当于API的调用一样没有什么好说的,不清楚的可以直接参考官方文档:https://docs.spring.io/spring-cloud-gateway/reference/spring-cloud-gateway/request-predicates-factories.html

中文官网:https://springdoc.cn/spring-cloud-gateway/#gateway-request-predicates-factories

这里在单独将一个知识点领出来说:快捷方式的配置(常用)完全展开的参数 语法如下:

yml 复制代码
# 快捷方式的配置
- Cookie=mycookie,mycookievalue
#完全展开的参数:完全展开的参数看起来更像标准的yaml配置,有名称/值对。一般来说,会有一个 name key和一个 args key。args key是一个键值对的映射,用于配置谓词或过滤器。
- name: Cookie
  args:
    name: mycookie
    regexp: mycookievalue
自定义Predicate(断言)

创建 ~RoutePredicateFactory 类,其代码如下:

java 复制代码
@Component /*注入Spring容器中*/
/*注意命名规范:~~~RoutePredicateFactory*/
public class MyRoutePredicateFactory extends AbstractRoutePredicateFactory<MyRoutePredicateFactory.Config> {
    public MyRoutePredicateFactory() {
        super(MyRoutePredicateFactory.Config.class);
    }

    @Validated
    public static class Config {
        @Setter
        @Getter
        @NotEmpty
        private String userType; //钻、金、银等用户等级
    }

    /**
     * 写逻辑:根据userType进行判断是否可以访问此路由
     * @param config
     * @return
     */
    @Override
    public Predicate<ServerWebExchange> apply(MyRoutePredicateFactory.Config config) {
        return new Predicate<ServerWebExchange>() {
            @Override
            public boolean test(ServerWebExchange serverWebExchange) {
                //检查request的参数里面,userType是否为指定的值,符合配置就通过
                String userType = serverWebExchange.getRequest().getQueryParams().getFirst("userType");

                if (userType == null) return false;

                //如果说参数存在,就和config的数据进行比较
                if (userType.equals(config.getUserType())) {
                    return true;
                }

                return false;
            }
        };
    }

    /**
     * 用来支持断言的快捷方式
     * @return
     */
    @Override
    public List<String> shortcutFieldOrder() {
        return Collections.singletonList("userType");
    }
}

配置文件的设置

yml 复制代码
#....
predicates:
  #这里的"My"是根据你类名有关系,当遵守命名规范:~RoutePredicateFactory,则默认的名字就是:~
  - My=diamond  #当参数userType的值等于diamond才可以访问此路由

三大核心之Filter:过滤

概念:指的是Spring框架中GatewayFilter的实例,使用过滤器,可以在请求路由之前或之后对请求进行修改

过滤器类型分为:

  1. 全局默认过滤器 (Global Filters):每一个路由都要执行的过滤器
  2. 单一内置过滤器 (GateWayFilter),"又称路由过滤器":指定某一个路由执行的过滤器
配置文件
yml 复制代码
          filters:
            #             RequestHeader的增删改
            - AddRequestHeader=chyb-name, chybName1 #添加一个请求头(chyb-name)值为:chybName1
            - AddRequestHeader=chyb-name2, chybName2
            - RemoveRequestHeader=chyb-name  #删除一个请求头(user-agent)
            - SetRequestHeader=chyb-name2, chybName3 #修改某一个请求头(chyb-name2)的值为chybName3,若是没哟此请求头,则直接会自动添加

            #             RequestParameter请求参数的增删
            - AddRequestParameter=param1, 2983 #增加参数
            - RemoveRequestParameter=param2   #删除参数

            #             ResponseHeader 响应头的增删改
            - AddResponseHeader=chybName, chyb1 #创建一个响应头(chybName)值为:chyb1
            - SetResponseHeader=date, 2006-02-09 #设置date响应头的值为:2006-02-09
            - RemoveResponseHeader=Content-Type #删除Content-Type(接受类型)

            #             前缀、路径、相关的配置
            - PrefixPath=/pay   #设置访问路径的前缀
            - SetPath=/pay/gateway/{param}   #设置访问路径,{~}为占位符
            - RedirectTo=301, https://baidu.com   #重定向到https://baidu.com,状态吗为302

参考官网:https://docs.spring.io/spring-cloud-gateway/reference/spring-cloud-gateway/gatewayfilter-factories.html

中文官网:https://springdoc.cn/spring-cloud-gateway/#gatewayfilter-工厂

自定义全局过滤 ------ 输出每一个路由所执行的时间

1、创建一个~~GlobalFilter,并实现自定义过滤器所需的接口:GlobalFilter, Ordered

java 复制代码
/*注入到Spring容器中,之后会将所有注入到容器中的过滤器进行合并,形成过滤器列表*/
@Component
@Slf4j
/*这里是自定配置全局过滤器,就是每一个路由都会去执行的*/
public class MyGlobalFilter implements GlobalFilter, Ordered {
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        return null;
    }

    /**
     * 设置执行接口进行排序,返回值越小越靠前
     * @return
     */
    @Override
    public int getOrder() {
        return 0;
    }
}

2、在filter方法下写执行逻辑,实现:输出每一个路由所执行的时间

java 复制代码
public static final String BEGIN_VISIT_TIME = "BEGIN_VISIT_TIME";/*初始化方法执行的开始时间*/

/**
 *用来接受响应请求报文,以及控制放行操作(这里有点像将拦截器中的preHandle[访问前拦截方法]和postHandle[访问后拦截方法]结合到一起了)
 * @param exchange :类似于Server,它里面封装了三个属性:
 *                 ServerHttpResponse、ServerHttpRequest:这两个属性存储着:请求/响应的报文,所以可以通过exchange这个参数获取到这些属性
 *                 Map<String, Object>:这属性就是我们要进行额外的添加对象就可以添加到这里,运用的方法就直接.pus即可
 * @param chain : 这个属性可以控制过滤器的放行操作
 * @return 这里返回的类型是Mono这个类型是在SpringBoot响应式编程中所引出的类,适用于高并发和高吞吐量场景(在资源有限的情况下提高系统的吞吐量和伸缩性【并非提高性能】)
 */
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
    System.out.println("=====" + exchange.getAttributes().get(BEGIN_VISIT_TIME) + "=====");
    exchange.getAttributes().put(BEGIN_VISIT_TIME, System.currentTimeMillis());
    System.out.println("------" + exchange.getAttributes().get(BEGIN_VISIT_TIME) + "-----");

    /**
     * 这里的chain.filter(exchange)是放行操作,而.then()是任务串联(在 Mono完成后触发新任务,但不依赖前一个任务的输出结果。)
     */
    return chain.filter(exchange).then(Mono.fromRunnable(() -> {
        Long beginTime = exchange.getAttribute(BEGIN_VISIT_TIME);
        URI uri = exchange.getRequest().getURI();
        if (beginTime != null) {
            log.info("访问接口主机:{}", uri.getHost());
            log.info("访问接口端口号:{}", uri.getPort());
            log.info("放文件接口URL:{}", uri.getPath());
            log.info("访问接口的URL参数:{}", uri.getQuery());
            log.info("访问时长:{}ms", (System.currentTimeMillis() - beginTime));
            log.info("============================================");
            System.out.println();
        }
    }));
}

这里因为是全局过滤器,所以并不需要进行单独配置yml。
3、测试(访问任意路由查看日志是否输出)

自定义路由过滤器 ------ 输出指定的路由所执行的时间、设置路由必填参数

1、创建自定义路由过滤器类,注意类名~~GatewayFilterFactory

案例一:日志输出指定的路由所执行的时间

java 复制代码
/*注入到Spring容器中,之后会将所有注入到容器中的过滤器进行合并,形成过滤器列表*/
@Component
@Slf4j
/*自定义路由过滤器,指定某一个路由执行的过滤器,无参数的写法(只需要编写apply方法即可,注意config的类型)*/
/*注意命名规范:~GatewayFilterFactory*/
public class MyGatewayFilterFactory extends AbstractGatewayFilterFactory<Object> {
    static final String BEGIN_TIME = "begin_time";

    @Override
    public GatewayFilter apply(Object config) {
        System.out.println("进入了自定义网关过滤器MyGatewayFilterFactory,status:" + config);
        return new GatewayFilter() {
            @Override
            public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
                exchange.getAttributes().put(BEGIN_TIME, System.currentTimeMillis());
                return chain.filter(exchange).then(Mono.fromRunnable(() -> {
                    Long beginTime = exchange.getAttribute(BEGIN_TIME);
                    URI uri = exchange.getRequest().getURI();
                    if (beginTime != null) {
                        log.info("------------------我是路由过滤器----------------------");
                        log.info("访问接口主机:{}", uri.getHost());
                        log.info("访问接口端口号:{}", uri.getPort());
                        log.info("放文件接口URL:{}", uri.getPath());
                        log.info("访问接口的URL参数:{}", uri.getQuery());
                        log.info("访问时长:{}ms", (System.currentTimeMillis() - beginTime));
                        log.info("============================================");
                        System.out.println();
                    }
                }));
            }
        };
    }
}

案例二:设置路由必填参数

java 复制代码
@Component
/*定义路由有参过滤器*/
public class MustQueryGatewayFilterFactory extends AbstractGatewayFilterFactory<MustQueryGatewayFilterFactory.Config> {
    /*初始化*/
    public MustQueryGatewayFilterFactory() {
        super(MustQueryGatewayFilterFactory.Config.class);
    }

    /*用来配置快捷访问方式的传参的位置*/
    @Override
    public List<String> shortcutFieldOrder() {
        return Arrays.asList("status");
    }

    /*定义一个内部类用来存放接受的参数,同时也是继承AbstractGatewayFilterFactory类的泛型*/
    public static class Config {
        @Getter
        @Setter
        private String status;//设定一个状态值/标志位,它等于多少,匹配和才可以访问
    }

    /**
     * 写过滤器逻辑的方法
     * @param config 传递参数值
     * @return
     */
    @Override
    public GatewayFilter apply(MustQueryGatewayFilterFactory.Config config) {
        return new GatewayFilter() {
            @Override
            public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
                ServerHttpRequest request = exchange.getRequest();
                /*动态的将配置的参数设为传必传参数的name*/
                System.out.println("进入了自定义网关过滤器MyGatewayFilterFactory,status:" + config.getStatus());
                if (request.getQueryParams().containsKey(config.getStatus())) {
                    return chain.filter(exchange);
                } else {
                    exchange.getResponse().setStatusCode(HttpStatus.BAD_REQUEST);
                    return exchange.getResponse().setComplete();
                }
            }
        };
    }
}
//单一内置过滤器GatewayFilter

2、配置yml

在指定的路由上添加路由过滤器

yml 复制代码
server:
  port: 9567
spring:
  application:
    name: cloud-gateway
  cloud:
    consul:
      port: 8500 #consul的端口号
      host: localhost #consul的网址
      discovery:
        service-name: ${spring.application.name} #配置服务名
    gateway:
      routes:
        - id: gateway_router_1
          uri: lb://cloud-payment-service
          predicates:
            - Path=/pay/gateway/info/**              # 断言,路径相匹配的进行路由
          filters:
            - My #配置日志输出指定的路由所执行的时间的路由过滤器

        - id: gateway_router_2
          uri: lb://cloud-payment-service
          predicates:
            - Path=/pay/gateway/get/**
          filters:
            - MustQuery=chyb #配置必传参数名,否则访问不了

测试

案例一:


案例二:

源码分析:自定义过滤器 ------ apply()

我们以这段代码为例:

java 复制代码
@Override
public GatewayFilter apply(Object config) {
    System.out.println("进入了自定义网关过滤器MyGatewayFilterFactory,status:" + config);
    return new GatewayFilter() {
        @Override
        public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
            exchange.getAttributes().put(BEGIN_TIME, System.currentTimeMillis());
            return chain.filter(exchange).then(Mono.fromRunnable(() -> {
//                .....
            }));
        }
    };
}

这里我们把重心放在:public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {}

这里的第一个参数exchange:里面封装了三个参数(上面自定义全局过滤器注释有,所以不多讲 getRequest、getResponse、getAttributes)

第二个参数chain:主要是用来进行放行操作的,而这里是怎么放行的我们就从过滤链列表构建过滤器链的执行流程

过滤链列表的构建:

过滤链列表由来:全局过滤器列表路由过滤器列表 动态合并而成

  1. 全局过滤器列表

    是根据Spring容器扫描所有的 GlobalFilter 的实现类,

    通过 @Order 注解或 Ordered 接口的 getOrder()

    方法对全局过滤器进行排序,形成全局过滤器列表。从而形成的一个列表。

  2. 路由过滤器列表

    从配置文件(如 YAML)或代码中解析路由规则,提取每个路由的 filters 列表。

    这里还有一个点要注意:配置文件中的过滤器名称(如 AddRequestHeader),会默认转化为 GatewayFilter实例

    (也就是我们自定义的路由过滤器类),

    然后根据所有的 GatewayFilter实例添加到 路由过滤器列表

  3. 动态合并而成 :

    当请求到达时,网关根据当前请求匹配的路由,将 全局过滤器列表路由过滤器列表 合并。

    路由过滤器按配置顺序追加到全局过滤器之后,便形成了过滤链。排序规则[全局过滤器按 @Order 排序(值越小优先级越高)]

    源码示例(合并):FilteringWebHandler类中的handle()

java 复制代码
public Mono<Void> handle(ServerWebExchange exchange) {
    // 1. 获取全局过滤器和路由过滤器
    List<GatewayFilter> globalFilters = this.globalFilters;
    List<GatewayFilter> routeFilters = route.getFilters();
    
    // 2. 合并过滤器列表
    List<GatewayFilter> combined = new ArrayList<>(globalFilters);
    combined.addAll(routeFilters);
    
    // 3. 排序(全局过滤器按 Order,路由过滤器按配置顺序)
    AnnotationAwareOrderComparator.sort(combined);
    
    // 4. 创建过滤器链
    return new DefaultGatewayFilterChain(combined).filter(exchange);
}

这就是形成过滤器列表的大致过程。

过滤器链的执行流程
  1. 链式调用模型:

    1. 责任链模式 :通过 DefaultGatewayFilterChain (
      这个类是FilteringWebHandler类的内部类,要想找的话可以直接搜索这个类) 递归调用每个过滤器,直到所有过滤器执行完毕。
    2. 响应式编程:使用 Mono 和 .then() 实现异步非阻塞执行。(这里不懂的话可以看一下SpringBoot的响应式编程)
  2. 核心代码展示:
    这里可以找到 chain.filter(exchange)【我们自定义过滤器中所返回的那个方法】的实现类 FilteringWebHandler
    找到内部类 DefaultGatewayFilterChain

    核心代码如下:

java 复制代码
public class DefaultGatewayFilterChain implements GatewayFilterChain {
    private final List<GatewayFilter> filters;
    private final int index; // 当前执行过滤器的索引
    @Override
    public Mono<Void> filter(ServerWebExchange exchange) {
        return Mono.defer(() -> {
            if (index < filters.size()) {
                // 1. 获取当前过滤器
                GatewayFilter filter = filters.get(index);
                // 2. 创建下一个链(index+1)
                GatewayFilterChain nextChain = new DefaultGatewayFilterChain(this, index + 1);
                // 3. 执行当前过滤器,传递下一个链
                return filter.filter(exchange, nextChain);
            } else {
                // 4. 所有过滤器执行完毕
                return Mono.empty();
            }
        });
    }
}
  1. 执行顺序示例
    假设合并后的过滤器列表为:[GlobalFilterA, GlobalFilterB, RouteFilter1, RouteFilter2]

执行流程:

  1. GlobalFilterA 的前置逻辑 → 调用 chain.filter()
  2. GlobalFilterB 的前置逻辑 → 调用 chain.filter()
  3. RouteFilter1 的逻辑 → 调用 chain.filter()
  4. RouteFilter2 的逻辑 → 调用 chain.filter()
  5. 路由到目标服务 → 返回响应
  6. RouteFilter2 的后置逻辑(通过 .then())
  7. RouteFilter1 的后置逻辑
  8. GlobalFilterB 的后置逻辑
  9. GlobalFilterA 的后置逻辑。
    这里可以自己打几个断点到DefaultGatewayFilterChain类中一步一步的查看源码查看,会更清晰一些

上述大部分代码以上传到gitee:https://gitee.com/banhuayue/springCloud-Alibaba-code.git

笔记参考来自尚硅谷

相关推荐
LUCIAZZZ7 小时前
ElasticSearch基本概念
java·大数据·elasticsearch·搜索引擎·中间件·操作系统
CodeWithMe15 小时前
【C/C++】RPC与线程间通信:高效设计的关键选择
c++·中间件
柚个朵朵18 小时前
Springclound常用五大组件及其使用原理
spring cloud·hystrix·eureka·ribbon·gateway·feign
逸Y 仙X18 小时前
适合java程序员的Kafka消息中间件实战
分布式·中间件·kafka·消息中间件
代码哈士奇1 天前
认识中间件-以及两个简单的示例
后端·中间件·typescript·nodejs·nest
曹朋羽2 天前
spring cloud gateway 断言(Predicates)与过滤器(filters)
spring cloud·gateway
言之。2 天前
【Django】中间件
中间件·django·sqlite
hzj62 天前
GateWay使用
java·spring·gateway
[email protected]2 天前
ASP.NET Core 中间件
后端·中间件·asp.net·.netcore