Spring Cloud Gateway 之动态路由

news2024/11/18 9:37:02

前言

熟悉 Spring Cloud Gateway 的人都知道 Gateway 提供了鉴权、路由等功能,本篇我们重点分析的是 Gateway 的动态路由功能。

Gateway Actuator API 方法源码解析

Spring Cloud Gateway 的 Actuator 端点允许监视 Spring Cloud Gateway 应用程序并与之交互,Gateway 依赖中默认包含了路由端点操作,我们引入 spring-boot-starter-actuator。

Gateway 端点开启方式如下:

management.endpoint.gateway.enabled=true
management.endpoints.web.exposure.include=gateway

我们在 IDEA 中按住 Ctrl,用鼠标点击 enable 就可以看 Gateway 端点的源码,如下:

在这里插入图片描述

GatewayControllerEndpoint 源码解析

GatewayControllerEndpoint 主要提供了三个方法,作用如下:

  • routesdef():获取路由定义信息。
  • routes():获取网关已经加载的所有路由信息。
  • route(@PathVariable String id):根据路由 id 获取路由信息。
@RestControllerEndpoint(
    id = "gateway"
)
public class GatewayControllerEndpoint extends AbstractGatewayControllerEndpoint {
    public GatewayControllerEndpoint(List<GlobalFilter> globalFilters, List<GatewayFilterFactory> gatewayFilters, List<RoutePredicateFactory> routePredicates, RouteDefinitionWriter routeDefinitionWriter, RouteLocator routeLocator, RouteDefinitionLocator routeDefinitionLocator) {
        super(routeDefinitionLocator, globalFilters, gatewayFilters, routePredicates, routeDefinitionWriter, routeLocator);
    }
	
	//获取路由定义信息
    @GetMapping({"/routedefinitions"})
    public Flux<RouteDefinition> routesdef() {
        return this.routeDefinitionLocator.getRouteDefinitions();
    }
	
	//获取网关已经加载的所有路由信息
    @GetMapping({"/routes"})
    public Flux<Map<String, Object>> routes() {
        return this.routeLocator.getRoutes().map(this::serialize);
    }

    Map<String, Object> serialize(Route route) {
        HashMap<String, Object> r = new HashMap();
        r.put("route_id", route.getId());
        r.put("uri", route.getUri().toString());
        r.put("order", route.getOrder());
        r.put("predicate", route.getPredicate().toString());
        if (!CollectionUtils.isEmpty(route.getMetadata())) {
            r.put("metadata", route.getMetadata());
        }

        ArrayList<String> filters = new ArrayList();

        for(int i = 0; i < route.getFilters().size(); ++i) {
            GatewayFilter gatewayFilter = (GatewayFilter)route.getFilters().get(i);
            filters.add(gatewayFilter.toString());
        }

        r.put("filters", filters);
        return r;
    }

	//根据路由id 获取路由信息
    @GetMapping({"/routes/{id}"})
    public Mono<ResponseEntity<Map<String, Object>>> route(@PathVariable String id) {
        return this.routeLocator.getRoutes().filter((route) -> {
            return route.getId().equals(id);
        }).singleOrEmpty().map(this::serialize).map(ResponseEntity::ok).switchIfEmpty(Mono.just(ResponseEntity.notFound().build()));
    }
}

获取路由信息接口演示如下:

在这里插入图片描述

AbstractGatewayControllerEndpoint 源码解析

AbstractGatewayControllerEndpoint 主要提供了一下功能:

  • refresh():刷新路由。
  • refresh():获取全局过滤器。
  • refresh():获取路由过滤器。
  • refresh():获取断言工厂。
  • refresh():保存路由信息。
  • refresh():删除路由信息。
  • refresh():根据路由id 获取组合过滤器。
public class AbstractGatewayControllerEndpoint implements ApplicationEventPublisherAware {
    private static final Log log = LogFactory.getLog(GatewayControllerEndpoint.class);
    protected RouteDefinitionLocator routeDefinitionLocator;
    protected List<GlobalFilter> globalFilters;
    protected List<GatewayFilterFactory> GatewayFilters;
    protected List<RoutePredicateFactory> routePredicates;
    protected RouteDefinitionWriter routeDefinitionWriter;
    protected RouteLocator routeLocator;
    protected ApplicationEventPublisher publisher;

