SpringCloud Gateway动态转发后端服务实现过程讲解

 更新时间:2023年03月28日 10:06:20   作者:QiHY  
这篇文章主要介绍了SpringCloud Gateway动态转发后端服务实现过程,简单的路由转发可以通过SpringCloudGateway的配置文件实现,在一些业务场景种,会需要动态替换路由配置中的后端服务地址,单纯靠配置文件无法满足这种需求

前言

API网关的核心功能是统一流量入口,实现路由转发,SpringCloudGateway是API网关开发的技术之一,此外比较流行的还有Kong和ApiSix,这2个都是基于OpenResty技术栈。

简单的路由转发可以通过SpringCloudGateway的配置文件实现,在一些业务场景种,会需要动态替换路由配置中的后端服务地址,单纯靠配置文件无法满足这种需求。

本文介绍一种将路由配置保存到数据库中,可以根据接口请求的特定条件,从数据库中动态读取后端服务地址,实现灵活转发。

具体的代码参照 示例项目 https://github.com/qihaiyan/springcamp/tree/master/spring-cloud-gateway

一、概述

通过把SpringCloudGateway的相关路由配置规则保存到数据库中,可以动态的灵活调整路由。在本文的实现中,我们通过请求header中的特定值,动态选择对应的后端服务地址。

二、项目中加入依赖

在项目的gradle中增加依赖关系。

build.gradle:

plugins {
    id 'org.springframework.boot' version '3.0.2'
    id 'io.spring.dependency-management' version '1.1.0'
    id 'java'
}

group = 'cn.springcamp'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '17'

configurations {
    compileOnly {
        extendsFrom annotationProcessor
    }
    testCompileOnly {
        extendsFrom testAnnotationProcessor
    }
}

repositories {
    mavenCentral()
}

dependencies {
    implementation "org.springframework.boot:spring-boot-starter-json"
    implementation 'org.springframework.boot:spring-boot-starter-validation'
    implementation 'org.springframework.boot:spring-boot-starter-data-r2dbc'
    implementation 'org.springframework.cloud:spring-cloud-starter-gateway'
    runtimeOnly 'com.h2database:h2'
    runtimeOnly 'io.r2dbc:r2dbc-h2'
    annotationProcessor 'org.projectlombok:lombok'
    testAnnotationProcessor 'org.projectlombok:lombok'
    testImplementation "org.springframework.boot:spring-boot-starter-test"
    testImplementation 'org.junit.vintage:junit-vintage-engine'
    testImplementation 'io.projectreactor:reactor-test'
    testImplementation 'com.h2database:h2'
    testImplementation 'io.r2dbc:r2dbc-h2'
    testImplementation 'org.junit.vintage:junit-vintage-engine'
}

dependencyManagement {
    imports {
        mavenBom "org.springframework.cloud:spring-cloud-dependencies:2022.0.1"
    }
}

test {
    useJUnitPlatform()
}

由于SpringCloudGateway基于SpringWebFlux技术构建,所以依赖中的数据库配置需要使用r2dbc 。

三、配置文件

示例程序首选通过配置文件对路由进行基本配置,配置文件代码:

