【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编程方式。

相关推荐
盛派网络小助手2 小时前
微信 SDK 更新 Sample,NCF 文档和模板更新,更多更新日志,欢迎解锁
开发语言·人工智能·后端·架构·c#
∝请叫*我简单先生2 小时前
java如何使用poi-tl在word模板里渲染多张图片
java·后端·poi-tl
zquwei3 小时前
SpringCloudGateway+Nacos注册与转发Netty+WebSocket
java·网络·分布式·后端·websocket·网络协议·spring
dessler3 小时前
Docker-run命令详细讲解
linux·运维·后端·docker
Q_19284999064 小时前
基于Spring Boot的九州美食城商户一体化系统
java·spring boot·后端
ZSYP-S4 小时前
Day 15:Spring 框架基础
java·开发语言·数据结构·后端·spring
Yuan_o_5 小时前
Linux 基本使用和程序部署
java·linux·运维·服务器·数据库·后端
程序员一诺5 小时前
【Python使用】嘿马python高级进阶全体系教程第10篇:静态Web服务器-返回固定页面数据,1. 开发自己的静态Web服务器【附代码文档】
后端·python
DT辰白6 小时前
如何解决基于 Redis 的网关鉴权导致的 RESTful API 拦截问题?
后端·微服务·架构