学Springboot必须要懂的内嵌式Tomcat启动与请求处理
前言
找了一圈也没有找到合适内置容器源码解读的文章,就打算自己写一篇,方便后面阅读加深映像
Springboot相对于以前的Spring省去了很多配置xml的麻烦,而且还支持使用内嵌式的Servlet容器,非常的便利。既然这样,为了精准快速的定位问题,以及满足我们膨胀的好奇心,那我们就有必要了解便利的背后是什么操作了,学习高效的策略以便于提升自己嘛,下面我们用Tomcat来探索一下。篇幅较长,请耐心阅读,一定会有收获的。
还可以阅读一下我的另外一篇文章《自己实现Servlet---我自己的“tomcat”》
),一定能够更好的理解web容器。
章节说明
1.内嵌式tomcat配置
配置项分析
2.启动流程
(1)启动主流程
(2)NioEndpoint启动监听
3.处理Http请求
一次http请求的生命周期
一、内嵌式tomcat配置
// 在类org.springframework.boot.autoconfigure.web.ServerProperties可以找到tomcat的相关配置参数
// 日志配置
private final Accesslog accesslog = new Accesslog();
// 授信的地址前缀
private String internalProxies = "10\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}|" // 10/8
+ "192\\.168\\.\\d{1,3}\\.\\d{1,3}|" // 192.168/16
+ "169\\.254\\.\\d{1,3}\\.\\d{1,3}|" // 169.254/16
+ "127\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}|" // 127/8
+ "172\\.1[6-9]{1}\\.\\d{1,3}\\.\\d{1,3}|" // 172.16/12
+ "172\\.2[0-9]{1}\\.\\d{1,3}\\.\\d{1,3}|"
+ "172\\.3[0-1]{1}\\.\\d{1,3}\\.\\d{1,3}|" //
+ "0:0:0:0:0:0:0:1|::1";
// 请求头协议,一般为"X-Forwarded-Proto"
private String protocolHeader;
// ssl的头协议https
private String protocolHeaderHttpsValue = "https";
// 请求的原始端口号
private String portHeader = "X-Forwarded-Port";
// 请求源ip
private String remoteIpHeader;
// tomcat的文件基地址
private File basedir;
// 定期调用backgroundProcessor方法。backgroundProcessor方法主要用于重新加载Web应用程序的类文件和资源、扫描Session过期
@DurationUnit(ChronoUnit.SECONDS)
private Duration backgroundProcessorDelay = Duration.ofSeconds(10);
// 并行线程的最大值
private int maxThreads = 200;
// 最小的闲置线程数
private int minSpareThreads = 10;
// post请求的数据最大值
private DataSize maxHttpPostSize = DataSize.ofMegabytes(2);
// 请求头的最大值,如果是0,则取默认的8kb
private DataSize maxHttpHeaderSize = DataSize.ofBytes(0);
// 请求体数据的最大值
private DataSize maxSwallowSize = DataSize.ofMegabytes(2);
// 请求跳转时是否带上上下文
private Boolean redirectContextRoot = true;
// 是否使用相对的跳转路径
private Boolean useRelativeRedirects;
// 编码格式
private Charset uriEncoding = StandardCharsets.UTF_8;
// 最大的连接数10000
private int maxConnections = 10000;
// 当所有线程在忙的时候,等待队列中请求个数
private int acceptCount = 100;
二、启动流程
2.1 主流程
2.2 TomcatServletWebServerFactory
顾名思义它是一个TomcatWebServer的生产工厂,它内部有一个getWebServer的方法,是这个工厂用来生产WebServer的,然后我们需要查看一下它的类继承结构,不然理解上会断层
继承结构图中,我们可以看到它实现了ServletWebServerFactory这个接口。然后我们转到Springboot其中过程中的onRefresh方法中
protected void onRefresh() {
super.onRefresh();
try {// 创建WebServer
createWebServer();
}
catch (Throwable ex) {
throw new ApplicationContextException("Unable to start web server", ex);
}
}
private void createWebServer() {
WebServer webServer = this.webServer;
// 获取容器的上下文信息
ServletContext servletContext = getServletContext();
// 上下文对象和server对象都还没有初始化的话,通过工厂进行生成webserver
if (webServer == null && servletContext == null) {
// 从spring容器中获取server的生产工厂
ServletWebServerFactory factory = getWebServerFactory();
// 调用工厂的方法去生产server
// getSelfInitializer采用了lamda表达式去实现了初始化方法,下面稍微说一下
this.webServer = factory.getWebServer(getSelfInitializer());
}
// 如果上下文不是空的,那么可能就是tomcat初始化失败了,可以使用上下文去初始化tomcat
// 小小的推敲一下,说明tomcat的启动的基本信息都可以在context里面找到
else if (servletContext != null) {
try {
getSelfInitializer().onStartup(servletContext);
}
catch (ServletException ex) {
throw new ApplicationContextException("Cannot initialize servlet context",
ex);
}
}
initPropertySources();
}
protected ServletWebServerFactory getWebServerFactory() {
// 从容器中获取所有实现了ServletWebServerFactory接口的bean,刚才我们看了类结构
// TomcatServletWebServerFactory是实现了ServletWebServerFactory,如果它被容器加载了,然后这里就能获取到它了
String[] beanNames = getBeanFactory()
.getBeanNamesForType(ServletWebServerFactory.class);
// spring容器中必须要存在一个实现接口ServletWebServerFactory的bean
if (beanNames.length == 0) {
throw new ApplicationContextException(
"Unable to start ServletWebServerApplicationContext due to missing "
+ "ServletWebServerFactory bean.");
}
// 如果有好几个实现的bean,也会报错,只允许存在一个,说明在springboot里面是不支持多个servlet容器并存的
if (beanNames.length > 1) {
throw new ApplicationContextException(
"Unable to start ServletWebServerApplicationContext due to multiple "
+ "ServletWebServerFactory beans : "
+ StringUtils.arrayToCommaDelimitedString(beanNames));
}
return getBeanFactory().getBean(beanNames[0], ServletWebServerFactory.class);
}
private org.springframework.boot.web.servlet.ServletContextInitializer getSelfInitializer() {
// 看到这里也许有人会疑惑,方法是有返回值的,怎么它返回了void
// 其实它是使用了lamda的匿名实现方式,换种写法可能更容易理解return servletContext->selfInitialize(servletContext)
return this::selfInitialize;
}
private void selfInitialize(ServletContext servletContext) throws ServletException {
prepareWebApplicationContext(servletContext);
registerApplicationScope(servletContext);
WebApplicationContextUtils.registerEnvironmentBeans(getBeanFactory(),
servletContext);
for (ServletContextInitializer beans : getServletContextInitializerBeans()) {
beans.onStartup(servletContext);
}
}
好了这里就到了重点了,开始生产webserver
@Override
public WebServer getWebServer(ServletContextInitializer... initializers) {
Tomcat tomcat = new Tomcat();
// 新建一个tomcat对象之后,开始配置参数,配置连接等
File baseDir = (this.baseDirectory != null) ? this.baseDirectory
: createTempDir("tomcat");
tomcat.setBaseDir(baseDir.getAbsolutePath());
// 这个Connector也是tomcat的核心,等会2.4小节会仔细分析一下
Connector connector = new Connector(this.protocol);
tomcat.getService().addConnector(connector);
customizeConnector(connector);
tomcat.setConnector(connector);
tomcat.getHost().setAutoDeploy(false);
configureEngine(tomcat.getEngine());
for (Connector additionalConnector : this.additionalTomcatConnectors) {
tomcat.getService().addConnector(additionalConnector);
}
// 准备上下文对象
prepareContext(tomcat.getHost(), initializers);
// 创建webserver
return getTomcatWebServer(tomcat);
}
public TomcatWebServer(Tomcat tomcat, boolean autoStart) {
Assert.notNull(tomcat, "Tomcat Server must not be null");
this.tomcat = tomcat;
this.autoStart = autoStart;
// 初始化webserver
initialize();
}
private void initialize() throws WebServerException {
logger.info("Tomcat initialized with port(s): " + getPortsDescription(false));
synchronized (this.monitor) {
try {
addInstanceIdToEngineName();
Context context = findContext();
context.addLifecycleListener((event) -> {
if (context.equals(event.getSource())
&& Lifecycle.START_EVENT.equals(event.getType())) {
// 将连接从service中的连接移除,并转存到serviceConnectors这个map中
removeServiceConnectors();
}
});
// 启动tomcat,并触发启动监听事件,然后做一下StandardServer对象的初始化
this.tomcat.start();
// 我们可以在这里重新抛出主线程中的异常
rethrowDeferredStartupExceptions();
try {
ContextBindings.bindClassLoader(context, context.getNamingToken(),
getClass().getClassLoader());
}
catch (NamingException ex) {
// Naming is not enabled. Continue
}
// 跟jetty不一样的是,tomcat里面的线程都是守护线程,主线程停止,子线程当即停止
startDaemonAwaitThread();
}
catch (Exception ex) {
stopSilently();
throw new WebServerException("Unable to start embedded Tomcat", ex);
}
}
}
2.3 TomcatWebServerFactoryCustomizer
这个类是通过EmbeddedWebServerFactoryCustomizerAutoConfiguration加载进来的,不得不说这个类名是真的长,它主要就是将配置信息加载进来,在onRefresh之前加载。
// 这个方法有个入参,需要实现了接口ConfigurableTomcatWebServerFactory的bean,如果还没有忘记上面的类结构的话,你就会发现刚才的TomcatServletWebServerFactory也继承并实现了它。简而言之,就是把配置信息加载到TomcatServletWebServerFactory对象中。然后onRefresh的时候才能
public void customize(ConfigurableTomcatWebServerFactory factory) {
ServerProperties properties = this.serverProperties;
ServerProperties.Tomcat tomcatProperties = properties.getTomcat();
// 这个方法里面主要都是配置相关的,没什么特别的
// 不过PropertyMapper这个类的from when to的写法很有意思,可以模仿一下
PropertyMapper propertyMapper = PropertyMapper.get();
propertyMapper.from(tomcatProperties::getBasedir).whenNonNull()
.to(factory::setBaseDirectory);
propertyMapper.from(tomcatProperties::getBackgroundProcessorDelay).whenNonNull()
.as(Duration::getSeconds).as(Long::intValue)
.to(factory::setBackgroundProcessorDelay);
customizeRemoteIpValve(factory);
propertyMapper.from(tomcatProperties::getMaxThreads).when(this::isPositive)
.to((maxThreads) -> customizeMaxThreads(factory,
tomcatProperties.getMaxThreads()));
propertyMapper.from(tomcatProperties::getMinSpareThreads).when(this::isPositive)
.to((minSpareThreads) -> customizeMinThreads(factory, minSpareThreads));
propertyMapper.from(this::determineMaxHttpHeaderSize).whenNonNull()
.asInt(DataSize::toBytes).when(this::isPositive)
.to((maxHttpHeaderSize) -> customizeMaxHttpHeaderSize(factory,
maxHttpHeaderSize));
propertyMapper.from(tomcatProperties::getMaxSwallowSize).whenNonNull()
.asInt(DataSize::toBytes)
.to((maxSwallowSize) -> customizeMaxSwallowSize(factory, maxSwallowSize));
propertyMapper.from(tomcatProperties::getMaxHttpPostSize).asInt(DataSize::toBytes)
.when((maxHttpPostSize) -> maxHttpPostSize !=