【SpringBoot系列】Spring cloud Gateway 动态路由到底有多简单

1、概念解析

docs.spring.io/spring-clou...

Spring Cloud Gateway

1️⃣ 路由(route):路由是网关最基础的部分,路由信息由一个ID,一个目的URL、一组断言工厂和一 组Filter组成。如果断言为真,则说明请求URL和配置的路由匹配。

2️⃣ 断言(Predicate):Java8中的断言函数,Spring Cloud Gateway中的断言函数输入类型是 Spring5.0框架中的ServerWebExchange。Spring Cloud Gateway中的断言函数允许开发者去定义匹配 来自http Request中的任何信息,比如请求头和参数等。

3️⃣ 过滤器(Filter):一个标准的Spring WebFilter,Spring Cloud Gateway中的Filter分为两种类型: Gateway Filter和Global Filter。过滤器Filter可以对请求和响应进行处理。

2、hello word

2.1 加入spring cloud gateway依赖

创建springboot项目,加入spring cloud gateway 依赖

pom文件如下,注:有用的没几行,就是加入gateway的依赖

js 复制代码
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>3.1.2</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.xin</groupId>
    <artifactId>gateway-test</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>gateway-test</name>
    <description>gateway-test</description>
    <properties>
        <java.version>17</java.version>
        <spring-cloud.version>2022.0.4</spring-cloud.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-gateway</artifactId>
        </dependency>
 
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>
    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>${spring-cloud.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>
 
    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <configuration>
                    <excludes>
                        <exclude>
                            <groupId>org.projectlombok</groupId>
                            <artifactId>lombok</artifactId>
                        </exclude>
                    </excludes>
                </configuration>
            </plugin>
        </plugins>
    </build>
 
</project>

2.2 配置路由

不喜欢properties的配置,结构不明显,所以一般使用 application.yml

配置如下

js 复制代码
spring:
  cloud:
    gateway:
      routes:
        - id: after_route
          uri: http://192.168.3.8:29001
          predicates:
            - Path=/road/**
          filters:
            - StripPrefix=1

这里加入了一个路由的配置:

路由的服务是 http://192.168.3.8:29001

路由的逻辑是路径中以road开始的路径

过滤器是StripPrefix ,会去除路径中的第一个,也就是road ,最终的实际路径为 uir+ path后面的***

2.3 测试

在浏览器中输入

http://localhost:8080/road/magic/web/index.html 可以看到结果

3、自定义filter

3.1 filter的分类

Gateway分为全局过滤器GlobalFilter和局部过滤器GatewayFilter,局部过滤器只有在路由配置中针对路由ID配置了才会使用,对应配置中的

filters属性。

3.2 实现局部过滤器

局部过滤器相当于对于每个路由会有一个实例,Gateway路由过滤器通过GatewayFilterFactory这个工厂类生成。

注意点:

1、在配置时会去掉FilterFactory 比如 StripPrefixGatewayFilterFactory 配置时使用StripPrefixGatewayFilterFactory

2、config接收配置的参数

js 复制代码
@Component
public class TokenCheckGatewayFilterFactory extends AbstractGatewayFilterFactory<TokenCheckGatewayFilterFactory.Config> {
 
    public TokenCheckGatewayFilterFactory() {
        super(Config.class);
    }
 
    @Override
    public GatewayFilter apply(Config config) {
        return (exchange, chain) -> {
            String token = exchange.getRequest().getHeaders().getFirst("username");
 
            // 是否登录成功状态
            if (token != null) {
                // 合法
                // 将用户id作为参数传递下去
                return chain.filter(exchange);
            }
 
            //不合法(响应未登录的异常)
            ServerHttpResponse response = exchange.getResponse();
            //设置headers
            HttpHeaders httpHeaders = response.getHeaders();
            httpHeaders.add("Content-Type", "application/json; charset=UTF-8");
            httpHeaders.add("Cache-Control", "no-store, no-cache, must-revalidate, max-age=0");
            //设置body
            String warningStr = "未登录或登录超时";
            DataBuffer bodyDataBuffer = response.bufferFactory().wrap(warningStr.getBytes());
 
            return response.writeWith(Mono.just(bodyDataBuffer));
        };
    }
    static class Config {}
}

3.3 自定义全局过滤器

全局过滤器是所有的请求都会经过,全局只有一个实例,需要实现GlobalFilter和 Ordered 接口

看下一个黑名单的实现。

js 复制代码
@Component
@Slf4j
//  TODO 改为动态判断
public class BlackFilter implements GlobalFilter, Ordered {
    private  static final List<String> blackList=new ArrayList<>();
    static {
        blackList.add("0:0:0:0:0:0:0:1");//模拟本机ip地址
    }
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        ServerHttpRequest request = exchange.getRequest();
        ServerHttpResponse response =exchange.getResponse();
        String clientIp = request.getRemoteAddress().getHostString();
        if (blackList.contains(clientIp)){
            response.setStatusCode(HttpStatus.UNAUTHORIZED);
            log.error(clientIp+"在黑名单中,拒绝访问");
            String data = "request be denied";
            DataBuffer wrap = response.bufferFactory().wrap(data.getBytes());
            return response.writeWith(Mono.just(wrap));
        }
        return chain.filter(exchange);
    }
 
    @Override
    public int getOrder() {
        return 0;
    }
}

4、自定义router

4.1 原理

4.1.1RouteDefinition 和Route的区别

RouteDefinition 是对配置的读取的对象,纯字符串配置

Route 是将定义实例化之后的对象,比如RouteDefinition 的filter 和 Predicate的实例化对象

4.1.2 路由的加载

先看下类图 RouteLocator 接口用于获取路由信息,其有三个实现类

  • RouteDefinitionRouteLocator
  • CompositeRouteLocator
  • CachingRouteLocator 最终使用的是CachingRouteLocator,它包装了CompositeRouteLocator,而CompositeRouteLocator则组合了所有的RouteDefinitionRouteLocator。

RouteDefinitionRouteLocator 与 RouteDefinitionLocator比较容易混淆,前者是一个RouteLocator(路由定位器),后者是一个RouteDefinitionLocator(路由定义定位器),前者的 RouteDefinitionRouteLocator 主要从后者获取路由定义信息。

4.2 实现自定义路由

从原理中可以看到,我们只要实现自己的RouteDefinitionRouteLocator 就可以了 ,Gateway会自动加载路由

下面是项目中实现的从数据库中加载路由的代码

js 复制代码
@Component
public class MysqlRouteLocator implements RouteDefinitionLocator {
    @Resource
    GatewayService gatewayService;
    @Override
    public Flux<RouteDefinition> getRouteDefinitions() {
        List<RouteDefinition> definitionList = new ArrayList<>();
        try {
            definitionList = gatewayService.loadRouteDefinition();
        } catch (URISyntaxException e) {
            throw new RuntimeException(e);
        }
        return Flux.fromIterable(definitionList);
    }
}

4.3 Gateway的执行流程

Gateway的执行流程如下:

  • 1:Gateway的客户端回向Spring Cloud Gateway发起请求,请求首先会被HttpWebHandlerAdapter进行提取组装成网关的上下文,然后网关的上下文会传递到DispatcherHandler。
  • 2:DispatcherHandler是所有请求的分发处理器,DispatcherHandler主要负责分发请求对应的处理器,比如将请求分发到对应RoutePredicateHandlerMapping(路由断言处理器映射器)。
  • 3:路由断言处理映射器主要用于路由的查找,以及找到路由后返回对应的FilteringWebHandler。
  • 4:FilteringWebHandler主要负责组装Filter链表并调用Filter执行一系列Filter处理,然后把请求转到后端对应的代理服务处理,处理完毕后,将Response返回到Gateway客户端。
  • 在Filter链中,通过虚线分割Filter的原因是,过滤器可以在转发请求之前处理或者接收到被代理服务的返回结果之后处理。所有的Pre类型的Filter执行完毕之后,才会转发请求到被代理的服务处理。被代理的服务把所有请求完毕之后,才会执行Post类型的过滤器。

5、内置filter 和Predicate

6、一些点

1、RefreshRoutesEvent 发布此事件会将各个routerDefinitionLocator加载的路由放入cache中

2、可以使用Nacos或者其他的服务发现

3、在配置中使用lb://service_name 可以启用负载均衡

7、总结

spring cloud gateway 使用了webflux ,在编程的时候还是有一些不适应,但是好在gateway提供了足够多的Predicate和Filter

在日常的使用中足够了,但是定制还是蛮费劲的,不适应reactor编程方式。

相关推荐
wn53110 分钟前
【Go - 类型断言】
服务器·开发语言·后端·golang
希冀12334 分钟前
【操作系统】1.2操作系统的发展与分类
后端
GoppViper1 小时前
golang学习笔记29——golang 中如何将 GitHub 最新提交的版本设置为 v1.0.0
笔记·git·后端·学习·golang·github·源代码管理
爱上语文2 小时前
Springboot的三层架构
java·开发语言·spring boot·后端·spring
serve the people2 小时前
springboot 单独新建一个文件实时写数据,当文件大于100M时按照日期时间做文件名进行归档
java·spring boot·后端
罗政8 小时前
[附源码]超简洁个人博客网站搭建+SpringBoot+Vue前后端分离
vue.js·spring boot·后端
拾光师9 小时前
spring获取当前request
java·后端·spring
Java小白笔记11 小时前
关于使用Mybatis-Plus 自动填充功能失效问题
spring boot·后端·mybatis
JOJO___12 小时前
Spring IoC 配置类 总结
java·后端·spring·java-ee
白总Server13 小时前
MySQL在大数据场景应用
大数据·开发语言·数据库·后端·mysql·golang·php