专栏首页Spring Cloud 微服务Spring NettyWebServer 启动运行机制
原创

Spring NettyWebServer 启动运行机制

本文主要介绍 Spring 框架中 NettyWebServer的启动运行机制。

1. 版本说明

源码分析基于:

  • spring-webflux:5.2.6
  • spring-boot:2.3.0

2. 创建过程

在运行 Spring Boot Reactive 应用时,需要在应用内启动一个 WebServer,那么 WebServer 的启动过程是怎样的呢?通过以下源码:

WebServerManager

WebServerManager(ReactiveWebServerApplicationContext applicationContext, ReactiveWebServerFactory factory,
        Supplier<HttpHandler> handlerSupplier, boolean lazyInit) {
    this.applicationContext = applicationContext;
    Assert.notNull(factory, "Factory must not be null");
    this.handler = new DelayedInitializationHttpHandler(handlerSupplier, lazyInit);
    this.webServer = factory.getWebServer(this.handler);
}

可以看出:WebServer 是由 ReactiveWebServerFactoryWebServerManager 实例化时创建的。

那么 ReactiveWebServerFactory 是怎么创建的呢?通过以下源码:

ReactiveWebServerFactoryConfiguration.EmbeddedNetty

@Bean
NettyReactiveWebServerFactory nettyReactiveWebServerFactory(ReactorResourceFactory resourceFactory,
        ObjectProvider<NettyRouteProvider> routes, ObjectProvider<NettyServerCustomizer> serverCustomizers) {
    NettyReactiveWebServerFactory serverFactory = new NettyReactiveWebServerFactory();
    serverFactory.setResourceFactory(resourceFactory);
    routes.orderedStream().forEach(serverFactory::addRouteProviders);
    serverFactory.getServerCustomizers().addAll(serverCustomizers.orderedStream().collect(Collectors.toList()));
    return serverFactory;
}

可以看出:在 ReactiveWebServerFactoryConfiguration.EmbeddedNetty 中声明了 NettyReactiveWebServerFactory。那么 ReactiveWebServerFactoryConfiguration.EmbeddedNetty 是如何启用的呢?通过以下源码:

ReactiveWebServerFactoryAutoConfiguration

@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(ReactiveHttpInputMessage.class)
@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.REACTIVE)
@EnableConfigurationProperties(ServerProperties.class)
@Import({ ReactiveWebServerFactoryAutoConfiguration.BeanPostProcessorsRegistrar.class,
                ReactiveWebServerFactoryConfiguration.EmbeddedTomcat.class,
                ReactiveWebServerFactoryConfiguration.EmbeddedJetty.class,
                ReactiveWebServerFactoryConfiguration.EmbeddedUndertow.class,
                ReactiveWebServerFactoryConfiguration.EmbeddedNetty.class })
public class ReactiveWebServerFactoryAutoConfiguration {...}

可以看出:在 ReactiveWebServerFactoryAutoConfiguration 中导入了 ReactiveWebServerFactoryConfiguration.EmbeddedNetty

那么 WebServerManager 是怎么创建的呢?通过以下源码:

ReactiveWebServerApplicationContext.createWebServer()

private void createWebServer() {
    WebServerManager serverManager = this.serverManager;
    if (serverManager == null) {
        String webServerFactoryBeanName = getWebServerFactoryBeanName();
        ReactiveWebServerFactory webServerFactory = getWebServerFactory(webServerFactoryBeanName);
        boolean lazyInit = getBeanFactory().getBeanDefinition(webServerFactoryBeanName).isLazyInit();
        this.serverManager = new WebServerManager(this, webServerFactory, this::getHttpHandler, lazyInit);
        getBeanFactory().registerSingleton("webServerGracefulShutdown",
                new WebServerGracefulShutdownLifecycle(this.serverManager));
        getBeanFactory().registerSingleton("webServerStartStop",
                new WebServerStartStopLifecycle(this.serverManager));
    }
    initPropertySources();
}

可以看出:WebServerManager 是在 ReactiveWebServerApplicationContext 中创建的。关于 ReactiveWebServerApplicationContext 的创建过程可以参考 SpringApplication启动时如何选择ConfigurableApplicationContext

小结:

  1. ReactiveWebServerApplicationContext.createWebServer()
  2. new WebServerManager(…​,ReactiveWebServerFactory,…​)
  3. ReactiveWebServerFactory.getWebServer(HttpHandler)

3. 启动过程

已经创建好了 WebServer,那么何时启动呢?通过以下源码:

WebServerStartStopLifecycle.start()

public void start() {
    this.weServerManager.start();
    this.running = true;
}

