【源码】Spring Cloud Gateway 启动流程源码分析
以下分析以 spring-cloud-starter-gateway 4.1.0 源码为分析样本配置和启动类如果我们要使用 Spring Cloud Gateway需要在pom里引入如下依赖dependency groupIdorg.springframework.cloud/groupId artifactIdspring-cloud-starter-gateway/artifactId version4.1.0/version /dependencyspring-cloud-starter-gateway里的依赖如下dependencies dependency groupIdorg.springframework.cloud/groupId artifactIdspring-cloud-starter/artifactId /dependency dependency groupIdorg.springframework.cloud/groupId artifactIdspring-cloud-gateway-server/artifactId /dependency dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-webflux/artifactId /dependency dependency groupIdorg.springframework.cloud/groupId artifactIdspring-cloud-starter-loadbalancer/artifactId optionaltrue/optional /dependency /dependencies看上面引入了 spring-boot-starter-webflux为后面分析做铺垫除了引入依赖我们还需要有一个启动类如下import lombok.extern.slf4j.Slf4j; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.client.discovery.EnableDiscoveryClient; EnableDiscoveryClient Slf4j SpringBootApplication public class XXGatewayApp { public static void main(String[] args) { SpringApplication.run(XXGatewayApp.class, args); log.info(XX网关启动成功); } }启动 nettyserver在主类启动后是如何启动一个nettyserver的我们来分析一下跟踪启动类代码来到如下方法org.springframework.boot.SpringApplication#run(java.lang.String…)public ConfigurableApplicationContext run(String... args) { Startup startup SpringApplication.Startup.create(); if (this.registerShutdownHook) { shutdownHook.enableShutdownHookAddition(); } ... 省略代码... try { ApplicationArguments applicationArguments new DefaultApplicationArguments(args); ConfigurableEnvironment environment this.prepareEnvironment(listeners, bootstrapContext, applicationArguments); Banner printedBanner this.printBanner(environment); // A1 创建 ConfigurableApplicationContext context this.createApplicationContext(); context.setApplicationStartup(this.applicationStartup); this.prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner); // A2 触发创建server this.refreshContext(context); ... 省略代码... } catch (Throwable ex) { if (ex instanceof AbandonedRunException) { throw ex; } this.handleRunFailure(context, ex, listeners); throw new IllegalStateException(ex); } try { if (context.isRunning()) { listeners.ready(context, startup.ready()); } return context; } catch (Throwable ex) { if (ex instanceof AbandonedRunException) { throw ex; } else { this.handleRunFailure(context, ex, (SpringApplicationRunListeners)null); throw new IllegalStateException(ex); } } }A1这个方法会执行以下逻辑org.springframework.boot.WebApplicationType#deduceFromClasspath计算web容器类型而最上面的pom依赖引入了 spring-boot-starter-webfluxstatic WebApplicationType deduceFromClasspath() { if (ClassUtils.isPresent(org.springframework.web.reactive.DispatcherHandler, (ClassLoader)null) !ClassUtils.isPresent(org.springframework.web.servlet.DispatcherServlet, (ClassLoader)null) !ClassUtils.isPresent(org.glassfish.jersey.servlet.ServletContainer, (ClassLoader)null)) { return REACTIVE; } else { for(String className : SERVLET_INDICATOR_CLASSES) { if (!ClassUtils.isPresent(className, (ClassLoader)null)) { return NONE; } } return SERVLET; } }上面代码根据类路径中加入的依赖返回REACTIVE最终返回 org.springframework.boot.web.reactive.context.AnnotationConfigReactiveWebServerApplicationContext 对象创建websever接上面 A2方法会触发AnnotationConfigReactiveWebServerApplicationContext如下调用注AnnotationConfigReactiveWebServerApplicationContext父类是ReactiveWebServerApplicationContext#createWebServerprivate void createWebServer() { WebServerManager serverManager this.serverManager; if (serverManager null) { StartupStep createWebServer this.getApplicationStartup().start(spring.boot.webserver.create); String webServerFactoryBeanName this.getWebServerFactoryBeanName(); //B1 创建ReactiveWebServerFactory 创建server的核心点 ReactiveWebServerFactory webServerFactory this.getWebServerFactory(webServerFactoryBeanName); createWebServer.tag(factory, webServerFactory.getClass().toString()); boolean lazyInit this.getBeanFactory().getBeanDefinition(webServerFactoryBeanName).isLazyInit(); //B2 WebServerManager构造方法创建server this.serverManager new WebServerManager(this, webServerFactory, this::getHttpHandler, lazyInit); this.getBeanFactory().registerSingleton(webServerGracefulShutdown, new WebServerGracefulShutdownLifecycle(this.serverManager.getWebServer())); //B3 很绝的方法 this.getBeanFactory().registerSingleton(webServerStartStop, new WebServerStartStopLifecycle(this.serverManager)); createWebServer.end(); } this.initPropertySources(); }B1 方法很绕会从ReactiveWebServerFactoryConfiguration里去获得NettyReactiveWebServerFactory的bean定义而这个bean定义依赖 ReactorResourceFactory 代码如下Bean NettyReactiveWebServerFactory nettyReactiveWebServerFactory(ReactorResourceFactory resourceFactory, ObjectProviderNettyRouteProvider routes, ObjectProviderNettyServerCustomizer serverCustomizers) { NettyReactiveWebServerFactory serverFactory new NettyReactiveWebServerFactory(); serverFactory.setResourceFactory(resourceFactory); Stream var10000 routes.orderedStream(); Objects.requireNonNull(serverFactory); var10000.forEach((xva$0) - serverFactory.addRouteProviders(new NettyRouteProvider[]{xva$0})); serverFactory.getServerCustomizers().addAll(serverCustomizers.orderedStream().toList()); return serverFactory; }也就是说在容器返回NettyReactiveWebServerFactory 对象前会把ReactorResourceFactory 对象初始化完毕ReactorResourceFactory 这个类实现了InitializingBean我们看看afterPropertiesSet方法初始化内容public void start() { synchronized(this.lifecycleMonitor) { if (!this.isRunning()) { if (!this.useGlobalResources) { if (this.loopResources null) { this.manageLoopResources true; this.loopResources (LoopResources)this.loopResourcesSupplier.get(); } if (this.connectionProvider null) { this.manageConnectionProvider true; this.connectionProvider (ConnectionProvider)this.connectionProviderSupplier.get(); } } else { Assert.isTrue(this.loopResources null this.connectionProvider null, useGlobalResources is mutually exclusive with explicitly configured resources); // C1 HttpResources httpResources HttpResources.get(); if (this.globalResourcesConsumer ! null) { this.globalResourcesConsumer.accept(httpResources); } this.connectionProvider httpResources; this.loopResources httpResources; } this.running true; } } }C1方法最终执行的是 reactor.netty.resources.LoopResources#create(java.lang.String)static LoopResources create(String prefix) { if (((String)Objects.requireNonNull(prefix, prefix)).isEmpty()) { throw new IllegalArgumentException(Cannot use empty prefix); } else { return new DefaultLoopResources(prefix, DEFAULT_IO_SELECT_COUNT, DEFAULT_IO_WORKER_COUNT, true); } }是不是很熟悉了是创建的reactor.netty.resources.DefaultLoopResources 对象todo~~B2 方法会调用到如下方法org.springframework.boot.web.embedded.netty.NettyReactiveWebServerFactory#getWebServerpublic WebServer getWebServer(HttpHandler httpHandler) { // D1 创建 HttpServer HttpServer httpServer this.createHttpServer(); ReactorHttpHandlerAdapter handlerAdapter new ReactorHttpHandlerAdapter(httpHandler); NettyWebServer webServer this.createNettyWebServer(httpServer, handlerAdapter, this.lifecycleTimeout, this.getShutdown()); webServer.setRouteProviders(this.routeProviders); return webServer; }D1 方法会执行到如下方法 org.springframework.boot.web.embedded.netty.NettyReactiveWebServerFactory#createHttpServerprivate HttpServer createHttpServer() { HttpServer server HttpServer.create().bindAddress(this::getListenAddress); if (Ssl.isEnabled(this.getSsl())) { server this.customizeSslConfiguration(server); } if (this.getCompression() ! null this.getCompression().getEnabled()) { CompressionCustomizer compressionCustomizer new CompressionCustomizer(this.getCompression()); server compressionCustomizer.apply(server); } server server.protocol(this.listProtocols()).forwarded(this.useForwardHeaders); return this.applyCustomizers(server); }继续分析上面B3这个地方太绝了向容器中注入了一个 WebServerStartStopLifecycle这种类型的类会被框架中触发start方法触发一些列的start方法最终执行的是 reactor.netty.http.server.HttpServerBind父类 ---- HttpServer 父类 ----- reactor.netty.transport.ServerTransport的 bindNow方法 ---- bind方法。重要方法 reactor.netty.transport.ServerTransport#bindpublic Mono? extends DisposableServer bind() { CONF config (CONF)(this.configuration()); Objects.requireNonNull(config.bindAddress(), bindAddress); Mono? extends DisposableServer mono Mono.create((sink) - { SocketAddress local (SocketAddress)Objects.requireNonNull((SocketAddress)config.bindAddress().get(), Bind Address supplier returned null); if (local instanceof InetSocketAddress) { InetSocketAddress localInet (InetSocketAddress)local; if (localInet.isUnresolved()) { local AddressUtils.createResolved(localInet.getHostName(), localInet.getPort()); } } boolean isDomainSocket false; DisposableBind disposableServer; if (local instanceof DomainSocketAddress) { isDomainSocket true; disposableServer new UdsDisposableBind(sink, config, local); } else { disposableServer new InetDisposableBind(sink, config, local); } ConnectionObserver childObs new ChildObserver(config.defaultChildObserver().then(config.childObserver())); // E1 Acceptor acceptor new Acceptor(config.childEventLoopGroup(), config.channelInitializer(childObs, (SocketAddress)null, true), config.childOptions, config.childAttrs, isDomainSocket); // E2 TransportConnector.bind(config, new AcceptorInitializer(acceptor), local, isDomainSocket).subscribe(disposableServer); }); if (config.doOnBind() ! null) { mono mono.doOnSubscribe((s) - config.doOnBind().accept(config)); } return mono; }E1最终调用 reactor.netty.resources.DefaultLoopResources#cacheNioServerLoops 获得work线程池E2最终调用 reactor.netty.resources.DefaultLoopResources#cacheNioSelectLoops 获得boss线程池问题来了看reactor.netty.resources.LoopResources源码如果系统参数里没有配置reactor.netty.ioSelectCount则boss线程会和work线程池的AtomicReference在cacheNioSelectLoops 方法中返回同一对象这是不是会造成线程池共用可以参看 https://blog.csdn.net/qq_42651904/article/details/134561804 这篇文章理解那么你们生产环境会单独设置 reactor.netty.ioSelectCount 参数吗jvisualvm监控验证如果不设置reactor.netty.ioSelectCount 这个启动参数通过监控网关启动后线程名称是 reactor-http-nio-xx如果在启动的时候设置这个参数为1启动的线程不一样 reactor-http-select-xxpublic static void main(String[] args) { System.setProperty(reactor.netty.ioSelectCount, 1); SpringApplication.run(XXXGatewayApp.class, args); log.info(XXX网关启动成功); }设想一下如果代码写得不好在GlobalFilter有阻塞写法比如数据查询、redis查询加上高并发请求那么是不是会影响boss线程“接客”后面再通过压测的方式论证一下。

