深入理解SpringCloud之Gateway,小白都能看懂的保姆级教学
liuian 2025-05-25 14:04 42 浏览
虽然在服务网关有了zuul(在这里是zuul1),其本身还是基于servlet实现的,换言之还是同步阻塞方式的实现。就其本身来讲它的最根本弊端也是再此。而非阻塞带来的好处不言而喻,高效利用线程资源进而提高吞吐量,基于此Spring率先拿出针对于web的杀手锏,对,就是webflux。而Gateway本身就是基于webflux基础之上实现的。毕竟spring推出的技术,当然要得以推广嘛。不过就国内的软件公司而言为了稳定而选择保守,因此就这项技术的广度来说我本身还是在观望中。
1. Gateway快速上手
添加依赖:
implementation 'org.springframework.cloud:spring-cloud-starter-gateway'
这里请注意,springcloud-gateway是基于netty运行的环境,在servlet容器环境或者把它构建为war包运行的话是不允许的,因此在项目当中没有必要添加spring-boot-starter-web。在gateway当中有三个重要的元素他们分别是:
- Route 是最核心的路由元素,它定义了ID,目标URI ,predicates的集合与filter的集合,如果Predicate聚合返回真,则匹配该路由
- Predicate 基于java8的函数接口Predicate,其输入参数类型ServerWebExchange,其作用就是允许开发人员根据当前的http请求进行规则的匹配,比如说http请求头,请求时间等,匹配的结果将决定执行哪种路由
- Filter为GatewayFilter,它是由特殊的工厂构建,通过Filter可以在下层请求路由前后改变http请求与响应
我们编辑application.yaml,定义如下配置:
spring:
application:
name: gateway
cloud:
gateway:
routes:
- id: before_route
uri: http://www.baidu.com
predicates:
- Path=/baidu
server:
port: 8088
此时当我们访问路径中包含/baidu的,gateway将会帮我们转发至百度页面
2. 工作流程
在这里我贴上官网的一张图:
在这里我想结合源代码来说明其流程,这里面有个关键的类,叫
RoutePredicateHandlerMapping,我们可以发现这个类有如下特点:
public class RoutePredicateHandlerMapping extends AbstractHandlerMapping {
// ....省略部分代码
@Override
protected Mono<?> getHandlerInternal(ServerWebExchange exchange) {
// don't handle requests on management port if set and different than server port
if (this.managementPortType == DIFFERENT && this.managementPort != null
&& exchange.getRequest().getURI().getPort() == this.managementPort) {
return Mono.empty();
}
exchange.getAttributes().put(GATEWAY_HANDLER_MAPPER_ATTR, getSimpleName());
return lookupRoute(exchange)
// .log("route-predicate-handler-mapping", Level.FINER) //name this
.flatMap((Function<Route, Mono<?>>) r -> {
exchange.getAttributes().remove(GATEWAY_PREDICATE_ROUTE_ATTR);
if (logger.isDebugEnabled()) {
logger.debug(
"Mapping [" + getExchangeDesc(exchange) + "] to " + r);
}
exchange.getAttributes().put(GATEWAY_ROUTE_ATTR, r);
return Mono.just(webHandler);
}).switchIfEmpty(Mono.empty().then(Mono.fromRunnable(() -> {
exchange.getAttributes().remove(GATEWAY_PREDICATE_ROUTE_ATTR);
if (logger.isTraceEnabled()) {
logger.trace("No RouteDefinition found for ["
+ getExchangeDesc(exchange) + "]");
}
})));
}
//...省略部分代码
}
- 此类继承了AbstractHandlerMapping,注意这里的是reactive包下的,也就是webflux提供的handlermapping,其作用等同于webmvc的handlermapping,其作用是将请求映射找到对应的handler来处理。
- 在这里处理的关键就是先寻找合适的route,关键的方法为lookupRoute():
protected Mono<Route> lookupRoute(ServerWebExchange exchange) {
return this.routeLocator.getRoutes()
// individually filter routes so that filterWhen error delaying is not a
// problem
.concatMap(route -> Mono.just(route).filterWhen(r -> {
// add the current route we are testing
exchange.getAttributes().put(GATEWAY_PREDICATE_ROUTE_ATTR, r.getId());
return r.getPredicate().apply(exchange);
})
// instead of immediately stopping main flux due to error, log and
// swallow it
.doOnError(e -> logger.error(
"Error applying predicate for route: " + route.getId(),
e))
.onErrorResume(e -> Mono.empty()))
// .defaultIfEmpty() put a static Route not found
// or .switchIfEmpty()
// .switchIfEmpty(Mono.<Route>empty().log("noroute"))
.next()
// TODO: error handling
.map(route -> {
if (logger.isDebugEnabled()) {
logger.debug("Route matched: " + route.getId());
}
validateRoute(route, exchange);
return route;
});
/*
* TODO: trace logging if (logger.isTraceEnabled()) {
* logger.trace("RouteDefinition did not match: " + routeDefinition.getId()); }
*/
}
- 其中RouteLocator的接口作用是获取Route定义,那么在GatewayAutoConfiguaration里有相关的配置,大家可自行查阅:
@Bean
public RouteLocator routeDefinitionRouteLocator(GatewayProperties properties,
List<GatewayFilterFactory> GatewayFilters,
List<RoutePredicateFactory> predicates,
RouteDefinitionLocator routeDefinitionLocator,
@Qualifier("webFluxConversionService") ConversionService conversionService) {
return new RouteDefinitionRouteLocator(routeDefinitionLocator, predicates,
GatewayFilters, properties, conversionService);
}
- 然后在注释add the current route we are testing处可以得到一个结论,其是根据Predicate的声明条件过滤出合适的Route
- 最终拿到FilteringWebHandler作为它的返回值,这个类是真正意义上处理请求的类,它实现了webflux提供的WebHandler接口:
public class FilteringWebHandler implements WebHandler {
//.....省略其它代码
@Override
public Mono<Void> handle(ServerWebExchange exchange) {
//拿到当前的route
Route route = exchange.getRequiredAttribute(GATEWAY_ROUTE_ATTR);
//获取所有的gatewayFilter
List<GatewayFilter> gatewayFilters = route.getFilters();
//获取全局过滤器
List<GatewayFilter> combined = new ArrayList<>(this.globalFilters);
combined.addAll(gatewayFilters);
// TODO: needed or cached?
AnnotationAwareOrderComparator.sort(combined);
if (logger.isDebugEnabled()) {
logger.debug("Sorted gatewayFilterFactories: " + combined);
}
//交给默认的过滤器链执行所有的过滤操作
return new DefaultGatewayFilterChain(combined).filter(exchange);
}
//....省略其它代码
}
在这里可以看到它的实际处理方式是委派给过滤器链进行处理请求操作的
3. Predicate
Spring Cloud Gateway包含许多内置的Predicate Factory。所有的Predicate都匹配HTTP请求的不同属性。如果配置类多个Predicate, 那么必须满足所有的predicate才可以,官网上列举的内置的Predicate,我在这里不做过多的说明,请大家参考:地址,predicate的实现可以在
org.springframework.cloud.gateway.handler.predicate的包下找到。
3.1、自定义Predicate
先改一下application.yaml中的配置:
spring:
application:
name: gateway
cloud:
gateway:
routes:
- id: before_route
uri: http://www.baidu.com
predicates:
- Number=1
默认命名规则:名称RoutePredicateFactory,在这里我们可以看到如下代码规则用以解析Predicate的名称,该代码在NameUtils当中:
public static String normalizeRoutePredicateName(
Class<? extends RoutePredicateFactory> clazz) {
return removeGarbage(clazz.getSimpleName()
.replace(RoutePredicateFactory.class.getSimpleName(), ""));
}
那么在这里我们就按照如上规则建立对应的
NumberRoutePredicateFactory,代码如下:
@Component
public class NumberRoutePredicateFactory extends AbstractRoutePredicateFactory<NumberRoutePredicateFactory.Config> {
public NumberRoutePredicateFactory() {
super(Config.class);
}
@Override
public List<String> shortcutFieldOrder() {
return Arrays.asList("number");
}
@Override
public ShortcutType shortcutType() {
return ShortcutType.GATHER_LIST;
}
@Override
public Predicate<ServerWebExchange> apply(Config config) {
return new GatewayPredicate() {
@Override
public boolean test(ServerWebExchange serverWebExchange) {
String number = serverWebExchange.getRequest().getQueryParams().getFirst("number");
return config.number == Integer.parseInt(number);
}
};
}
public static class Config {
private int number;
public int getNumber() {
return number;
}
public void setNumber(int number) {
this.number = number;
}
}
}
- 该类可以继承AbstractRoutePredicateFactory,同时需要注册为spring的Bean
- 在此类当中按照规范来讲,需要定义一个内部类,该类的作用用于封装application.yaml中的配置,Number=1这个配置会按照规则进行封装,这个规则由以下几项决定:ShortcutType,该值是枚举类型,分别是DEFAULT :按照shortcutFieldOrder顺序依次赋值GATHER_LIST:shortcutFiledOrder只能有一个值,如果参数有多个拼成一个集合GATHER_LIST_TAIL_FLAG:shortcutFiledOrder只能有两个值,其中最后一个值为true或者false,其余的值变成一个集合付给第一个值shortcutFieldOrder,这个值决定了Config中配置的属性,配置的参数都会被封装到该属性当中
4. Filter
Gateway中的filter可以分为(GlobalFilter)全局过滤器与普通过滤器,过滤器可以在路由到代理服务的前后改变请求与响应。在这里我会列举两个常见的filter给大家用作参考:
4.1、负载均衡的实现
与zuul类似,Gateway也可以作为服务端的负载均衡,那么负载均衡的处理关键就是与Ribbon集成,那么Gateway是利用GlobalFilter进行实现的,它的实现类是LoadBalancerClientFilter:
public class LoadBalancerClientFilter implements GlobalFilter, Ordered {
protected final LoadBalancerClient loadBalancer;
private LoadBalancerProperties properties;
//....
@Override
@SuppressWarnings("Duplicates")
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
// preserve the original url
addOriginalRequestUrl(exchange, url);
log.trace("LoadBalancerClientFilter url before: " + url);
//选择一个服务实例
final ServiceInstance instance = choose(exchange);
if (instance == null) {
throw NotFoundException.create(properties.isUse404(),
"Unable to find instance for " + url.getHost());
}
URI uri = exchange.getRequest().getURI();
// if the `lb:<scheme>` mechanism was used, use `<scheme>` as the default,
// if the loadbalancer doesn't provide one.
//判断协议类型
String overrideScheme = instance.isSecure() ? "https" : "http";
if (schemePrefix != null) {
overrideScheme = url.getScheme();
}
//重构uri地址
URI requestUrl = loadBalancer.reconstructURI(
new DelegatingServiceInstance(instance, overrideScheme), uri);
//...
return chain.filter(exchange);
}
}
在这里我们可以看到这里它是基于Spring-Cloud-Commons规范里的LoadBalanceClient包装实现的。
4.2、集成Hystrix
Gateway同样也可以和Hystrix进行集成,这里面的关键类是
HystrixGatewayFilterFactory,这里面的关键是RouteHystrixCommand该类继承了HystrixObservableCommand:
@Override
protected Observable<Void> construct() {
// 执行过滤器链
return RxReactiveStreams.toObservable(this.chain.filter(exchange));//1
}
@Override
protected Observable<Void> resumeWithFallback() {
if (this.fallbackUri == null) {
return super.resumeWithFallback();
}
// TODO: copied from RouteToRequestUrlFilter
URI uri = exchange.getRequest().getURI();
// TODO: assume always?
boolean encoded = containsEncodedParts(uri);
URI requestUrl = UriComponentsBuilder.fromUri(uri).host(null).port(null)
.uri(this.fallbackUri).scheme(null).build(encoded).toUri();//2
exchange.getAttributes().put(GATEWAY_REQUEST_URL_ATTR, requestUrl);
addExceptionDetails();
ServerHttpRequest request = this.exchange.getRequest().mutate()
.uri(requestUrl).build();
ServerWebExchange mutated = exchange.mutate().request(request).build();
return RxReactiveStreams.toObservable(getDispatcherHandler().handle(mutated));//3
}
- 在代码1处会执行滤器链,写到此处的代码会被统一加上hystrix的保护
- 在代码2处再是执行回退的方法,根据fallbackUri构建一个回退请求地址
- 在代码3处获取WebFlux的总控制器DispatcherHandler进行回退地址的处理
5、服务发现
服务发现对于Gateway来说也是个非常重要的内容,Gateway在这里定义了一个核心接口叫做:RouteDefinitionLocator,这个接口用于获取Route的定义,服务发现的机制实现了该接口:
public class DiscoveryClientRouteDefinitionLocator implements RouteDefinitionLocator {
@Override
public Flux<RouteDefinition> getRouteDefinitions() {
//....省略部分代码
return Flux.fromIterable(discoveryClient.getServices())//获取所有服务
.map(discoveryClient::getInstances) //映射转换所有服务实例
.filter(instances -> !instances.isEmpty()) //过滤出不为空的服务实例
.map(instances -> instances.get(0)).filter(includePredicate)//根据properites里的include表达式过滤实例
.map(instance -> {
/*
构建Route的定义
*/
String serviceId = instance.getServiceId();
RouteDefinition routeDefinition = new RouteDefinition();
routeDefinition.setId(this.routeIdPrefix + serviceId);
String uri = urlExpr.getValue(evalCtxt, instance, String.class);
routeDefinition.setUri(URI.create(uri));
final ServiceInstance instanceForEval = new DelegatingServiceInstance(
instance, properties);
//添加Predicate
for (PredicateDefinition original : this.properties.getPredicates()) {
PredicateDefinition predicate = new PredicateDefinition();
predicate.setName(original.getName());
for (Map.Entry<String, String> entry : original.getArgs()
.entrySet()) {
String value = getValueFromExpr(evalCtxt, parser,
instanceForEval, entry);
predicate.addArg(entry.getKey(), value);
}
routeDefinition.getPredicates().add(predicate);
}
//添加filter
for (FilterDefinition original : this.properties.getFilters()) {
FilterDefinition filter = new FilterDefinition();
filter.setName(original.getName());
for (Map.Entry<String, String> entry : original.getArgs()
.entrySet()) {
String value = getValueFromExpr(evalCtxt, parser,
instanceForEval, entry);
filter.addArg(entry.getKey(), value);
}
routeDefinition.getFilters().add(filter);
}
return routeDefinition;
});
}
}
由此我们可以知道,这里面利用DiscoveryClient获取所有的服务实例并将每个实例构建为一个Route,不过在此之前,在自动装配的类
GatewayDiscoveryClientAutoConfiguration里已经配置了默认的Predicate与Filter,它会预先帮我们配置默认的Predicate与Filter:
public static List<PredicateDefinition> initPredicates() {
ArrayList<PredicateDefinition> definitions = new ArrayList<>();
// TODO: add a predicate that matches the url at /serviceId?
// add a predicate that matches the url at /serviceId/**
PredicateDefinition predicate = new PredicateDefinition();
predicate.setName(normalizeRoutePredicateName(PathRoutePredicateFactory.class));
predicate.addArg(PATTERN_KEY, "'/'+serviceId+'/**'");
definitions.add(predicate);
return definitions;
}
public static List<FilterDefinition> initFilters() {
ArrayList<FilterDefinition> definitions = new ArrayList<>();
// add a filter that removes /serviceId by default
FilterDefinition filter = new FilterDefinition();
filter.setName(normalizeFilterFactoryName(RewritePathGatewayFilterFactory.class));
String regex = "'/' + serviceId + '/(?<remaining>.*)'";
String replacement = "'/${remaining}'";
filter.addArg(REGEXP_KEY, regex);
filter.addArg(REPLACEMENT_KEY, replacement);
definitions.add(filter);
return definitions;
}
这里面主要会根据ServiceId构建为 Path=/serviceId/**的Predicate和路由至对应服务前把ServiceId去掉的filter
6、总结
根据上述说明,我仅仅选取了两个比较典型意义的Predicate与Filter代码进行说明,由于官网上没有说明自定义Predicate,我在这里索性写了个简单的例子,那么自定义Filter的例子可以参考官网地址:
这里需要吐槽一下官方 什么时候能把TODO补充完整的呢?
Gateway是基于Webflux实现的,它通过扩展HandlerMapping与WebHandler来处理用户的请求,先通过Predicate定位到Router然后在经过FilterChain的过滤处理,最后定位到下层服务。同时官方给我们提供了许多Prdicate与Filter,比如说限流的。从这点来说它的功能比zuul还强大呢,zuul里有的服务发现,断路保护等,Gateway分别通过GlobalFilter与Filter来实现。
最后至于Gateway能普及到什么样的程度,亦或者能不能最终成为统一的网关标准,这个我也不能再这里有所保证,那么就交给时间来证明吧。
相关推荐
- 教你把多个视频合并成一个视频的方法
-
一.情况介绍当你有一个m3u8文件和一个目录,目录中有连续的视频片段,这些片段可以连成一段完整的视频。m3u8文件打开后像这样:m3u8文件,可以理解为播放列表,里面是播放视频片段的顺序。视频片段像这...
- 零代码编程:用kimichat合并一个文件夹下的多个文件
-
一个文件夹里面有很多个srt字幕文件,如何借助kimichat来自动批量合并呢?在kimichat对话框中输入提示词:你是一个Python编程专家,完成如下的编程任务:这个文件夹:D:\downloa...
- Java APT_java APT 生成代码
-
JavaAPT(AnnotationProcessingTool)是一种在Java编译阶段处理注解的工具。APT会在编译阶段扫描源代码中的注解,并根据这些注解生成代码、资源文件或其他输出,...
- Unit Runtime:一键运行 AI 生成的代码,或许将成为你的复制 + 粘贴神器
-
在我们构建了UnitMesh架构之后,以及对应的demo之后,便着手于实现UnitMesh架构。于是,我们就继续开始UnitRuntime,以用于直接运行AI生成的代码。PS:...
- 挣脱臃肿的枷锁:为什么说Vert.x是Java开发者手中的一柄利剑?
-
如果你是一名Java开发者,那么你的职业生涯几乎无法避开Spring。它如同一位德高望重的老国王,统治着企业级应用开发的大片疆土。SpringBoot的约定大于配置、SpringCloud的微服务...
- 五年后,谷歌还在全力以赴发展 Kotlin
-
作者|FredericLardinois译者|Sambodhi策划|Tina自2017年谷歌I/O全球开发者大会上,谷歌首次宣布将Kotlin(JetBrains开发的Ja...
- kotlin和java开发哪个好,优缺点对比
-
Kotlin和Java都是常见的编程语言,它们有各自的优缺点。Kotlin的优点:简洁:Kotlin程序相对于Java程序更简洁,可以减少代码量。安全:Kotlin在类型系统和空值安全...
- 移动端架构模式全景解析:从MVC到MVVM,如何选择最佳设计方案?
-
掌握不同架构模式的精髓,是构建可维护、可测试且高效移动应用的关键。在移动应用开发中,选择合适的软件架构模式对项目的可维护性、可测试性和团队协作效率至关重要。随着应用复杂度的增加,一个良好的架构能够帮助...
- 颜值非常高的XShell替代工具Termora,不一样的使用体验!
-
Termora是一款面向开发者和运维人员的跨平台SSH终端与文件管理工具,支持Windows、macOS及Linux系统,通过一体化界面简化远程服务器管理流程。其核心定位是解决多平台环境下远程连接、文...
- 预处理的底层原理和预处理编译运行异常的解决方案
-
若文章对您有帮助,欢迎关注程序员小迷。助您在编程路上越走越好![Mac-10.7.1LionIntel-based]Q:预处理到底干了什么事情?A:预处理,顾名思义,预先做的处理。源代码中...
- 为“架构”再建个模:如何用代码描述软件架构?
-
在架构治理平台ArchGuard中,为了实现对架构的治理,我们需要代码+模型描述所要处理的内容和数据。所以,在ArchGuard中,我们有了代码的模型、依赖的模型、变更的模型等,剩下的两个...
- 深度解析:Google Gemma 3n —— 移动优先的轻量多模态大模型
-
2025年6月,Google正式发布了Gemma3n,这是一款能够在2GB内存环境下运行的轻量级多模态大模型。它延续了Gemma家族的开源基因,同时在架构设计上大幅优化,目标是让...
- 比分网开发技术栈与功能详解_比分网有哪些
-
一、核心功能模块一个基本的比分网通常包含以下模块:首页/总览实时比分看板:滚动展示所有正在进行的比赛,包含比分、比赛时间、红黄牌等关键信息。热门赛事/焦点战:突出显示重要的、关注度高的比赛。赛事导航...
- 设计模式之-生成器_一键生成设计
-
一、【概念定义】——“分步构建复杂对象,隐藏创建细节”生成器模式(BuilderPattern):一种“分步构建型”创建型设计模式,它将一个复杂对象的构建与其表示分离,使得同样的构建过程可以创建...
- 构建第一个 Kotlin Android 应用_kotlin简介
-
第一步:安装AndroidStudio(推荐IDE)AndroidStudio是官方推荐的Android开发集成开发环境(IDE),内置对Kotlin的完整支持。1.下载And...
- 一周热门
-
-
【验证码逆向专栏】vaptcha 手势验证码逆向分析
-
Psutil + Flask + Pyecharts + Bootstrap 开发动态可视化系统监控
-
一个解决支持HTML/CSS/JS网页转PDF(高质量)的终极解决方案
-
再见Swagger UI 国人开源了一款超好用的 API 文档生成框架,真香
-
网页转成pdf文件的经验分享 网页转成pdf文件的经验分享怎么弄
-
C++ std::vector 简介
-
飞牛OS入门安装遇到问题,如何解决?
-
系统C盘清理:微信PC端文件清理,扩大C盘可用空间步骤
-
10款高性能NAS丨双十一必看,轻松搞定虚拟机、Docker、软路由
-
python使用fitz模块提取pdf中的图片
-
- 最近发表
- 标签列表
-
- python判断字典是否为空 (50)
- crontab每周一执行 (48)
- aes和des区别 (43)
- bash脚本和shell脚本的区别 (35)
- canvas库 (33)
- dataframe筛选满足条件的行 (35)
- gitlab日志 (33)
- lua xpcall (36)
- blob转json (33)
- python判断是否在列表中 (34)
- python html转pdf (36)
- 安装指定版本npm (37)
- idea搜索jar包内容 (33)
- css鼠标悬停出现隐藏的文字 (34)
- linux nacos启动命令 (33)
- gitlab 日志 (36)
- adb pull (37)
- python判断元素在不在列表里 (34)
- python 字典删除元素 (34)
- vscode切换git分支 (35)
- python bytes转16进制 (35)
- grep前后几行 (34)
- hashmap转list (35)
- c++ 字符串查找 (35)
- mysql刷新权限 (34)