《Spring Cloud Gateway 快速入门:从路由到自定义 Filter 的完整教程》

1.网关介绍

在前面的学习中,我们通过Eureka和Nacos解决了辅助注册,使用Spring Cloud LoadBalance解决了负载均衡的问题,使用OpenFeign解决了远程调用的问题。

但是当前的所有微服务的接口都是直接对外暴露的,外部是可以直接访问的,为了保证对外服务的安全性,服务端实现的微服务接口通常都带有一定的权限校验机制,由于我们使用了微服务,原本一个应用被拆成了多个模块,所以程序员不得不多次实现校验逻辑,当这套校验逻辑需要修改时,程序员需要修改多个应用,加重了开发人员的负担

针对以上问题,一个常用的解决方案是使用API网关

API网关也简称为网关,网关通常也是单独一个服务,通常是后端的唯一入口,网关的设计模式主要是门面模式,网关就是类似于整个微服务的架构,所有的外部客户端访问后端服务时,都必须要经过网关来进行调度和过滤

网关的核心功能:

1.权限控制: 作为微服务的入口,对用户的权限进行校验,如果校验失败则进行拦截

2.动态路由:一切请求先经过网关,但网关不进行业务处理,而是根据某种规则,将请求妆发到某一个微服务

3.负载均衡:当路由的目标服务有多个时,还需要进行负载均衡

4.限流:请求流量过高时,按照网关中的配置对微服务能够接受的流量进行放行,避免服务压力过大

2.常见的网关实现

业界常用的网关方式众多,技术方案成熟,开源产品丰富,如Nginx、Kong、Zuul、Spring Cloud Gateway等。以下介绍两种常见网关方案:

  • ​Zuul​:Netflix开源的API网关组件,是Spring Cloud Netflix子项目核心组件之一,可与Eureka、Ribbon、Hystrix等配合使用。在Spring Cloud Finchley正式版前,Spring Cloud推荐使用Netflix的Zuul(指Zuul 1.X)。但Netflix在2018年宣布部分组件进入维护状态,不再开发新特性,其中包含Zuul。
  • ​Spring Cloud Gateway​:Spring Cloud全新的API网关项目,基于Spring + Spring Boot等技术开发,旨在替代Zuul,为微服务架构提供简单有效的请求转发途径,并提供安全性、监控/指标和弹性等横切关注点。

3.Spring Cloud Gateway的快速上手

注意:这里我是基于我前面的代码来使用的,各位读者视自己的情况而定

1.创建一个网关服务

2.引入网关依赖

由于网关设计到Nacos上的服务,负载均衡,所以还要额外将这两个依赖引入网关工程的pom文件中,所以,在网关工程中的pom文件引入下面的依赖

XML 复制代码
        <!--网关依赖-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-gateway</artifactId>
        </dependency>
        <!--基于nacos实现服务发现依赖-->
        <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-loadbalancer</artifactId>
        </dependency>

3.编写启动类

java 复制代码
@SpringBootApplication
public class GatewayApplication {
    public static void main(String[] args) {
        SpringApplication.run(GatewayApplication.class,args);
    }
}

4.添加Gateway的路由配置

创建appication.yml文件,添加响应的配置,添加的配置有网关的端口号,Nacos服务发现的配置项和网关对应的配置项,如一下配置

TypeScript 复制代码
server:
  port: 10030   #网关端口号,随便配置一个即可,只要端口号不冲突
