Spring boot之所以如此受欢迎,不仅自动完成了大量配置,还内嵌了web容器tomcat,开箱即用。今天我们一起从源码中了解下tomcat是如何在spring boot中启动的。
首先说明下,网上很多文章在介绍这个主题时都是直接告诉读者在XXX类的XXX方法启动的,或者是第几行加了注释说明其作用,这样虽然十分直观快速了解到启动原理,但这只是“授之于鱼”,下次遇到其它知识点又不懂了。这里给大家“授之于渔”,也是我平时学习和排查问题的方法,就是 日志+debug 了!
当启动一个spring boot web项目时,会打印以下日志:
从日志中的tomcat关键词,可以看出是TomcatWebServer来启动tomcat的,于是进入这个类,在打印的日志行上打断点,重新启动spring boot就可以看到整个调用琏啦:
至此,我们就能找到tomcat的启动位置了。
然后我们来看看具体的启动过程:首先tomcat启动是在spring boot refresh步骤中开始的,而具体的方法在org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext#onRefresh,在里面调用了方法createWebServer
private void createWebServer() {
WebServer webServer = this.webServer;
ServletContext servletContext = getServletContext();
if (webServer == null && servletContext == null) {
ServletWebServerFactory factory = getWebServerFactory();
this.webServer = factory.getWebServer(getSelfInitializer());
}
// ……
}
这里首先创建ServletWebServerFactory对象,创建过程很简单,只是找出ServletWebServerFactory的实现类,但其实现类不止一个:
可以看出,spring boot至少支持jetty、tomcat、undertow三种web容器,为何使用的是tomcat呢?可以从tomcat的依赖树中找到原因,通过 mvn dependency:tree或者ide的maven插件show dependency:tree显示项目的依赖树,从中再查看tomcat的依赖关系:
[INFO] +- org.springframework.boot:spring-boot-starter-web:jar:2.2.4.RELEASE:compile
[INFO] | +- org.springframework.boot:spring-boot-starter-tomcat:jar:2.2.4.RELEASE:compile
[INFO] | | +- org.apache.tomcat.embed:tomcat-embed-core:jar:9.0.30:compile
[INFO] | | +- org.apache.tomcat.embed:tomcat-embed-el:jar:9.0.30:compile
[INFO] | | \- org.apache.tomcat.embed:tomcat-embed-websocket:jar:9.0.30:compile
可以看出tomcat是在·spring-boot-starter-web引用的,在其pom文件中可以找到tomcat的dependency,并且在description也说明了原因:
<description>Starter for building web, including RESTful, applications using Spring
MVC. Uses Tomcat as the default embedded container</description>
回到代码当中,在TomcatServletWebServerFactory#getWebServer就将tomcat初始化完成了,之后在ServletWebServerApplicationContext#finishRefresh将其启动起来
彩蛋:spring boot是如何支持多种不同的容器
为了方便地支持不同的容器,自然想到使用接口定义统一行为,翻看TomcatServletWebServerFactory的代码可以看到springboot的确也是这样做的。getWebServer返回的是WebServer对象,正是它定义了所有容器的通用行为:
public interface WebServer {
// 启动容器
void start() throws WebServerException;
// 停止容器
void stop() throws WebServerException;
// 获取监听端口
int getPort();
}
同样,创建WebServer工厂当然也有接口定义——ServletWebServerFactory:
public interface ServletWebServerFactory {
WebServer getWebServer(ServletContextInitializer... initializers);
}
在前面的介绍中已经了解过ServletWebServerFactory的实现类,其中就有一个抽象实现类AbstractServletWebServerFactory,它里面实现了session、contextPath、ServletContextInitializer等容器的通用方法,这样tomcat、jetty等容器工厂实现类只需继承这个抽象类即可拥有通用能力,无需重复实现,这种设计也就是常说的 模板模式