可以看出:在 WebServerStartStopLifecycle.start() 中调用了 WebServerManager.start(),而 WebServerManager.start() 又调用了 WebServer.start()。那么 WebServerStartStopLifecycle.start() 是何时被调用的呢?在 ReactiveWebServerApplicationContext.createWebServer 中,已经将 WebServerStartStopLifecycle 注册到了应用上下文,WebServerStartStopLifecycle 实现了 Lifecycle 接口。通过以下源码:

DefaultLifecycleProcessor.startBeans(boolean)

private void startBeans(boolean autoStartupOnly) {
    Map<String, Lifecycle> lifecycleBeans = getLifecycleBeans();
    Map<Integer, LifecycleGroup> phases = new HashMap<>();
    lifecycleBeans.forEach((beanName, bean) -> {
        if (!autoStartupOnly || (bean instanceof SmartLifecycle && ((SmartLifecycle) bean).isAutoStartup())) {
            int phase = getPhase(bean);
            LifecycleGroup group = phases.get(phase);
            if (group == null) {
                group = new LifecycleGroup(phase, this.timeoutPerShutdownPhase, lifecycleBeans, autoStartupOnly);
                phases.put(phase, group);
            }
            group.add(beanName, bean);
        }
    });
    if (!phases.isEmpty()) {
        List<Integer> keys = new ArrayList<>(phases.keySet());
        Collections.sort(keys);
        for (Integer key : keys) {
            phases.get(key).start();
        }
    }
}

可以看出:Lifecycle.start() 会被 DefaultLifecycleProcessor.startBeans(boolean) 调用。从应用启动到 WebServer.start(),完整的调用路径如下:

  1. SpringApplication.run
  2. SpringApplication.refresh
  3. ReactiveWebServerApplicationContext.refresh
  4. AbstractApplicationContext.finishRefresh
  5. DefaultLifecycleProcessor.onRefresh
public void onRefresh() {
    startBeans(true);
    this.running = true;
}

小结:

  1. ReactiveWebServerApplicationContext.createWebServer():注册 WebServerStartStopLifecycle
  2. DefaultLifecycleProcessor.onRefresh():触发 WebServerStartStopLifecycle.start()

4. 应用编程

现在服务已经启动了,那么服务启动之后,如何运行我们的代码呢?通过以下源码:

NettyReactiveWebServerFactory.getWebServer

public WebServer getWebServer(HttpHandler httpHandler) {
    HttpServer httpServer = createHttpServer();
    ReactorHttpHandlerAdapter handlerAdapter = new ReactorHttpHandlerAdapter(httpHandler);
    NettyWebServer webServer = new NettyWebServer(httpServer, handlerAdapter, this.lifecycleTimeout, getShutdown());
    webServer.setRouteProviders(this.routeProviders);
    return webServer;
}

可以看出:构造 NettyWebServer 需要传入 HttpHandler,而这个 HttpHandler 就是 NettyWebServer 处理请求的主要入口。那么 HttpHandler 是怎么被创建的呢?通过以下源码:

HttpHandlerAutoConfiguration.AnnotationConfig

@Bean
public HttpHandler httpHandler(ObjectProvider<WebFluxProperties> propsProvider) {
    HttpHandler httpHandler = WebHttpHandlerBuilder.applicationContext(this.applicationContext).build();
    WebFluxProperties properties = propsProvider.getIfAvailable();
    if (properties != null && StringUtils.hasText(properties.getBasePath())) {
        Map<String, HttpHandler> handlersMap = Collections.singletonMap(properties.getBasePath(), httpHandler);
        return new ContextPathCompositeHandler(handlersMap);
    }
    return httpHandler;
}

可以看出:HttpHandler 是由 WebHttpHandlerBuilder.applicationContext(ApplicationContext).build() 构建的。通过以下源码:

WebHttpHandlerBuilder.applicationContext

public static WebHttpHandlerBuilder applicationContext(ApplicationContext context) {
    WebHttpHandlerBuilder builder = new WebHttpHandlerBuilder(
            context.getBean(WEB_HANDLER_BEAN_NAME, WebHandler.class), context);

    List<WebFilter> webFilters = context
            .getBeanProvider(WebFilter.class)
            .orderedStream()
            .collect(Collectors.toList());
    builder.filters(filters -> filters.addAll(webFilters));
    ...
    return builder;
}