spring:
  application:
    name: gateway
  cloud:
    nacos:
      discovery:
        server-addr: 8.134.130.95:10020 #Nacos服务发现的配置
    gateway:
      routes:
        - id: orders-service   #路由规则id, 随便起, 不重复即可
          uri: lb://orders-service/ #目标服务地址
          predicates:   #路由条件
            - Path=/order/**,/feign/**
        - id: product-service
          uri: lb://product-service/
          predicates:
            - Path=/product/**

配置字段说明:

  1. id:自定义路由,随便起,不重复即可

  2. uri:目标服务地址,支持普通uri和 lb://应用注册的服务名称 ,其中lb表示负载均衡,lb:// 方式表示从注册中心获取的服务地址

3.predicate:路由条件,根据匹配结果决定是否将请求转发到对应的微服务中,上述代码中,我们将符合Path规则的一切请求都有网关转发到uri参数指定的地址(也就是uri指定的注册在Nacos上的微服务)

5.测试网关

启动网关服务,并重启订单服务和产品服务

1.通过网关访问product-service服务,url和predicates的Path条件匹配成功,则成功访问product-service服务

不匹配,则会报404,如下图

2.通过网关访问订单服务,url匹配成功的话,就会成功访问订单服务

3.访问订单服务中的其他服务,成功访问

4.Predicate

Predicate是Java8提供的一个函数式编程接口,它用于接收一个参数并返回一个布尔值,用于请求校验,请求参数的校验,Prdicate里面的核心方法就是test()方法,如下面截图

1.代码演示---判断一个字符串是否是空字符串

1.定义一个Predicate,通过实现Predicate接口

java 复制代码
/**
 * 判断字符串是否为空
 * true ->空字符串
 * false -> 非空字符串
 */
public class StringPredicate implements Predicate<String> {

    @Override
    public boolean test(String s) {
        return s==null||s.isEmpty();
    }
    
}

2.编写测试方法:

java 复制代码
    @Test
    public void Test(){
        Predicate predicate=new StringPredicate();
        System.out.println(predicate.test(""));
        System.out.println(predicate.test("aa"));
    }

运行截图:

2.Predicate的其他写法

1.匿名内部类写法

代码演示:

java 复制代码
    /**
     * 匿名内部类
     */
    @Test
    public void Test2(){
        Predicate<String> predicate=new Predicate<String>() {
            @Override
            public boolean test(String s) {
                return s==null||s.isEmpty();
            }
        };
        System.out.println(predicate.test(""));
        System.out.println(predicate.test("aa"));
    }

运行截图

2.lambda表达式写法

代码演示:

java 复制代码
    /**
     * lambda表达式
     */
    @Test
    public void Test3(){
        Predicate<String> predicate= s -> s==null||s.isEmpty();
        System.out.println(predicate.test(""));
        System.out.println(predicate.test("aa"));
    }

运行截图:

3.Predicate中的其他方法

1.negate()方法

negate() 是用来对Predicate返回结果取反的方法,如下图

代码演示:

java 复制代码
    /**
     * 取反->negate()
     */
    @Test
    public void test(){
        Predicate<String> predicate= s -> s==null||s.isEmpty();
        System.out.println(predicate.negate().test(""));
        System.out.println(predicate.negate().test("aa"));
    }

运行截图:

2.and()方法

and()方法其实就是条件A&&条件B,即当前调用and()方法的predicate的test方法&&other的test方法

代码演示:

java 复制代码
    /**
     * and
     * 字符串不为空,且字符串有数字组成  "aa","bb"
     */
    @Test
    public void test2(){
        Predicate<String> predicate= s-> s==null||!s.isEmpty();
        Predicate<String> predicate1=s -> s.chars().allMatch(Character::isDigit);
        System.out.println(predicate.and(predicate1).test("12"));
        System.out.println(predicate.and(predicate1).test(""));
        System.out.println(predicate.and(predicate1).test("a1"));
    }

运行截图:

3.or()

or()方法其实就是条件A||条件B,即当前调用or()方法的predicate的test方法||other的test方法

代码演示:

java 复制代码
    /**
     * or
     * 判断字符串是不是aa或者bb
     */
    @Test
    public void test3(){
        Predicate<String> predicate1=s-> s.isEmpty();
        Predicate<String> predicate=s -> s.equals("aa");
        Predicate<String> predicate2=s -> s.equals("bb");
        System.out.println(predicate2.or(predicate1).test("bb"));
        System.out.println(predicate2.or(predicate1).test("cc"));
    }

运行截图:

5. Route Predicate Factories

Route Predicate Factories(路由断言工厂),在Spring Cloud Gateway中,Predicate提供了路由规则的匹配机制

在配置文件中写的断言规则只是字符串,这些字符串会被Route Predicate Factories读取并处理,转变为路由判断的条件

Spring Cloud Gateway默认提供了很多Route Predicate Factory,这些Predicate会分别匹配HTTP请求中的不同属性,并且多个Predicate会默认为and逻辑组合

