业务流程
在上面两个章节中我们已经为我们的网关注册中心(api-gateway-center
)提供了注册功能,无论是网关服务api-gateway-core
还是RPC服务都可以想网关注册中心发起注册。
在第9章的网关注册中心服务初始化中,我们梳理过一次整个网关业务的项目架构
我们说到过我们是将api-gateway-core
当作一种类似于Mybatis的数据资源(算力资源),这种数据资源在springboot中绝对不能和springboot耦合,因此我们提供了对其的包装api-gateway-assist
,将其类比为一个SpringBoot starter
。
那么现在我们来思考一下这个SpringBoot starter
到底应该具有那些功能呢?我们的api-gateway-core
在之前的实现过程中,我们一直有一个HttpStatement
的映射,正是因为有这个映射,网关通信组件才能知道每一个访问网关的http请求,到底该访问哪一个具体的RPC服务,在之前的api-gateway-center
的数据库设计和注册功能实现都是为了最终能够让对应的访问api-gateway-core
的具体请求和对应的RPC服务链接起来,即构建成功最终的HttpStatement。
现在我们已经分别提供好了网关注册中心的注册功能,但是由于我们是将api-gateway-core
当作一种类似于Mybatis的数据资源(算力资源),无论是从单一职责原则,还是解耦合方面,发起注册的功能绝不能由api-gateway-core
进行执行,因此我们引入了类似SpringBoot starter
的api-gateway-assist
,对数据资源进行包装,但是从结构图中,我们可以看到我们是由api-gateway-engin
这个类似于SpringBoot
的东西进行注册网关和拉取接口的能力,而这种详细信息的注册和拉去一定是从api-gateway-assist
这个SpringBoot starter
加载而来的,我们当初设计api-gateway-assist
就是为了让api-gateway-core
和api-gateway-engin
解耦,后续两者那方有更改只需要更新starter即可。通常像 MyBatis 的 ORM 框架、RPC 的 Dubbo 服务,在接入到 SpringBoot 都是有自己的 Starter 组件的。因为这些组件可以封装一些共性共用的逻辑处理,一次开发处处使用的目的。所以我们要做这样的处理,方便在网关引擎中启动网关算力。可以想象这是把算力注入到引擎中的纽带开发。
所以本章节最大的目的在于搭建起用于封装网关算力服务的api-gateway-core
系统为目的,提供网关服务注册发现能力的组件api-gateway-assist
,我们希望把这样的统一公用能力进行一致的管理,如果没有这样的组件服务,那么将需要每一个 SpringBoot 服务都要做类似这样的事情,整体来看就会耗费很大的成本,所以要把这样的功能进行收口。如下图(图片来源于xfg)
api-gateway-core 是网关的算力服务,api-gateway-center 是网关的注册中心,那么为了把这块服务链接起来,中间则需要一套 api-gateway-engin 网关的引擎,用于启动网关的算力服务。
但由于启动网关的算力服务还需要一些功能的整合,来包装网关算力到注册中心的连接,所以这部分需要整合到 api-gateway-assist 这个辅助组件,它是一个 SpringBoot Starter 起到包装和连接的作用。
现在我们来思考下我们应该如何实现starter的自动注册功能,我们现在的需求是,当这个SpringStarter启动时,就会自动向网管注册中心发起注册请求(由于api-gateway-engin
还未搭建起来,所以暂时先直接请求到网关注册中心上去,随着后续开发再更改)。
首先通常我们使用一个公用的starter的时候,只需要将相应的依赖添加的Maven的配置文件当中即可,免去了自己需要引用很多依赖类,并且SpringBoot会自动进行类的自动配置。而我们自己开发一个starter也需要做相应的处理。
结合本章节要求就是只要把这个 Starter 作为依赖加进Spring Boot 应用,不用再写任何集成代码,它就会在应用启动结束时,自动把本机的网关信息发到网关中心完成注册,即引入依赖即可生效(零侵入),在应用启动完成后自动把当前网关实例注册到网关中心。
SpringBoot 在启动时会去依赖的starter包中寻找 resources/META-INF/spring.factories 文件,然后根据文件中配置的Jar包去扫描项目所依赖的Jar包,这类似于 Java 的 SPI 机制。那我们就可以在这其中进行一个配置类的加载
在加载配置类的过程中引入容器,注册事件监听器,只要某事件触发,就进行注册服务。
即最终底色效果为:引入依赖 → SPI 自动装配生效 → 注册监听器与服务 → 容器刷新完成发布事件 → 自动发起注册请求。这样就无需我们在springboot应用中写一行集成代码。
详细的starter开发可以参考下这篇博客:Spring Boot 中间件开发(一)《服务治理中间件之统一白名单验证》
详细的有关SPI的知识可以参考博客:自己写的Java SPI梳理 ,其它推荐:Java SPI机制总结系列之开发入门实例,Java SPI机制总结系列之万字最详细图解Java SPI机制源码
业务实现
实现再梳理
首先创建好对应的项目api-gateway-assist
我么再仔细梳理下实现流程:
springboot启动时会去依赖的starter包中寻找 resources/META-INF/spring.factories 文件,根据文件中配置的Jar包去扫描项目所依赖的Jar包,这类似于 Java 的 SPI 机制。加载出来了配置类。
在配置类中我们可以注册bean,我们希望是通过事件触发来自动进行注册,那么这个时候就需要至少两个bean,一个用于负责注册的bean,一个用于监听事件的bean。
其次发起注册需要那些信息,这些信息从何而来?我们再设计网关注册中心的数据库表时,关于网关服务的信息有以下信息(如下图),其中服务状态在网关注册中心的注册逻辑中,我们会根据接口的调用自动赋值,也就是说,当前starter只需要提供分组标识,网关标识,网关地址这几个字段属性,但是这里有一个问题,仅有上述三个字段是不知道注册到那个注册中心的,因此这里还需要提供一个网关注册中心的地址字段。最后这些信息从那里读入呢,我们不可能进行硬编码,所以我们需要将这部分内容写入到配置文件中去。而这些信息我们最好绑定到一个配置类中去,并在springboot启动时将其创建为一个bean,以方便注册服务拿到相应的字段。
具体实现
接着上述的梳理,现在就来具体实现这个starter
首先是信息的配置类GatewayServiceProperties
利用@ConfigurationProperties
注解从配置文件中读取内容
package com.zshunbao.gateway.assist.config;
import org.springframework.boot.context.properties.ConfigurationProperties;
/**
* @program: api-gateway-assist
* @ClassName GatewayServiceProperties
* @description: 网关配置服务--主要是针对api-gateway-center的网关明细表多了一个 api-gateway-center注册中心地址的属性
* @author: zs宝
* @create: 2025-08-21 15:13
* @Version 1.0
**/
@ConfigurationProperties("api-gateway")
public class GatewayServiceProperties {
/** 注册中心地址 */
private String address;
/** 分组ID */
private String groupId;
/** 网关ID */
private String gatewayId;
/** 网关名称 */
private String gatewayName;
/** 网关地址 */
private String gatewayAddress;
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
public String getGroupId() {
return groupId;
}
public void setGroupId(String groupId) {
this.groupId = groupId;
}
public String getGatewayId() {
return gatewayId;
}
public void setGatewayId(String gatewayId) {
this.gatewayId = gatewayId;
}
public String getGatewayName() {
return gatewayName;
}
public void setGatewayName(String gatewayName) {
this.gatewayName = gatewayName;
}
public String getGatewayAddress() {
return gatewayAddress;
}
public void setGatewayAddress(String gatewayAddress) {
this.gatewayAddress = gatewayAddress;
}
}
然后是提供注册功能的类
RegisterGatewayService.java
package com.zshunbao.gateway.assist.service;
import cn.hutool.http.HttpUtil;
import com.alibaba.fastjson.JSON;
import com.zshunbao.gateway.assist.GatewayException;
import com.zshunbao.gateway.assist.common.Result;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.HashMap;
import java.util.Map;
/**
* @program: api-gateway-assist
* @ClassName RegisterGatewayService
* @description: 网关注册服务
* @author: zs宝
* @create: 2025-08-21 15:17
* @Version 1.0
**/
public class RegisterGatewayService {
private Logger logger = LoggerFactory.getLogger(RegisterGatewayService.class);
/**
* 进行向api-gateway-center网关注册中心发起注册的服务
* @param address 注册中心地址
* @param groupId api-gateway-core分组ID
* @param gatewayId 网关ID
* @param gatewayName 网关名称
* @param gatewayAddress 网关地址
*/
public void doRegister(String address, String groupId, String gatewayId, String gatewayName, String gatewayAddress) {
//封装请求参数
Map<String,Object> paramMap=new HashMap<>();
paramMap.put("groupId", groupId);
paramMap.put("gatewayId", gatewayId);
paramMap.put("gatewayName", gatewayName);
paramMap.put("gatewayAddress", gatewayAddress);
//调用hutool工具包发送请求
String resultStr = HttpUtil.post(address, paramMap, 350);
//将返回结果用自定义的结果类包装
Result result = JSON.parseObject(resultStr, Result.class);
logger.info("向网关中心注册网关算力服务 gatewayId:{} gatewayName:{} gatewayAddress:{} 注册结果:{}", gatewayId, gatewayName, gatewayAddress, resultStr);
if(!"0000".equals(result.getCode())){
throw new GatewayException("网关服务注册异常 [gatewayId:" + gatewayId + "] 、[gatewayAddress:" + gatewayAddress + "]");
}
}
}
在 RegisterGatewayService 注册网关类中,调用 api-gateway-center 提供的服务发现接口,向网关中心注册网关算力节点。这是第一步非常重要的关联作用,有了这块逻辑的处理,才能打通整个网关算力和网关注册中心。
事件监听类GatewayApplication.java
,这里我们希望在容器刷新完成事件 refresh时完成注册
package com.zshunbao.gateway.assist.application;
import com.zshunbao.gateway.assist.config.GatewayServiceProperties;
import com.zshunbao.gateway.assist.service.RegisterGatewayService;
import org.springframework.context.ApplicationListener;
import org.springframework.context.event.ContextRefreshedEvent;
/**
* @program: api-gateway-assist
* @ClassName GatewayApplication
* @description: 网关应用;与 Spring 链接,调用网关注册和接口拉取
* @author: zs宝
* @create: 2025-08-21 15:26
* @Version 1.0
**/
public class GatewayApplication implements ApplicationListener<ContextRefreshedEvent> {
private GatewayServiceProperties properties;
private RegisterGatewayService registerGatewayService;
public GatewayApplication(GatewayServiceProperties properties, RegisterGatewayService registerGatewayService) {
this.properties = properties;
this.registerGatewayService = registerGatewayService;
}
//当所有容器加载完毕时,即监听到了上下文事件更新,然后调用注册方法,向网关注册中心发起注册
@Override
public void onApplicationEvent(ContextRefreshedEvent contextRefreshedEvent) {
// 1. 注册网关服务;每一个用于转换 HTTP 协议泛化调用到 RPC 接口的网关都是一个算力,这些算力需要注册网关配置中心
registerGatewayService.doRegister(properties.getAddress(),
properties.getGroupId(),
properties.getGatewayId(),
properties.getGatewayName(),
properties.getGatewayAddress());
}
}
最后是相关的配置类GatewayAutoConfig.java
package com.zshunbao.gateway.assist.config;
import com.zshunbao.gateway.assist.application.GatewayApplication;
import com.zshunbao.gateway.assist.service.RegisterGatewayService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* @program: api-gateway-assist
* @ClassName GatewayAutoConfig
* @description: 网关配置服务,这里利用了Java的 SPI机制进行bean创建
* @author: zs宝
* @create: 2025-08-21 15:30
* @Version 1.0
**/
@Configuration
@EnableConfigurationProperties(GatewayServiceProperties.class)
public class GatewayAutoConfig {
private Logger logger = LoggerFactory.getLogger(GatewayAutoConfig.class);
@Bean
public RegisterGatewayService registerGatewayService(){
return new RegisterGatewayService();
}
@Bean
public GatewayApplication gatewayApplication(RegisterGatewayService registerGatewayService,GatewayServiceProperties properties){
return new GatewayApplication(properties,registerGatewayService);
}
}
相关的spring.factories
文件
org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.zshunbao.gateway.assist.config.GatewayAutoConfig
最后还有一些自定义的封装返回结果和错误类
Result.java
package com.zshunbao.gateway.assist.common;
/**
* @program: api-gateway-assist
* @ClassName Result
* @description: 统一返回对象中,Code码、Info描述
* @author: zs宝
* @create: 2025-08-21 15:11
* @Version 1.0
**/
public class Result {
private String code;
private String info;
public String getCode() {
return code;
}
public void setCode(String code) {
this.code = code;
}
public String getInfo() {
return info;
}
public void setInfo(String info) {
this.info = info;
}
}
GatewayException.java
package com.zshunbao.gateway.assist;
/**
* @program: api-gateway-assist
* @ClassName GatewayException
* @description:
* @author: zs宝
* @create: 2025-08-21 15:25
* @Version 1.0
**/
public class GatewayException extends RuntimeException{
public GatewayException(String msg){
super(msg);
}
public GatewayException(String msg, Throwable cause) {
super(msg, cause);
}
}
相关pom文件
<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.zshunbao</groupId>
<artifactId>api-gateway-assist-01</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>jar</packaging>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.3.5.RELEASE</version>
<relativePath/>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-autoconfigure</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<!-- https://mvnrepository.com/artifact/org.mybatis.spring.boot/mybatis-spring-boot-starter -->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.4</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.34</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-test -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-test</artifactId>
<scope>test</scope>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework/spring-test -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>commons-beanutils</groupId>
<artifactId>commons-beanutils</artifactId>
<version>1.9.4</version>
</dependency>
<dependency>
<groupId>commons-lang</groupId>
<artifactId>commons-lang</artifactId>
<version>2.6</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.75</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
<!-- https://mvnrepository.com/artifact/cn.hutool/hutool-all -->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.8.10</version>
</dependency>
</dependencies>
<build>
<finalName>api-gateway-assist</finalName>
<resources>
<resource>
<directory>src/main/resources</directory>
<filtering>true</filtering>
<includes>
<include>**/**</include>
</includes>
</resource>
</resources>
<testResources>
<testResource>
<directory>src/test/resources</directory>
<filtering>true</filtering>
<includes>
<include>**/**</include>
</includes>
</testResource>
</testResources>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.12.4</version>
<configuration>
<skipTests>true</skipTests>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-resources-plugin</artifactId>
<version>2.5</version>
<configuration>
<encoding>${project.build.sourceEncoding}</encoding>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>2.3.2</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
<encoding>${project.build.sourceEncoding}</encoding>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-source-plugin</artifactId>
<version>2.1.2</version>
<executions>
<execution>
<id>attach-sources</id>
<goals>
<goal>jar</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
测试
package com.zshunbao.gateway.assist.test;
import cn.hutool.http.HttpUtil;
import com.alibaba.fastjson.JSON;
import com.zshunbao.gateway.assist.common.Result;
import org.junit.Test;
import java.util.HashMap;
import java.util.Map;
/**
* @program: api-gateway-assist
* @ClassName ApiTest
* @description:
* @author: zs宝
* @create: 2025-08-21 15:39
* @Version 1.0
**/
public class ApiTest {
public static void main(String[] args) {
System.out.println("Hi Api Gateway");
}
@Test
public void test_register_gateway() {
Map<String, Object> paramMap = new HashMap<>();
paramMap.put("groupId", "10001");
paramMap.put("gatewayId", "api-gateway-g4");
paramMap.put("gatewayName", "电商配送网关");
paramMap.put("gatewayAddress", "127.0.0.1");
String resultStr = HttpUtil.post("http://localhost:8001/wg/admin/config/registerGateway", paramMap, 600);
System.out.println(resultStr);
Result result = JSON.parseObject(resultStr, Result.class);
System.out.println(result.getCode());
}
}
记得测试时启动网关注册中心api-gateway-center
网关注册中心打印日志如下