相关新闻

番茄小说下载器:10分钟搭建你的永久离线小说库

番茄小说下载器:10分钟搭建你的永久离线小说库

番茄小说下载器:10分钟搭建你的永久离线小说库 【免费下载链接】fanqienovel-downloader 下载番茄小说 项目地址: https://gitcode.com/gh_mirrors/fa/fanqienovel-downloader 你是否经历过在地铁上网络信号差无法追更心爱小说的烦恼?是否担心某天…

2026/10/26 21:48:47 阅读更多 →
为什么uber-apk-signer成为Android开发者的首选签名工具:技术原理与实践指南

为什么uber-apk-signer成为Android开发者的首选签名工具:技术原理与实践指南

为什么uber-apk-signer成为Android开发者的首选签名工具:技术原理与实践指南 【免费下载链接】uber-apk-signer A cli tool that helps signing and zip aligning single or multiple Android application packages (APKs) with either debug or provided release c…

2026/10/25 15:51:54 阅读更多 →
STDF Viewer终极指南:如何5分钟完成半导体测试数据分析

STDF Viewer终极指南:如何5分钟完成半导体测试数据分析

STDF Viewer终极指南:如何5分钟完成半导体测试数据分析 【免费下载链接】STDF-Viewer A free GUI tool to visualize STDF (semiconductor Standard Test Data Format) data files. 项目地址: https://gitcode.com/gh_mirrors/st/STDF-Viewer STDF Viewer是一…