    public AbstractGatewayControllerEndpoint(RouteDefinitionLocator routeDefinitionLocator, List<GlobalFilter> globalFilters, List<GatewayFilterFactory> gatewayFilters, List<RoutePredicateFactory> routePredicates, RouteDefinitionWriter routeDefinitionWriter, RouteLocator routeLocator) {
        this.routeDefinitionLocator = routeDefinitionLocator;
        this.globalFilters = globalFilters;
        this.GatewayFilters = gatewayFilters;
        this.routePredicates = routePredicates;
        this.routeDefinitionWriter = routeDefinitionWriter;
        this.routeLocator = routeLocator;
    }

    public void setApplicationEventPublisher(ApplicationEventPublisher publisher) {
        this.publisher = publisher;
    }

	//刷新路由
    @PostMapping({"/refresh"})
    public Mono<Void> refresh() {
        this.publisher.publishEvent(new RefreshRoutesEvent(this));
        return Mono.empty();
    }

	//获取全局过滤器
    @GetMapping({"/globalfilters"})
    public Mono<HashMap<String, Object>> globalfilters() {
        return this.getNamesToOrders(this.globalFilters);
    }

	//获取路由过滤器
    @GetMapping({"/routefilters"})
    public Mono<HashMap<String, Object>> routefilers() {
        return this.getNamesToOrders(this.GatewayFilters);
    }

	//获取断言工厂
    @GetMapping({"/routepredicates"})
    public Mono<HashMap<String, Object>> routepredicates() {
        return this.getNamesToOrders(this.routePredicates);
    }

    private <T> Mono<HashMap<String, Object>> getNamesToOrders(List<T> list) {
        return Flux.fromIterable(list).reduce(new HashMap(), this::putItem);
    }

    private HashMap<String, Object> putItem(HashMap<String, Object> map, Object o) {
        Integer order = null;
        if (o instanceof Ordered) {
            order = ((Ordered)o).getOrder();
        }

        map.put(o.toString(), order);
        return map;
    }
	
	//保存路由信息
    @PostMapping({"/routes/{id}"})
    public Mono<ResponseEntity<Object>> save(@PathVariable String id, @RequestBody RouteDefinition route) {
        return Mono.just(route).filter(this::validateRouteDefinition).flatMap((routeDefinition) -> {
            return this.routeDefinitionWriter.save(Mono.just(routeDefinition).map((r) -> {
                r.setId(id);
                log.debug("Saving route: " + route);
                return r;
            })).then(Mono.defer(() -> {
                return Mono.just(ResponseEntity.created(URI.create("/routes/" + id)).build());
            }));
        }).switchIfEmpty(Mono.defer(() -> {
            return Mono.just(ResponseEntity.badRequest().build());
        }));
    }

    private boolean validateRouteDefinition(RouteDefinition routeDefinition) {
        boolean hasValidFilterDefinitions = routeDefinition.getFilters().stream().allMatch((filterDefinition) -> {
            return this.GatewayFilters.stream().anyMatch((gatewayFilterFactory) -> {
                return filterDefinition.getName().equals(gatewayFilterFactory.name());
            });
        });
        boolean hasValidPredicateDefinitions = routeDefinition.getPredicates().stream().allMatch((predicateDefinition) -> {
            return this.routePredicates.stream().anyMatch((routePredicate) -> {
                return predicateDefinition.getName().equals(routePredicate.name());
            });
        });
        log.debug("FilterDefinitions valid: " + hasValidFilterDefinitions);
        log.debug("PredicateDefinitions valid: " + hasValidPredicateDefinitions);
        return hasValidFilterDefinitions && hasValidPredicateDefinitions;
    }
	
