WebFlux SpringBoot下启动流程源码解析


前言

目前大部分公司还在使用SpringMVC做WEB应用的开发,对SpringMVC有过了解的同学应该知道,MVC核心是通过DispatchServlet对请求进行解析处理,从这点上体现了MVC是依赖于Servlet容器的(对Servlet不太熟悉的同学可以借鉴一下这篇文章Servlet是什么),而Servlet是阻塞的。什么是阻塞呢?打个比方,用户A发起请求,服务器的Servlet容器S接收请求并做出响应,而S处理请求是需要启动线程的,在A的响应完成之前,S分配的处理线程是一直处于运行状态,尤其是在执行一些耗时操作时,比如IO。调用IO后,将会由IO线程进行操作,此时Servlet线程则开始进行等待,一直到IO结束,返回结果后,Servlet线程才会接着执行。更直接的比方就是餐厅传菜,Servlet模式就相当于用户点好菜之后,服务员去厨房交给厨师做,此时服务员得在厨房等着,等厨师做好了,服务员才会端着菜给用户。

为了应对这种情况,Reactive横空出世,说白了就是非阻塞模型,也可以理解成设计模式,技术标准,不懂的同学就自己搜索了解一下,目前我还没看到解释的比较直观,比较好理解的文章,暂时就不推荐了。Reactive实际为我们解决的是线程阻塞的问题,应用到web容器中的话,可以提高服务的吞吐量(即每秒接收多少请求数的峰值),还是拿餐厅来举例子,餐厅里就3个人服务员,按阻塞模式来说的话,也就是同一时间,只能安排3桌客人,来了第4桌时就得等待,等待3桌当中某一桌上完菜才能处理第4桌。而Reactive就是服务员不必在厨房等着,把菜单交代给厨师,自己就可以该干啥干啥了,厨师菜做好了,对讲机呼叫一下服务员,服务员再去上菜,这样也就意味着同时可以接待更多的顾客。但实际做菜需要多长时间还是多长时间,并不能提高厨师做菜的效率,同样服务员上菜之前是走的,用Reactive后,改走还是走,并不会用跑的,所以上菜的效率也是没有提升的,反而会有些损耗。之前厨师做好,服务员立马端走,现在厨师做好,还得呼叫服务员,因为Reactive是基于事件驱动的设计模式,所以会有大篇幅的代码来实现设计模式,过程中当然会有更多代码执行,有些损耗也是理所当然,只是这种损耗是可以忽略不计的。综上所述来看,Reactive在WEB上的应用只是帮我们解决阻塞产生的性能浪费,从而发挥更多性能用于接收更多请求。

Spring为了实现Reactive在WEB上的应用,推出了WebFlux,是用于替代传统SpringMVC的解决方案,这便是WebFlux的产生背景(也是个人理解,有不对之处还望指正)。个人认为事件驱动的非罪模式必然将会慢慢替代掉之前的阻塞模式,因此WebFlux还是非常有必要了解的。还一个题外话,从减少性能损耗上来说,单纯的只在容器上使用Reactive有点不伦不类,比如说WebFlux是非阻塞模型,但读取数据库或是其他操作还是阻塞的,那对于整体的应用架构来说,就有点不伦不类了。代入到传菜的例子里,厨师做菜前得配菜,菜配好之前,厨师也要等着,何不让厨师也解放出来,菜配好了,通知厨师来做菜。

后面我们就直接进入正题,对WebFlux源码进行解析。源码解析会分成几个部分,这篇文章的话就先介绍一下SpringBoot下,WebFlux启动流程。WebFlux怎么引入使用这个就不提了,这玩意一找一大堆,官网也有介绍。


一、ReactiveWebServerApplicationContext - WebFlux ApplicationContext

用过SpringBoot的同学应该都知道,SpringApplication.run()用于启动WEB服务,其中核心步骤是获取对应的ConfigurableApplicationContext,然后调用context的“准备环境”,“刷新应用”,“后置处理”方法,从而实现Web容器的启动。代码片段如下:

public class SpringApplication {
   

	......
	
	/**
	 * SpringBoot启动
	 */
	public ConfigurableApplicationContext run(String... args) {
    
		......
		// 创建ApplicationContext
		context = createApplicationContext();
		// 设置应用启动步骤对象
		context.setApplicationStartup(this.applicationStartup);
		// 准备Context应用环境,包括Bean初始化
		prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);
		// 刷新应用
		refreshContext(context);
		// 应用后置处理
		afterRefresh(context, applicationArguments);
	}
}