2026/10/25 22:50:20 阅读更多 →

最新新闻

Electron桌面应用开发实战:从环境搭建到打包发布全流程

Electron桌面应用开发实战:从环境搭建到打包发布全流程

做前端这些年,我大部分时间都在跟浏览器标签页打交道,直到第一次把桌面应用跑起来,才发现原来自己一直被限制在地址栏和刷新按钮围起来的框框里。Electron这个词,最开始是在某段时间频繁出现在同事的讨论里,一个用HTML…

2026/10/26 12:00:21 阅读更多 →
滑盖智能锁T109深度解析:结构原理、安装调试与故障排查

滑盖智能锁T109深度解析:结构原理、安装调试与故障排查

简介:亚太天能T109滑盖智能锁产品说明书是一份PDF格式的官方技术文档,主要面向智能锁安装人员、终端用户及售后维护者,帮助快速掌握产品安装、配置与常见故障处理。全包仅1个PDF文件,大小4.37MB,内容结构清晰&#xff…

2026/10/25 22:36:10 阅读更多 →
PMSM弱磁控制原理与Simulink建模:高速AGV驱动系统设计指南

PMSM弱磁控制原理与Simulink建模:高速AGV驱动系统设计指南

上个月我在调试一台高速AGV的驱动系统,整车要求空载跑到8m/s,电机转速已经拉到额定点,驱动器的输出电压也顶到了母线电压极限附近。再往上踩速度指令,转速纹丝不动,三相电流反而开始震荡。那会儿我的第一反应就是&…