1.通过时间匹配

Predicate支持设置一个时间,在请求转发的时候,可以通过判断是否在这个时间之前转发或者之后转发。比如现在我设置只有在2025年5月29号时,该请求才能转发请求,在这之前,不会将请求转发,我就可以添加一下配置:

Spring是通过ZoneDateTimela来对时间进行的对比,ZoneDateTime是Java8中日期时间功能里,用于表示带时区的日期与时间信息的类,ZoneDateTime支持通过时区来设置时间

添加完以上配置,重启网关服务,去调用订单服务,发现服务调用成功

如果添加一个Before配置,如下图

此时,重启网关服务,此时就会报404,因为此时访问时间已经在Before的条件的时间之后

还有很多匹配规则,可以去Spring官网查看,这里就不一 一说明了

6. Gateway Filter Factories(网关过滤器工厂)

Predicate决定了请求由哪一个路由处理,如果需要在请求处理前后添加一些逻辑,我们此时就需要用到Filter(过滤器)

Filter分为两种类型:Pre类型和Post类型

**Pre类型过滤器:**路由处理之前执行(请求转发到后端服务之前执行),在Pre类型过滤器中可以做身份验证和限流登操作

**Post类型过滤器:**请求执行完后完成,也就是将结果返回给客户端之前执行

Spring Cloud Gateway中内置了很多FIlter,用于拦截和链式处理web请求,比如权限校验,访问超时等设定

Spring Cloud Gateway从作用范围上,可以将Filter分为GatewayFilterGlobalFilter

**GatewayFilter:**应用到单个路由或者一个分组的路由上

**GlobalFilter:**应用到所有的路由上,也就是对所有的请求生效

6.1 GatewayFilter

GateFilter同Predicate类似,都是在配置文件中配置,每个过滤器的逻辑都是固定的

1.为当前请求添加参数---AddRequestParameter,添加下面的配置

  1. 为当前请求添加Header---AddRequestHeader

3.从当前请求中删除某一个Header信息---RemoveRequestHeader

4.为响应结果添加Header---AddResponseHeader

过程图:

5.从响应结果中删除某个Header---RemoveResponseHeader

此时通过Postman发现,p6方法中添加的Header信息已经被删除

6.默认过滤器---default-filters

添加一个filter并将其应用到所有路由,并配置了重传次数

订单服务

产品服务

6.2 GlobalFilter

GlobalFilter是Spring Cloud Gateway中的全局过滤器,它和Gateway的作用是相同的,但是GlobalFilter会应用到所有的路由请求上,全局过滤器通常用于实现与安全性,性能监控和日志记录等相关的全局功能

Spring Cloud Gateway内置的全局过滤器有很多,比如:

1.Gateway Metrics Filter:网关指标,提供监控指标

2.Forward Routing Filter:用于本地forword,请求不转发到下游服务器

3.LoadBalancer Client Filter:针对下游服务,实现负载均衡

1.快速上手---以Gateway Metrics Filter为例

在网关工程中添加下面的依赖:

XML 复制代码
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>

然后在网关工程中的配置文件中添加相关的配置,如下图

TypeScript 复制代码
spring:
  cloud:
    gateway:
      metrics:
        enabled: true
management:
  endpoints:
    web:
      exposure:
        include: "*"
  endpoint:
    health:
      show-details: always
    shutdown:
      enabled: true

重启网关工程,通过下面的链接去访问,将会显示所有监控的信息

链接:127.0.0.1:10030/actuator

2.过滤器的执行顺序

一个项目中,既有GatewayFilter,又有GlobalFilter,执行的先后顺序是什么呢?

请求路由后,网关会把当前项目中的GatewayFilter和GlobalFilter合并到一个过滤器链中,并进行排序,依次执行过滤器

每一个过滤器都必须指定一个int类型的order值,默认值为0,表示过滤的优先级,order值越小,优先级越高,执行顺序越靠前

1、Filter通过实现Order接口或者天剑@Order注解来指定Order值

2、Spring Cloud Gateway提供的Filter有Spring指定,用户也可以自定义Filter,由用户指定

3、当过滤器的Oorder值一样,会按照dafaultFilter>GatewayFlter>ClobalFilter的顺序来执行