	//删除路由信息
    @DeleteMapping({"/routes/{id}"})
    public Mono<ResponseEntity<Object>> delete(@PathVariable String id) {
        return this.routeDefinitionWriter.delete(Mono.just(id)).then(Mono.defer(() -> {
            return Mono.just(ResponseEntity.ok().build());
        })).onErrorResume((t) -> {
            return t instanceof NotFoundException;
        }, (t) -> {
            return Mono.just(ResponseEntity.notFound().build());
        });
    }
	
	//根据路由id 获取组合过滤器
    @GetMapping({"/routes/{id}/combinedfilters"})
    public Mono<HashMap<String, Object>> combinedfilters(@PathVariable String id) {
        return this.routeLocator.getRoutes().filter((route) -> {
            return route.getId().equals(id);
        }).reduce(new HashMap(), this::putItem);
    }
}

获取全局过滤器演示如下:

在这里插入图片描述
其他接口不在一一演示。

通过 GatewayControllerEndpoint 和 AbstractGatewayControllerEndpoint 类的源码,我们可以知道 Gateway 提供获取路由信息、过滤器、断言等接口,这里面我们重点关注了的关于路由部分的,也是源码中涉及较多的,这两个类的源码提供了路由的增删改查及刷新的接口。

AbstractGatewayControllerEndpoint#save 方法源码分析