ConfigurableApplicationContext是Spring的接口,不同Web容器会有不同的ApplicationContext实现对象,在WebFlux环境下,context = createApplicationContext(),实际获取的是AnnotationConfigReactiveWebServerApplicationContext(通过ApplicationContextFactory获取,具体代码这里不做分析),继承自ReactiveWebServerApplicationContext,应用刷新的核心代码就在当中。代码如下:

package org.springframework.boot.web.reactive.context;
import ...

public class ReactiveWebServerApplicationContext extends GenericReactiveWebApplicationContext
		implements ConfigurableWebServerApplicationContext {
   

	@Override
	public final void refresh() throws BeansException, IllegalStateException {
   
		try {
   
			// 调用父类refresh()方法,父级关系较多,实际执行为AbstractApplicationContext的refresh()方法
			// 此父类为spring-context包下refresh用于各种配置文件解析、BEAN定义、属性注入等,这个不做具体分析。
			// 最后会调用自身onRefresh() 方法
			super.refresh();
		}
		catch (RuntimeException ex) {
   
			WebServerManager serverManager = this.serverManager;
			if (serverManager != null) {
   
				serverManager.getWebServer().stop();
			}
			throw ex;
		}
	}
		
	@Override
	protected void onRefresh() {
   
		// 父类onRefresh,为空方法
		super.onRefresh();
		try {
   
			// 这里便是核心,创建Web服务
			createWebServer();
		}
		catch (Throwable ex) {
   
			throw new ApplicationContextException("Unable to start reactive web server", ex);
		}
	}
	
	private void createWebServer() {
   
		// 这里为null,进入下面的if
		WebServerManager serverManager = this.serverManager;
		if (serverManager == null) {
   
			// 设定spring启动步骤
			StartupStep createWebServer = this.getApplicationStartup().start("spring.boot.webserver.create");
			// 获取webServerFactoryBean名称
			String webServerFactoryBeanName = getWebServerFactoryBeanName();
			// 获取webServerFactory,即NettyReactiveWebServerFactory
			// 详见-> org.springframework.boot.autoconfigure.web.reactive.ReactiveWebServerFactoryConfiguration.nettyReactiveWebServerFactory()
			ReactiveWebServerFactory webServerFactory = getWebServerFactory(webServerFactoryBeanName);
			createWebServer.tag("factory", webServerFactory.getClass().toString());
			// HttpHandler是否使用延迟加载,默认为false
			boolean lazyInit = getBeanFactory().getBeanDefinition(webServerFactoryBeanName).isLazyInit();
			// 初始化Web服务管理器,getHttpHandler() -> 获取HttpHandler
			// 初始化详见第二大块
			this.serverManager = new WebServerManager(this, webServerFactory, this::getHttpHandler, lazyInit);
			// 注册停机生命周期流程:SpringBoot提供的优雅停机方案,详见:https://blog.csdn.net/jackcheng1117/article/details/109380524
			getBeanFactory().registerSingleton("webServerGracefulShutdown",
					new WebServerGracefulShutdownLifecycle(this.serverManager.getWebServer()));
			// 注册启动/停止生命周期流程,详见第三大块
			getBeanFactory().registerSingleton("webServerStartStop",
					new WebServerStartStopLifecycle(this.serverManager));
			// 结束当前步骤
			createWebServer.end();
		}
		initPropertySources();
	}
	
	/**
	 * 从Spring中获取HttpHandler
	 */
	protected HttpHandler getHttpHandler() {
   
		// Use bean names so that we don't consider the hierarchy
		String[] beanNames = getBeanFactory().getBeanNamesForType(HttpHandler.class);
		if (beanNames.length == 0) {
   
			throw new ApplicationContextException(
					"Unable to start ReactiveWebApplicationContext due to missing HttpHandler bean.");
		}
		if (beanNames.length > 1) {
   
			throw new ApplicationContextException(
					"Unable to start ReactiveWebApplicationContext due to multiple HttpHandler beans : "
							+ StringUtils.arrayToCommaDelimitedString(beanNames));
		}
		return getBeanFactory().getBean(beanNames[0], HttpHandler.class);
	}
}

二、WebServerManager- Web服务管理器(初始化)

从ReactiveWebServerApplicationContext 代码中给我们可以看到WebFlux的web服务启动需要先初始化WebServerManager,再以WebServerManager为参注册生命周期,第二段内容,我们重点看下WebServerManager是如何进行初始化。

package org.springframework.boot.web.reactive.context
  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值