7.自定义过滤器

Spring Cloud Gateway提供了过滤器的扩展功能,开发者可以根据实际业务来自定义过滤器,同样自定义过滤器也支持GatewayFilter和GlobalFilter两种

1.自定义GatewayFilter

自定义GatewayFilter需要去实现对应的接口GatewayFilterFactory,SpringBoot默认帮我们实现的抽象类是AbstractGatewayFilterFactory,可以直接使用,由于我们还要声明该过滤器的优先级,所以还要去实现Ordered接口,分别去重写里面的方法

其次,对于过滤器来说,所有的路由配置都是通过配置文件来实现的,配置文件中写的是一些字符串,在应用启动时,Gateway会将这些字符串解析为内部数据结构,当请求达到网关时,Gateway会从解析后的配置提取参数,并传给对应的Filter实现类,所以要实现一个自定义GAtewayFilter,我们还要创建一个对应类型的数据类,作为自定义Gateway的参数类型

完整实现:

对应的参数类---CustomConfig

java 复制代码
@Data
public class CustomConfig {
    private String name;
}

自定义GAtewayFilter的实现代码---CustomGatewayFilterFactory

java 复制代码
@Slf4j
@Component
public class CustomGatewayFilterFactory extends AbstractGatewayFilterFactory<CustomConfig> implements Ordered {

    public CustomGatewayFilterFactory() {
        super(CustomConfig.class);
    }

    @Override
    public GatewayFilter apply(CustomConfig config) {
        return new GatewayFilter() {
            @Override
            public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
                //Pre -> 执行请求 -> Post
                log.info("Pre Filter,config:{}",config);//pre类型过滤器代码逻辑
                return chain.filter(exchange).then(Mono.fromRunnable(()->{
                    log.info("Post Filter,config:{}",config);
                }));
            }
        };
    }

    @Override
    public int getOrder() {
        return Ordered.LOWEST_PRECEDENCE;
    }
}

代码解析:

1.类名统一以GatewayFilter结尾,因为默认情况下,过滤器的name会采用该定义类的前缀,以上面的自定义GateawayFilter为例,此时自定义GateawayFilter的name就是Custom

2.apply方法中同时包含Pre和Post,then方法就是Post类型的逻辑

3.CustomConfig是一个配置类,也yml配置对应

4.CustomGatewayFilter需要交给Spring管理,所以需要添加@Component注解

5.getOder表示该过滤器的优先级,值越大,优先级越低

最后去配置文件中添加相关配置

测试:

2.自定义GlobalFilter

GlobalFilter的实现比较简单,它不需要额外的配置,只需要实现GlobalFilter接口即可,自动会过滤所有的Filter

代码实现:

java 复制代码
@Slf4j
@Component
public class CustomGlobalFilter implements GlobalFilter,Ordered {

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        log.info("Global Filter Pre...");
        return chain.filter(exchange).then(Mono.fromRunnable(()->{
            log.info("Global Filter Post...");
        }));
    }

    @Override
    public int getOrder() {
        return Ordered.LOWEST_PRECEDENCE;
    }
}

测试:

相关推荐
来杯@Java13 分钟前
图书管理系统(基于springboot+vue前后端分离的项目)计算机毕业设计java
java·spring boot·spring·vue·毕业设计·mybatis·课程设计
卷毛的技术笔记1 小时前
告别硬编码!Spring AI Alibaba 实现 AI Agent 智能工具调用(Tool Calling)
java·人工智能·后端·python·spring·ai编程
编程大师哥1 小时前
匿名函数 lambda + 高阶函数
java·python·算法
isyangli_blog1 小时前
OpenDayLight (Carbon 版本) 启动与组件安装
开发语言·php
vb2008111 小时前
FastAPI APIRouter
开发语言·python
Benszen1 小时前
KVM虚拟化解决方案
开发语言·perl
会编程的土豆1 小时前
Go 语言反射(Reflection)详解
开发语言·后端·golang
東雪木1 小时前
多线程与并发编程 专属复习笔记
java·开发语言·笔记·java面试
adrninistrat0r1 小时前
Java调用链MCP分析工具
java·python·ai编程
杨充2 小时前
1.3 浮点型数据设计灵魂
开发语言·python·算法