可以看出: WebHttpHandlerBuilderApplicationContext 中取出以下 Bean:

  1. WebHandler [1] — 查找 1 个名为 webHandler 的 WebHandler
  2. WebFilter [0..N] — 查找 0~n 个 WebFilter 并排序
  3. WebExceptionHandler [0..N] — 查找 0~n 个 WebExceptionHandler 并排序
  4. WebSessionManager [0..1] — 查找 1 个名为 webSessionManager 的 WebSessionManager
  5. ServerCodecConfigurer [0..1] — 查找 1 个名为 serverCodecConfigurer 的 ServerCodecConfigurer
  6. LocaleContextResolver [0..1] — 查找 1 个名为 localeContextResolver 的 LocaleContextResolver

并使用这些 Bean 构建 HttpHandler

WebHttpHandlerBuilder.build

public HttpHandler build() {

    WebHandler decorated = new FilteringWebHandler(this.webHandler, this.filters);
    decorated = new ExceptionHandlingWebHandler(decorated,  this.exceptionHandlers);

    HttpWebHandlerAdapter adapted = new HttpWebHandlerAdapter(decorated);
    if (this.sessionManager != null) {
        adapted.setSessionManager(this.sessionManager);
    }
    if (this.codecConfigurer != null) {
        adapted.setCodecConfigurer(this.codecConfigurer);
    }
    if (this.localeContextResolver != null) {
        adapted.setLocaleContextResolver(this.localeContextResolver);
    }
    if (this.forwardedHeaderTransformer != null) {
        adapted.setForwardedHeaderTransformer(this.forwardedHeaderTransformer);
    }
    if (this.applicationContext != null) {
        adapted.setApplicationContext(this.applicationContext);
    }
    adapted.afterPropertiesSet();

    return adapted;
}

最终构建出 HttpHandler 类似以下示例:

HttpHandler httpHandler = new HttpWebHandlerAdapter(
    new ExceptionHandlingWebHandler(
        new FilteringWebHandler(
            new DispatcherHandler(...)
        , this.filters)
    ,  this.exceptionHandlers)
);

小结:

  1. NettyReactiveWebServerFactory.getWebServer(HttpHandler)
  2. WebHttpHandlerBuilder.applicationContext(ApplicationContext).build()

5. 彩蛋

查看源码过程中,发现一个 BUG,已上报到 spring-framework

原创声明,本文系作者授权云+社区发表,未经许可,不得转载。

如有侵权,请联系 yunjia_community@tencent.com 删除。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • Spring Cloud 微服务(九)- 集成 Spring Boot Admin

    SBA(spring-boot-admin) 可简单理解为一个 UI 组件,提供 Endpoint 接口数据的界面展示。

    安宁
  • spring.profiles.active 和 spring.profiles.include 的区别

    spring.profiles.active 和 spring.profiles.include 有什么区别呢?笔者认为主要是语意上的区别,实际使用效果相同。假...

    安宁
  • Spring Cloud 微服务(外篇):一次部署故障

    之前只运行 NGINX 和 FBG 棋盘游戏很稳定。接着使用 配置中心+注册中心+接口网关 取代了 NGINX,也没有出现问题。后来再加上 UAA 认证授权中心...

    安宁
  • (四十六)c#Winform自定义控件-水波进度条

    GitHub:https://github.com/kwwwvagaa/NetWinformControl

    冰封一夏
  • Joomla V3.7.0 核心组件SQL注入漏洞分析

    风流
  • 常用数据结构的 JavaScript 实现代码[每日前端夜话0xED]

    在 JavaScript 中数据结构通常总是被忽略,或者接触得不多。但是对于许多大厂而言,一般都需要你深刻了解如何管理数据。掌握数据结构也能够在解决问题时为你...

    疯狂的技术宅
  • 一步一步教你使用AgileEAS.NET基础类库进行应用开发-WinForm应用篇-库存查询模块

    回顾与说明     前面我就用了大量的篇幅我讲解了“商品入库”模块,在商品入库模块之中,我们介绍 了与之相关的管理信息系统开发中的一个共性场景,以及这个应用场景...

    魏琼东
  • ASP.NET AJAX(14)__UpdatePanel与服务器端脚本控件脚本控件的作用脚本控件的指责Extender模型脚本控件和Extender模型在PostBack中保持状态在UpdatePa

    脚本控件的作用 ASP.NET AJAX的脚本控件,连接了服务器端和客户端,因为我们(可以)只在服务器端编程,而效果产生在客户端,这就需要我们首先在服务器端编写...

    小白哥哥
  • css+js实现左右滑动卡片组件

    最近的一个活动页面需要做一个可以左右滑动的抽签效果,故通过用css的transform属性和js结合来模拟可以无限滚动的效果。

    郭诗雅
  • 实现盒子动画和键盘特效

    望月从良

扫码关注云+社区

领取腾讯云代金券