spring:
  r2dbc:
    url: r2dbc:h2:mem:///testdb?options=DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE
    username: sa
    password:
  cloud:
    gateway:
      routes:
        - id: routeOne
          predicates:
            - Path=/route1/**
          uri: no://op
          filters:
            - UriHostPlaceholderFilter=10001
        - id: routeTwo
          predicates:
            - Path=/route2/**
          uri: no://op
          filters:
            - UriHostPlaceholderFilter=10001

配置文件中配置了2个路由,对应的接口地址路径分别是 /route1/**Path=/route2/**,路径中的 ***表示模糊匹配,只要是以 /route1/为前缀的路径都可以被访问到。

后端服务地址配置了一个无意的地址: uri: no://op,因为我们的处理逻辑会通过从数据库中读取配置来动态替换后端服务地址。

四、动态路由数据存储格式

我们通过 ROUTE_FILTER_ENTITY这个数据库表来存储接口后端服务配置数据。表结构为:

CREATE TABLE "ROUTE_FILTER_ENTITY"
(
   id VARCHAR(255) PRIMARY KEY,
   route_id VARCHAR(255),  -- 路由ID,对应配置文件中的 ```id```配置项
   code VARCHAR(255), -- 接口请求header中的code参数的值
   url VARCHAR(255) -- 后端服务地址
);

当客户端访问 /route1/test接口时,根据配置文件的路由配置,SpringCloudGateway 会命中 id: routeOne这个路由规则,这个规则对应的后端服务地址是 uri: no://op,并不是我们期望的真实后端服务地址。

因此,我们需要读取到真实的后端服务地址,并将请求转发到这个地址。跟据 routeId 和 接口请求header中的code参数的值,就可以从 ROUTE_FILTER_ENTITY 表中查到对应的后端服务地址 url这个字段的值。

我们已经读取到了后端服务地址,还需要将请求转发到这个地址,下面介绍转发的方法。

五、后端服务动态转发

动态转发通过自定义 filter 的方式实现,自定义 filter 代码如下:

@Component
public class UriHostPlaceholderFilter extends AbstractGatewayFilterFactory<UriHostPlaceholderFilter.Config> {
    @Autowired
    private RouteFilterRepository routeFilterRepository;
    public UriHostPlaceholderFilter() {
        super(Config.class);
    }
    @Override
    public List<String> shortcutFieldOrder() {
        return Collections.singletonList("order");
    }
    @Override
    public GatewayFilter apply(Config config) {
        return new OrderedGatewayFilter((exchange, chain) -> {
            String code = exchange.getRequest().getHeaders().getOrDefault("code", new ArrayList<>()).stream().findFirst().orElse("");
            String routeId = exchange.getAttribute(GATEWAY_PREDICATE_MATCHED_PATH_ROUTE_ID_ATTR);
            if (StringUtils.hasText(code)) {
                String newurl;
                try {
                    newurl = routeFilterRepository.findByRouteIdAndCode(routeId, code).toFuture().get().getUrl();
                } catch (InterruptedException | ExecutionException e) {
                    throw new RuntimeException(e);
                }
                if (StringUtils.hasText(exchange.getRequest().getURI().getQuery())) {
                    newurl = newurl + "?" + exchange.getRequest().getURI().getQuery();
                }
                URI newUri = null;
                try {
                    newUri = new URI(newurl);
                } catch (URISyntaxException e) {
                    log.error("uri error", e);
                }
                exchange.getAttributes().put(GATEWAY_REQUEST_URL_ATTR, newUri);
            }
            return chain.filter(exchange);
        }, config.getOrder());
    }
    @Data
    @NoArgsConstructor
    public static class Config {
        private int order;
        public Config(int order) {
            this.order = order;
        }
    }
}

通过扩展 AbstractGatewayFilterFactory 类,我们自定义了 UriHostPlaceholderFilter 这个 filter 。

代码的核心逻辑在 apply 方法中。

首先通过 String code = exchange.getRequest().getHeaders().getOrDefault("code", new ArrayList<>()).stream().findFirst().orElse("")可以获取到接口请求 header 中 code 这个参数的值。

再通过 String routeId = exchange.getAttribute(GATEWAY_PREDICATE_MATCHED_PATH_ROUTE_ID_ATTR)可以获取到 routeId 。

最后通过 newurl = routeFilterRepository.findByRouteIdAndCode(routeId, code).toFuture().get().getUrl()就可以从数据库中读取到配置好的后端服务地址。

拿到后端服务地址后, 通过调用 exchange.getAttributes().put(GATEWAY_REQUEST_URL_ATTR, newUri);将请求转发到对应的地址。

六、单元测试

在单元测试代码中,我们预置了一条后端服务动态配置数据:

insert into ROUTE_FILTER_ENTITY values('1','routeOne','alpha','http://httpbin.org/anything')

然后模拟请求 /route1/test?a=test这个接口,根据我们的配置,请求会被转发到 http://httpbin.org/anything

执行单元测试后,可以从日志中发现,接口返回的数据是 http://httpbin.org/anything 这个后端服务返回的数据。

当我们希望调整后端服务地址时,只需要把 ROUTE_FILTER_ENTITY 表中的这条配置数据中的 url 字段改成其它的任何服务地址即可,大大增加了程序的灵活度。

到此这篇关于SpringCloud Gateway动态转发后端服务实现过程讲解的文章就介绍到这了,更多相关SpringCloud Gateway动态转发内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • 如何使用HttpClient发送java对象到服务器

    如何使用HttpClient发送java对象到服务器

    这篇文章主要介绍了如何使用HttpClient发送java对象到服务器,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2019-11-11
  • java 简单的计算器程序实例代码

    java 简单的计算器程序实例代码

    这篇文章主要介绍了java 简单的计算器程序实例代码的相关资料,需要的朋友可以参考下
    2017-06-06
  • Java调用setStroke()方法设置笔画属性的语法

    Java调用setStroke()方法设置笔画属性的语法

    这篇文章主要介绍了Java调用setStroke()方法设置笔画属性的语法,如何改变线条的粗细、虚实和定义线段端点的形状、风格等,需要的朋友可以参考下
    2017-09-09
  • 浅析Spring基于注解的AOP

    浅析Spring基于注解的AOP

    Spring是一个广泛应用的框架,SpringAOP则是Spring提供的一个标准易用的aop框架,依托Spring的IOC容器,提供了极强的AOP扩展增强能力,对项目开发提供了极大地便利
    2022-11-11
  • Java优化重复冗余代码的8种方式总结

    Java优化重复冗余代码的8种方式总结

    日常开发中,我们经常会遇到一些重复代码,最近小编优化了一些系统中的重复代码,用了好几种的方式,感觉挺有用的,所以本文给大家讲讲优化重复代码的几种方式
    2023-08-08
  • java中long数据类型转换为int类型

    java中long数据类型转换为int类型

    这篇文章主要讲解Java中基本数据类型,java long 类型与其java int类型的转换的几种方法,希望能给大家做一个参考
    2016-07-07
  • Java中toString函数的使用示例代码

    Java中toString函数的使用示例代码

    toString()函数用于将当前对象以字符串的形式返回,比如我定义了一个User类,创建了一个user对象,然后使用相应命令去打印user对象,本文结合示例代码介绍了toString函数的使用,需要的朋友可以参考下
    2024-02-02
  • SpringMvc+Mybatis+Pagehelper分页详解

    SpringMvc+Mybatis+Pagehelper分页详解

    这篇文章主要介绍了SpringMvc+Mybatis+Pagehelper分页详解,非常不错,具有参考借鉴价值,需要的朋友可以参考下的相关资料
    2017-01-01
  • 一个简单的Spring容器初始化流程详解

    一个简单的Spring容器初始化流程详解

    这篇文章主要给大家介绍了一个简单的Spring容器初始化流程的相关资料,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2021-01-01
  • Java 将Excel转为OFD格式(方法步骤)

    Java 将Excel转为OFD格式(方法步骤)

    OFD是一种开放版式文档是我国国家版式文档格式标准,本文通过Java后端程序代码展示如何将Excel转为OFD格式,分步骤给大家介绍的非常详细,感兴趣的朋友一起看看吧
    2021-12-12

最新评论