上面我们分析到 Gateway 源码提供了完整的增删改查及刷新的接口,我们重点来分析一下 Gateway 的新增路由的接口,也就是 AbstractGatewayControllerEndpoint#save 方法,该方法主要逻辑如下:

  • 校验新增路由的入参,入参不合法直接返回失败。
  • 调用 RouteDefinitionWriter 保存路由(实际调用的是 InMemoryRouteDefinitionRepository#save 方法)。
//org.springframework.cloud.gateway.actuate.AbstractGatewayControllerEndpoint#save
@PostMapping({"/routes/{id}"})
public Mono<ResponseEntity<Object>> save(@PathVariable String id, @RequestBody RouteDefinition route) {
	//this::validateRouteDefinition 路由参数校验
	return Mono.just(route).filter(this::validateRouteDefinition).flatMap((routeDefinition) -> {
		//this.routeDefinitionWriter.save 调用routeDefinitionWriter 进行保存
		return this.routeDefinitionWriter.save(Mono.just(routeDefinition).map((r) -> {
			r.setId(id);
			log.debug("Saving route: " + route);
			return r;
		})).then(Mono.defer(() -> {
			return Mono.just(ResponseEntity.created(URI.create("/routes/" + id)).build());
		}));
	}).switchIfEmpty(Mono.defer(() -> {
		return Mono.just(ResponseEntity.badRequest().build());
	}));
}

private boolean validateRouteDefinition(RouteDefinition routeDefinition) {
	//是否有重复的过滤器
	boolean hasValidFilterDefinitions = routeDefinition.getFilters().stream().allMatch((filterDefinition) -> {
		return this.GatewayFilters.stream().anyMatch((gatewayFilterFactory) -> {
			return filterDefinition.getName().equals(gatewayFilterFactory.name());
		});
	});
	//是否有重复的断言
	boolean hasValidPredicateDefinitions = routeDefinition.getPredicates().stream().allMatch((predicateDefinition) -> {
		return this.routePredicates.stream().anyMatch((routePredicate) -> {
			return predicateDefinition.getName().equals(routePredicate.name());
		});
	});
	log.debug("FilterDefinitions valid: " + hasValidFilterDefinitions);
	log.debug("PredicateDefinitions valid: " + hasValidPredicateDefinitions);
	return hasValidFilterDefinitions && hasValidPredicateDefinitions;
}


RouteDefinitionWriter 接口源码分析

我们上面分析到保存路由调用了的 RouteDefinitionWriter 接口的方法,RouteDefinitionWriter 中定义了保存路由和删除路由的方法,上面提到的 InMemoryRouteDefinitionRepository 间接实现了 RouteDefinitionWriter 接口。


public interface RouteDefinitionWriter {
	
	//保存路由
    Mono<Void> save(Mono<RouteDefinition> route);
	
	//删除路由
    Mono<Void> delete(Mono<String> routeId);
}

InMemoryRouteDefinitionRepository#save 方法源码分析

InMemoryRouteDefinitionRepository#save 方法的逻辑很简单,就只是把路由信息存入到 Map 对象 routes 中,基于内存的操作,因此 Gateway 重启之后就会丢失。

//org.springframework.cloud.gateway.route.InMemoryRouteDefinitionRepository#save
public Mono<Void> save(Mono<RouteDefinition> route) {
	//private final Map<String, RouteDefinition> routes = Collections.synchronizedMap(new LinkedHashMap());
	return route.flatMap((r) -> {
		if (StringUtils.isEmpty(r.getId())) {
			return Mono.error(new IllegalArgumentException("id may not be empty"));
		} else {
			//存入 routes 中  本质是一个 map
			this.routes.put(r.getId(), r);
			return Mono.empty();
		}
	});
}



AbstractGatewayControllerEndpoint#refresh 方法源码分析

AbstractGatewayControllerEndpoint#refresh 路由刷新功能,逻辑很简单,使用了事件监听的机制,发布了一个路由刷新事件。


//org.springframework.cloud.gateway.actuate.AbstractGatewayControllerEndpoint#refresh
@PostMapping({"/refresh"})
public Mono<Void> refresh() {
	//发布路由刷新事件
	this.publisher.publishEvent(new RefreshRoutesEvent(this));
	return Mono.empty();
}


CachingRouteLocator#onApplicationEvent 方法源码分析

CachingRouteLocator#onApplicationEvent 方法订阅 RefreshRoutesEvent 事件,主要做了以下两件事:

  • 调用 CachingRouteLocator#fetch 方法。
  • 发布路由刷新完成事件。

//org.springframework.cloud.gateway.route.CachingRouteLocator#onApplicationEvent
public void onApplicationEvent(RefreshRoutesEvent event) {
	try {
		//调用 CachingRouteLocator#fetch 方法
		this.fetch().collect(Collectors.toList()).subscribe((list) -> {
			Flux.fromIterable(list).materialize().collect(Collectors.toList()).subscribe((signals) -> {
				//发布路由刷新完成事件
				this.applicationEventPublisher.publishEvent(new RefreshRoutesResultEvent(this));
				this.cache.put("routes", signals);
			}, (throwable) -> {
				this.handleRefreshError(throwable);
			});
		});
	} catch (Throwable var3) {
		this.handleRefreshError(var3);
	}

}

CachingRouteLocator#fetch 方法源码分析

CachingRouteLocator#fetch 方法调用了


//org.springframework.cloud.gateway.route.CachingRouteLocator#fetch
private Flux<Route> fetch() {
	return this.delegate.getRoutes().sort(AnnotationAwareOrderComparator.INSTANCE);
}

RouteDefinitionRouteLocator#getRoutes 方法源码分析

RouteDefinitionRouteLocator#getRoutes 方法主要逻辑如下:

  • 获取路由定义信息,调用 convertToRoute 方法将路由定义信息转换为路由信息 。
  • 将路由信息存入到 routes 中。
//org.springframework.cloud.gateway.route.RouteDefinitionRouteLocator#getRoutes
public Flux<Route> getRoutes() {
	//调用 convertToRoute 方法将路由定义信息转化为路由
	Flux<Route> routes = this.routeDefinitionLocator.getRouteDefinitions().map(this::convertToRoute);
	if (!this.gatewayProperties.isFailOnRouteDefinitionError()) {
		routes = routes.onErrorContinue((error, obj) -> {
			if (this.logger.isWarnEnabled()) {
				this.logger.warn("RouteDefinition id " + ((RouteDefinition)obj).getId() + " will be ignored. Definition has invalid configs, " + error.getMessage());
			}

		});
	}
	
	//路由存储 routes 中
	return routes.map((route) -> {
		if (this.logger.isDebugEnabled()) {
			this.logger.debug("RouteDefinition matched: " + route.getId());
		}

		return route;
	});
}


如有不正确的地方请各位指出纠正。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2066167.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

k8s学习(三十八) 使用OpenTelemetry+jaeger实现链路追踪

文章目录 前言一、安装jaeger二、安装cert-manager三、安装OpenTelemetry Operator四、配置 OpenTelemetry Collector五、配置 Instrumentation六、编写java示例程序并测试调用链跟踪 前言 OpenTelemetry 可以用于从应用程序收集数据。它是一组工具、API 和 SDK 集合&#xff…

《黑神话:悟空》Steam全球评价出炉:18个语言区好评率超90%

《黑神话&#xff1a;悟空》Steam在线人数已经不断打破纪录&#xff0c;连续三天刷榜&#xff0c;目前最高成绩超过241万。这个成绩也稳坐总榜第二&#xff0c;同时也是单机游戏的历史第一。 除了游玩人数之外&#xff0c;该作的评价口碑也非常出色&#xff0c;根据媒体汇总的数…

Flutter->`Flutter` 通过`ffi`调用`Rust`编译生成的产物.so文件(Android)和.a文件(iOS)接口方法

flutter_rust_ffi Flutter 通过ffi调用Rust编译生成的产物.so文件(Android)和.a文件(iOS)接口方法; 拾用本文您将获取以下技能: Rust编译.so文件的能力;Rust编译.a文件的能力;Flutter调用.so文件的能力;Flutter调用.a文件的能力; 附加Buff: Flutter环境安装指南;Rust环境安…

游戏行业如此竞争激烈,个人开发者是否仍存机会?

在当今这个数字化时代&#xff0c;游戏行业以其庞大的市场规模、高速的增长速度以及无限的创意空间&#xff0c;吸引了无数开发者投身其中。然而&#xff0c;随着技术的进步、资本的涌入以及大型游戏公司的强势扩张&#xff0c;游戏行业的竞争日益激烈&#xff0c;似乎形成了一…

选择合适系统

选择合适系统 原厂SDK系统 硬件兼容性 ⭐⭐⭐⭐⭐软件功能完善度 ⭐⭐⭐⭐⭐开发使用难度 ⭐⭐⭐⭐⭐烧写工具 全志自家烧录器。 TinaSDK-4.0 TF卡系统镜像 tina_v851se-tinyvision_uart0.img 默认TinaSDK编译出来 支持ADB 和默认SDK兼容性最好 tina-4.0_cameratest_ti…

使用知识图谱,大幅提升RAG准确性

大家好&#xff0c;图形检索—增强生成&#xff08;GraphRAG&#xff09;的发展势头日益强劲&#xff0c;已成为传统向量搜索检索方法的有力补充。这种方法利用图数据库的结构化特性&#xff0c;将数据组织为节点和关系&#xff0c;从而增强了检索信息的深度和上下文关联性。 知…

Stablediffusion有哪几种模型,小白入门必看!

前言 在Stable Diffusion中&#xff0c;模型有好几种&#xff0c;不同插件有不同的模型&#xff0c;分别作用于不同的功能。 今天卧龙君就带着大家一起来了解一下。 大模型&#xff1a;Stable Diffusion StableDiffusion大模型&#xff0c;可以理解为绘画风格集合&#xff…

16:【stm32】I2C的使用一:I2C片上外设的使用

I2C 1、片上外设1.1&#xff1a;寄存器与内部结构 2、通过I2C向外发送数据2.1&#xff1a;I2C的初始化2.1.1&#xff1a;初始化SCL和SDA2.1.2&#xff1a;使能时钟PCLK1&#xff08;APB1&#xff09;2.1.3&#xff1a;配置I2C1的参数 2.2&#xff1a;发送数据2.2.1&#xff1a;…

P2P 文件共享:现代网络中的高效文件传输

在互联网的世界中&#xff0c;不同应用程序的数据传输方法各异。P2P文件共享&#xff08;Peer-to-Peer File Sharing&#xff09; 作为一种高效的文件传输方式&#xff0c;使得用户可以在没有中央服务器的情况下直接进行文件交换。本文将详细介绍P2P文件共享的基本原理、优势及…

【通俗理解】CNN复杂度——卷积神经网络的计算成本解析

【通俗理解】CNN复杂度——卷积神经网络的计算成本解析 关键词提炼 #CNN复杂度 #卷积神经网络 #计算成本 #输入数据尺寸 #卷积核大小 #卷积核数量 #复杂度公式 第一节&#xff1a;CNN复杂度的类比与核心概念【尽可能通俗】 1.1 CNN复杂度的类比 CNN的复杂度就像是烹饪一道大…

引擎切换pdf识别简历分析

文章目录 1.EasyCode生成interview_history的crud1.在模板设置中手动指定逻辑删除的值2.生成代码&#xff0c;进行测试 2.PDF识别关键字1.引入依赖2.代码概览3.PDFUtil.java4.keyword1.EndType.java2.FlagIndex.java3.WordType.java4.KeyWordUtil.java 3.策略模式实现引擎切换&…

QT-window记事本

QT-window记事本 一、演示效果二、核心代码三、下载连接 一、演示效果 二、核心代码 #include <QMessageBox> #include <QFileDialog> #include <QDebug> #include <QProcess> #include <QDesktopServices> #include <QDateTime> #includ…

孙宇晨:区块链领域的坚韧领航者,以智慧铸就行业基石

​ 在区块链领域&#xff0c;孙宇晨以其卓越的智慧与不懈的韧性&#xff0c;成为行业内备受瞩目的领军人物。从创立波场 TRON 到引领去中心化的变革&#xff0c;孙宇晨始终以坚定的信念和独特的战略眼光推动着区块链技术的发展。 孙宇晨的成功不仅仅是因为他对技术的深入…

叉车驾驶员状态监控系统,司机身份安全识别,强化监管能力建设!

人脸识别技术作为人工智能领域的一个重要分支&#xff0c;已经广泛应用于安全识别、个人化推荐、社交网络等多个领域。其基于计算机视觉、图像处理、人脸检测、特征提取和人脸识别等先进技术&#xff0c;能够实现对人脸图像的精准分析和识别。在叉车驾驶场景中&#xff0c;AI人…

windows mfc webview2 接收html信息

webview2导入到mfc参考&#xff1a; windows vs2022 MFC使用webview2嵌入网页-CSDN博客 webview2与js交互参考&#xff1a;WebView2教程(基于C)【四】JS与C互访&#xff08;上&#xff09;_window.chrome.webview.postmessage-CSDN博客 一、JS端发送和接收 JS中&#xff0c;…

12代装win7影响性能吗?12代酷睿装win7关闭小核提高性能方法

12代酷睿装win7影响性能吗&#xff1f;12代酷睿装win7在性能上有点损耗&#xff0c;可以关闭小核提高性能。有些小朋友不知道怎么关闭上核来提高性能&#xff0c;下面电脑系统城小编就教大家12代酷睿装win7关闭核提高性能方法。 12代酷睿装win7说明&#xff1a;关闭小核解锁更多…

【分布式】分布式Session共享

这里通过SpringSession来实现Session的共享&#xff0c;Session数据存储在Redis中 SpringSession的操作指南&#xff1a; https://docs.spring.io/spring-session/docs/2.5.6/reference/html5/guides/boot-redis.html 导入相关的依赖 <dependency><groupId>org.s…

算法入门-递归3

第四部分&#xff1a;递归 143.重排链表&#xff08;中等&#xff09; 题目&#xff1a;给定一个单链表 L 的头节点 head &#xff0c;单链表 L 表示为&#xff1a; L0 → L1 → … → Ln - 1 → Ln 请将其重新排列后变为&#xff1a; L0 → Ln → L1 → Ln - 1 → L2 → …

问题-小技巧-win11状态栏卡住

目前我只知道治标不治本的办法&#xff0c;打开任务管理器&#xff0c;找到Windows资源管理器右键重新启动&#xff0c;就可以解决这个问题。 这个问题我觉得是Win11自己的问题&#xff0c;等有新的发现&#xff0c;会进行补充。

使用Hutool工具类轻松生成验证码

效果图&#xff1a; 引入依赖&#xff1a; <!--hutool工具包--> <dependency><groupId>cn.hutool</groupId><artifactId>hutool-all</artifactId><version>5.7.15</version> </dependency>核心代码 import cn.hutool.…