文章目录
SpringCloud:
微服务的中间件介绍与使用
微服务架构体系图:
依赖版本补充
下面所有代码中的依赖版本如下:
xml<properties> <maven.compiler.source>17</maven.compiler.source> <maven.compiler.target>17</maven.compiler.target> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <hutool.version>5.8.22</hutool.version> <lombok.version>1.18.30</lombok.version> <druid.version>1.2.18</druid.version> <mybatis.springboot.version>3.0.3</mybatis.springboot.version> <mysql.version>8.0.33</mysql.version> <fastjson2.version>2.0.48</fastjson2.version> <swagger3.version>2.2.0</swagger3.version> <mapper.version>4.2.3</mapper.version> <persistence-api.version>1.0.2</persistence-api.version> <spring.boot.test.version>3.1.5</spring.boot.test.version> <spring.boot.version>3.2.0</spring.boot.version> <spring.cloud.version>2023.0.0</spring.cloud.version> <spring.cloud.alibaba.version>2023.0.0.0-RC1</spring.cloud.alibaba.version> <knife4j-openapi3.version>4.4.0</knife4j-openapi3.version> </properties>
GateWay:网关
什么是网关 : 网关 简单的理解就是在请求和服务之间加了一个 中间层
作用是 :一、管控 路由的请求(反向代理、鉴权、流量控制、熔断、日志监控)
二、在每次路由中进行 增删内容 。
图形化展示网关位置:
工作原理
简单总结就是:路由转发+断言判断+执行过滤链列表
参考官网:https://docs.spring.io/spring-cloud-gateway/reference/spring-cloud-gateway/how-it-works.html
三大核心之Router:路由
概念:路由是构建网管的基本模块,它有ID、目标URI、一系列断言和过滤器组成,如果断言为true则匹配该条路由
接下来直接开始配置一个基本的路由
1、导入基础依赖
html
<!--gateway依赖-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<!--服务注册发现consul discovery,网关也要注册进服务注册中心统一管控-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-consul-discovery</artifactId>
</dependency>
<!-- 指标监控健康检查的actuator,网关是响应式编程删除掉spring-boot-starter-web dependency-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
2、进行服务注册
yml
server:
port: 9567
spring:
application:
name: cloud-gateway
cloud:
consul:
port: 8500 #consul的端口号
host: localhost #consul的网址
discovery:
service-name: ${spring.application.name} #配置服务名
启动类添加注册注解
java
@SpringBootApplication
@EnableDiscoveryClient/*服务组注册*/
public class Main9567 {
public static void main(String[] args) {
SpringApplication.run(Main9567.class, args);
}
}
启动查看是否完成了服务注册
3、路由映射
这里可以先创建一个简单的业务[可以不需要数据库就返回一个值即可]做映射地址(别忘了要进行服务注册)
这里为了方便我就修改一下上面的业务的进行测试了。配置好测试业务之后,在路由服务的配置文件中进行配置:
yml
server:
port: 9567
spring:
application:
name: cloud-gateway
cloud:
consul:
port: 8500 #consul的端口号
host: localhost #consul的网址
discovery:
service-name: ${spring.application.name} #配置服务名
gateway:
routes:
- id: gateway_router_1 #路由的ID(类似mysql主键ID),没有固定规则但要求唯一,建议配合服务名
#配置地址方法一(不推荐):
# uri: localhost:8001 #填写业务服务的地址和端口号(缺点:灵活性差)
#方法二(推荐):动态路由
uri: lb://cloud-payment-service #填写业务服务名(优点:可动态的配置路由,lb:可以支持负载均衡)
predicates: # 配置一个简单的断言,以便于演示。也就是访问此路由的条件
- Path=/pay/gateway/get/** #满足此路由的才可以访问,**:通配符
- id: gateway_router_2
uri: lb://cloud-payment-service
predicates:
- Path=/pay/gateway/info/**
4、测试访问GateWay的端口是否可以访问

访问成功,说明映射成功了!
这里可以也试一下当有多个服务(yourServerName),下使用
lb://yourServerName
是否有负载均衡的效果
三大核心之Predicate:断言
概念:开发人员可以匹配HTTP请求中的所有内容,如果请求与断言相匹配则进行路由
配置文件
yml
gateway:
routes:
- id: gateway_router_1 #路由的ID(类似mysql主键ID),没有固定规则但要求唯一,建议配合服务名
#配置地址方法一(不推荐):
# uri: localhost:8001 #填写业务服务的地址和端口号(缺点:灵活性差)
#方法二(推荐):动态路由
uri: lb://cloud-payment-service #填写业务服务名(优点:可动态的配置路由,lb:可以支持负载均衡)
predicates: # 配置一个简单的断言,以便于演示。也就是访问此路由的条件
- Path=/pay/gateway/get/** #满足此路由的才可以访问,**:通配符
- id: gateway_router_2
uri: lb://cloud-payment-service
predicates:
- Path=/pay/gateway/get/** # 断言,路径相匹配的进行路由
- After=2025-04-20T16:04:22.672173200+08:00[Asia/Shanghai] #配置指定的时间之后才开启此路由
- Before=2025-04-20T16:08:13.979939200+08:00[Asia/Shanghai] #与after相反,没有达到此时间之后都可以访问,过了之后便会直接关闭
- Between=2025-04-20T16:09:38.676463500+08:00[Asia/Shanghai],2025-04-20T16:11:38.676463500+08:00[Asia/Shanghai] #区间
- Cookie=phone,^1(3[0-9]|4[57]|5[0-35-9]|7[0678]|8[0-9])\d{8}$ #根据cookie参数进行验证,第一个是key值,第二个是规定value值正则
- Header=X-Request-Id, \d+ #根据请求头的参数名和值进行判断,第一个值是name,第二个值是正则表达式
- Host=**.chyb.com,**.chyb.org #根据Host这个参数进行模糊查询,**:通配符
- Query=num, \d+ #必须要有num这个参数,且值为整数
- RemoteAddr=192.168.235.0/24 #根据请求人的ip进行拦截,/24:是指前面的24位是不可以变的,24:前面的192.168.43是不可以变得,注意:这里请求的时候不要在使用localhost而是要输入自己的IP地址
- Method=GET,POST #按照请求方式进行匹配
这里就相当于API的调用一样没有什么好说的,不清楚的可以直接参考官方文档:https://docs.spring.io/spring-cloud-gateway/reference/spring-cloud-gateway/request-predicates-factories.html
中文官网:https://springdoc.cn/spring-cloud-gateway/#gateway-request-predicates-factories
这里在单独将一个知识点领出来说:快捷方式的配置(常用) 、 完全展开的参数 语法如下:
yml
# 快捷方式的配置
- Cookie=mycookie,mycookievalue
#完全展开的参数:完全展开的参数看起来更像标准的yaml配置,有名称/值对。一般来说,会有一个 name key和一个 args key。args key是一个键值对的映射,用于配置谓词或过滤器。
- name: Cookie
args:
name: mycookie
regexp: mycookievalue
自定义Predicate(断言)
创建 ~RoutePredicateFactory 类,其代码如下:
java
@Component /*注入Spring容器中*/
/*注意命名规范:~~~RoutePredicateFactory*/
public class MyRoutePredicateFactory extends AbstractRoutePredicateFactory<MyRoutePredicateFactory.Config> {
public MyRoutePredicateFactory() {
super(MyRoutePredicateFactory.Config.class);
}
@Validated
public static class Config {
@Setter
@Getter
@NotEmpty
private String userType; //钻、金、银等用户等级
}
/**
* 写逻辑:根据userType进行判断是否可以访问此路由
* @param config
* @return
*/
@Override
public Predicate<ServerWebExchange> apply(MyRoutePredicateFactory.Config config) {
return new Predicate<ServerWebExchange>() {
@Override
public boolean test(ServerWebExchange serverWebExchange) {
//检查request的参数里面,userType是否为指定的值,符合配置就通过
String userType = serverWebExchange.getRequest().getQueryParams().getFirst("userType");
if (userType == null) return false;
//如果说参数存在,就和config的数据进行比较
if (userType.equals(config.getUserType())) {
return true;
}
return false;
}
};
}
/**
* 用来支持断言的快捷方式
* @return
*/
@Override
public List<String> shortcutFieldOrder() {
return Collections.singletonList("userType");
}
}
配置文件的设置
yml
#....
predicates:
#这里的"My"是根据你类名有关系,当遵守命名规范:~RoutePredicateFactory,则默认的名字就是:~
- My=diamond #当参数userType的值等于diamond才可以访问此路由
三大核心之Filter:过滤
概念:指的是Spring框架中GatewayFilter的实例,使用过滤器,可以在请求路由之前或之后对请求进行修改
过滤器类型分为:
- 全局默认过滤器 (Global Filters):每一个路由都要执行的过滤器
- 单一内置过滤器 (GateWayFilter),"又称路由过滤器":指定某一个路由执行的过滤器
配置文件
yml
filters:
# RequestHeader的增删改
- AddRequestHeader=chyb-name, chybName1 #添加一个请求头(chyb-name)值为:chybName1
- AddRequestHeader=chyb-name2, chybName2
- RemoveRequestHeader=chyb-name #删除一个请求头(user-agent)
- SetRequestHeader=chyb-name2, chybName3 #修改某一个请求头(chyb-name2)的值为chybName3,若是没哟此请求头,则直接会自动添加
# RequestParameter请求参数的增删
- AddRequestParameter=param1, 2983 #增加参数
- RemoveRequestParameter=param2 #删除参数
# ResponseHeader 响应头的增删改
- AddResponseHeader=chybName, chyb1 #创建一个响应头(chybName)值为:chyb1
- SetResponseHeader=date, 2006-02-09 #设置date响应头的值为:2006-02-09
- RemoveResponseHeader=Content-Type #删除Content-Type(接受类型)
# 前缀、路径、相关的配置
- PrefixPath=/pay #设置访问路径的前缀
- SetPath=/pay/gateway/{param} #设置访问路径,{~}为占位符
- RedirectTo=301, https://baidu.com #重定向到https://baidu.com,状态吗为302
中文官网:https://springdoc.cn/spring-cloud-gateway/#gatewayfilter-工厂
自定义全局过滤 ------ 输出每一个路由所执行的时间
1、创建一个~~GlobalFilter,并实现自定义过滤器所需的接口:GlobalFilter, Ordered
java
/*注入到Spring容器中,之后会将所有注入到容器中的过滤器进行合并,形成过滤器列表*/
@Component
@Slf4j
/*这里是自定配置全局过滤器,就是每一个路由都会去执行的*/
public class MyGlobalFilter implements GlobalFilter, Ordered {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
return null;
}
/**
* 设置执行接口进行排序,返回值越小越靠前
* @return
*/
@Override
public int getOrder() {
return 0;
}
}
2、在filter方法下写执行逻辑,实现:输出每一个路由所执行的时间
java
public static final String BEGIN_VISIT_TIME = "BEGIN_VISIT_TIME";/*初始化方法执行的开始时间*/
/**
*用来接受响应请求报文,以及控制放行操作(这里有点像将拦截器中的preHandle[访问前拦截方法]和postHandle[访问后拦截方法]结合到一起了)
* @param exchange :类似于Server,它里面封装了三个属性:
* ServerHttpResponse、ServerHttpRequest:这两个属性存储着:请求/响应的报文,所以可以通过exchange这个参数获取到这些属性
* Map<String, Object>:这属性就是我们要进行额外的添加对象就可以添加到这里,运用的方法就直接.pus即可
* @param chain : 这个属性可以控制过滤器的放行操作
* @return 这里返回的类型是Mono这个类型是在SpringBoot响应式编程中所引出的类,适用于高并发和高吞吐量场景(在资源有限的情况下提高系统的吞吐量和伸缩性【并非提高性能】)
*/
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
System.out.println("=====" + exchange.getAttributes().get(BEGIN_VISIT_TIME) + "=====");
exchange.getAttributes().put(BEGIN_VISIT_TIME, System.currentTimeMillis());
System.out.println("------" + exchange.getAttributes().get(BEGIN_VISIT_TIME) + "-----");
/**
* 这里的chain.filter(exchange)是放行操作,而.then()是任务串联(在 Mono完成后触发新任务,但不依赖前一个任务的输出结果。)
*/
return chain.filter(exchange).then(Mono.fromRunnable(() -> {
Long beginTime = exchange.getAttribute(BEGIN_VISIT_TIME);
URI uri = exchange.getRequest().getURI();
if (beginTime != null) {
log.info("访问接口主机:{}", uri.getHost());
log.info("访问接口端口号:{}", uri.getPort());
log.info("放文件接口URL:{}", uri.getPath());
log.info("访问接口的URL参数:{}", uri.getQuery());
log.info("访问时长:{}ms", (System.currentTimeMillis() - beginTime));
log.info("============================================");
System.out.println();
}
}));
}
这里因为是全局过滤器,所以并不需要进行单独配置yml。
3、测试(访问任意路由查看日志是否输出)
自定义路由过滤器 ------ 输出指定的路由所执行的时间、设置路由必填参数
1、创建自定义路由过滤器类,注意类名~~GatewayFilterFactory
案例一:日志输出指定的路由所执行的时间
java
/*注入到Spring容器中,之后会将所有注入到容器中的过滤器进行合并,形成过滤器列表*/
@Component
@Slf4j
/*自定义路由过滤器,指定某一个路由执行的过滤器,无参数的写法(只需要编写apply方法即可,注意config的类型)*/
/*注意命名规范:~GatewayFilterFactory*/
public class MyGatewayFilterFactory extends AbstractGatewayFilterFactory<Object> {
static final String BEGIN_TIME = "begin_time";
@Override
public GatewayFilter apply(Object config) {
System.out.println("进入了自定义网关过滤器MyGatewayFilterFactory,status:" + config);
return new GatewayFilter() {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
exchange.getAttributes().put(BEGIN_TIME, System.currentTimeMillis());
return chain.filter(exchange).then(Mono.fromRunnable(() -> {
Long beginTime = exchange.getAttribute(BEGIN_TIME);
URI uri = exchange.getRequest().getURI();
if (beginTime != null) {
log.info("------------------我是路由过滤器----------------------");
log.info("访问接口主机:{}", uri.getHost());
log.info("访问接口端口号:{}", uri.getPort());
log.info("放文件接口URL:{}", uri.getPath());
log.info("访问接口的URL参数:{}", uri.getQuery());
log.info("访问时长:{}ms", (System.currentTimeMillis() - beginTime));
log.info("============================================");
System.out.println();
}
}));
}
};
}
}
案例二:设置路由必填参数
java
@Component
/*定义路由有参过滤器*/
public class MustQueryGatewayFilterFactory extends AbstractGatewayFilterFactory<MustQueryGatewayFilterFactory.Config> {
/*初始化*/
public MustQueryGatewayFilterFactory() {
super(MustQueryGatewayFilterFactory.Config.class);
}
/*用来配置快捷访问方式的传参的位置*/
@Override
public List<String> shortcutFieldOrder() {
return Arrays.asList("status");
}
/*定义一个内部类用来存放接受的参数,同时也是继承AbstractGatewayFilterFactory类的泛型*/
public static class Config {
@Getter
@Setter
private String status;//设定一个状态值/标志位,它等于多少,匹配和才可以访问
}
/**
* 写过滤器逻辑的方法
* @param config 传递参数值
* @return
*/
@Override
public GatewayFilter apply(MustQueryGatewayFilterFactory.Config config) {
return new GatewayFilter() {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
ServerHttpRequest request = exchange.getRequest();
/*动态的将配置的参数设为传必传参数的name*/
System.out.println("进入了自定义网关过滤器MyGatewayFilterFactory,status:" + config.getStatus());
if (request.getQueryParams().containsKey(config.getStatus())) {
return chain.filter(exchange);
} else {
exchange.getResponse().setStatusCode(HttpStatus.BAD_REQUEST);
return exchange.getResponse().setComplete();
}
}
};
}
}
//单一内置过滤器GatewayFilter
2、配置yml
在指定的路由上添加路由过滤器
yml
server:
port: 9567
spring:
application:
name: cloud-gateway
cloud:
consul:
port: 8500 #consul的端口号
host: localhost #consul的网址
discovery:
service-name: ${spring.application.name} #配置服务名
gateway:
routes:
- id: gateway_router_1
uri: lb://cloud-payment-service
predicates:
- Path=/pay/gateway/info/** # 断言,路径相匹配的进行路由
filters:
- My #配置日志输出指定的路由所执行的时间的路由过滤器
- id: gateway_router_2
uri: lb://cloud-payment-service
predicates:
- Path=/pay/gateway/get/**
filters:
- MustQuery=chyb #配置必传参数名,否则访问不了
测试
案例一:
案例二:
源码分析:自定义过滤器 ------ apply()
我们以这段代码为例:
java
@Override
public GatewayFilter apply(Object config) {
System.out.println("进入了自定义网关过滤器MyGatewayFilterFactory,status:" + config);
return new GatewayFilter() {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
exchange.getAttributes().put(BEGIN_TIME, System.currentTimeMillis());
return chain.filter(exchange).then(Mono.fromRunnable(() -> {
// .....
}));
}
};
}
这里我们把重心放在:
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {}
这里的第一个参数exchange:里面封装了三个参数(上面自定义全局过滤器注释有,所以不多讲
getRequest、getResponse、getAttributes
)第二个参数chain:主要是用来进行放行操作的,而这里是怎么放行的我们就从过滤链列表构建 到 过滤器链的执行流程
过滤链列表的构建:
过滤链列表由来:全局过滤器列表 和 路由过滤器列表 动态合并而成
全局过滤器列表 :
是根据Spring容器扫描所有的 GlobalFilter 的实现类,
通过 @Order 注解或 Ordered 接口的 getOrder()
方法对全局过滤器进行排序,形成全局过滤器列表。从而形成的一个列表。
路由过滤器列表 :
从配置文件(如 YAML)或代码中解析路由规则,提取每个路由的 filters 列表。
这里还有一个点要注意:配置文件中的过滤器名称(如 AddRequestHeader),会默认转化为
GatewayFilter实例
(也就是我们自定义的路由过滤器类),
然后根据所有的
GatewayFilter实例
添加到 路由过滤器列表。动态合并而成 :
当请求到达时,网关根据当前请求匹配的路由,将 全局过滤器列表 和 路由过滤器列表 合并。
路由过滤器按配置顺序追加到全局过滤器之后,便形成了过滤链。排序规则[全局过滤器按 @Order 排序(值越小优先级越高)]
源码示例(合并):
FilteringWebHandler
类中的handle()
javapublic Mono<Void> handle(ServerWebExchange exchange) { // 1. 获取全局过滤器和路由过滤器 List<GatewayFilter> globalFilters = this.globalFilters; List<GatewayFilter> routeFilters = route.getFilters(); // 2. 合并过滤器列表 List<GatewayFilter> combined = new ArrayList<>(globalFilters); combined.addAll(routeFilters); // 3. 排序(全局过滤器按 Order,路由过滤器按配置顺序) AnnotationAwareOrderComparator.sort(combined); // 4. 创建过滤器链 return new DefaultGatewayFilterChain(combined).filter(exchange); }
这就是形成过滤器列表的大致过程。
过滤器链的执行流程
链式调用模型:
- 责任链模式 :通过 DefaultGatewayFilterChain (
这个类是FilteringWebHandler类的内部类,要想找的话可以直接搜索这个类) 递归调用每个过滤器,直到所有过滤器执行完毕。- 响应式编程:使用 Mono 和 .then() 实现异步非阻塞执行。(这里不懂的话可以看一下SpringBoot的响应式编程)
核心代码展示:
这里可以找到chain.filter(exchange)
【我们自定义过滤器中所返回的那个方法】的实现类FilteringWebHandler
找到内部类 DefaultGatewayFilterChain核心代码如下:
javapublic class DefaultGatewayFilterChain implements GatewayFilterChain { private final List<GatewayFilter> filters; private final int index; // 当前执行过滤器的索引 @Override public Mono<Void> filter(ServerWebExchange exchange) { return Mono.defer(() -> { if (index < filters.size()) { // 1. 获取当前过滤器 GatewayFilter filter = filters.get(index); // 2. 创建下一个链(index+1) GatewayFilterChain nextChain = new DefaultGatewayFilterChain(this, index + 1); // 3. 执行当前过滤器,传递下一个链 return filter.filter(exchange, nextChain); } else { // 4. 所有过滤器执行完毕 return Mono.empty(); } }); } }
- 执行顺序示例
假设合并后的过滤器列表为:[GlobalFilterA, GlobalFilterB, RouteFilter1, RouteFilter2]执行流程:
- GlobalFilterA 的前置逻辑 → 调用 chain.filter()
- GlobalFilterB 的前置逻辑 → 调用 chain.filter()
- RouteFilter1 的逻辑 → 调用 chain.filter()
- RouteFilter2 的逻辑 → 调用 chain.filter()
- 路由到目标服务 → 返回响应
- RouteFilter2 的后置逻辑(通过 .then())
- RouteFilter1 的后置逻辑
- GlobalFilterB 的后置逻辑
- GlobalFilterA 的后置逻辑。
这里可以自己打几个断点到DefaultGatewayFilterChain类中一步一步的查看源码查看,会更清晰一些
上述大部分代码以上传到gitee:https://gitee.com/banhuayue/springCloud-Alibaba-code.git