2026/10/26 4:52:12 阅读更多 →
C++核心知识点:从内存管理到STL的实战指南

C++核心知识点:从内存管理到STL的实战指南

C 这门语言我前前后后折腾了十几年,用它写过嵌入式驱动、做过游戏服务端,也搭过基础组件库。每次看到新人一头扎进"C 核心知识点"这个搜索词,我其实挺感慨的——这门语言的难点从来不是语法多,而是知识点之间的耦合太深…

2026/10/26 12:00:21 阅读更多 →
T109滑盖智能锁怎么装?安装调试与故障排查全攻略

T109滑盖智能锁怎么装?安装调试与故障排查全攻略

简介:亚太天能T109滑盖智能锁产品说明书以PDF格式发布,适合智能锁用户、安装人员与售后维修人员阅读。内容围绕T109系列指纹密码刷卡锁展开,先介绍前面板、后面板、锁体、机械钥匙、电池盒盖等部件名称与功能,再给出完整安装步骤&…

2026/10/26 5:00:37 阅读更多 →
LLM知识降熵:从信息垄断到理解权下放

LLM知识降熵:从信息垄断到理解权下放

你有没有过这种经历:翻开源码注释、论文摘要或者一份专业报告,每个字都认识,连在一起却像天书。想找人聊聊,身边要么没人懂,要么懂的懒得跟你解释。后来我试着把同一段内容丢给一个大语言模型,几秒钟之后&a…

2026/10/26 3:16:46 阅读更多 →

日新闻

周新闻

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/26 12:00:19 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/25 13:21:54 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/26 8:34:37 阅读更多 →