前言

为什么 Spring Boot 不需要额外安装 Tomcat 了?

因为 Spring Boot 有内置的 Web 服务器 Tomcat,所以不用单独配置。

这篇文章主要从源码的角度出发,解析 Spring Boot 内嵌 Tomcat 的实现原理,讨论 Tomcat 何时创建、启动以及是怎样启动的。

下面跟随源码一步步找到如何启动内置的 tomcat。

首先贴出启动类:

@SpringBootApplication

public class QuartJobApplication {

public static void main(String[] args) {

SpringApplication.run(QuartJobApplication.class, args);

}

}

我们点击 run 进入源代码:

public static ConfigurableApplicationContext run(Class primarySource, String... args) {

return run(new Class[]{primarySource}, args);

}

public static ConfigurableApplicationContext run(Class[] primarySources, String[] args) {

return (new SpringApplication(primarySources)).run(args);

}

public ConfigurableApplicationContext run(String... args) {

long startTime = System.nanoTime();

DefaultBootstrapContext bootstrapContext = this.createBootstrapContext();

ConfigurableApplicationContext context = null;

this.configureHeadlessProperty();

SpringApplicationRunListeners listeners = this.getRunListeners(args);

listeners.starting(bootstrapContext, this.mainApplicationClass);

try {

.......省略.......

this.refreshContext(context);

........省略.......

}

上面省略部分的源码在我分享的上一篇文章中已经讲过,这里就不在重复了,感兴趣的小伙伴可以去看一下:

Spring Boot 启动流程原理分析

这里面我们主要关心的是 this.refreshContext(context); 这个方法,因为 Tomcat 就在这里面。

下面我们点击进去看一下

我们接着点击 refresh 方法

这里选择 ServletWebServerApplicationContext 的类

进入以下的方法以后选择点击 onRefresh

public void refresh() throws BeansException, IllegalStateException {

synchronized(this.startupShutdownMonitor) {

//1、调用容器准备刷新的方法,获取容器的当时时间,同时给容器设置同步标识

this.prepareRefresh();

//2、创建 beanFactory,主要是加载 bean 的信息,分为定位,加载,注册

ConfigurableListableBeanFactory beanFactory = this.obtainFreshBeanFactory();

//3、为 BeanFactory 配置容器特性,例如类加载器、事件处理器等

this.prepareBeanFactory(beanFactory);

try {

//4、BeanFactory 准备工作完成后进行的后置处理工作;

//抽象的方法,当前未做处理。子类通过重写这个方法来在 BeanFactory 创建并预准备完成以后做进一步的设置

this.postProcessBeanFactory(beanFactory);

//5、调用所有注册的 BeanFactoryPostProcessor的Bean

this.invokeBeanFactoryPostProcessors(beanFactory);

//6、为 BeanFactory 注册 BeanPost 事件处理器.这是仅仅是注册,调用在 getbean 的时候

this.registerBeanPostProcessors(beanFactory);

//7、初始化 MessageSource 组件(做国际化功能;消息绑定,消息解析)

this.initMessageSource();

//8、初始化容器事件传播器

this.initApplicationEventMulticaster();

//9、调用子类的某些特殊 Bean 初始化方法(这里是我们主要看的)

this.onRefresh();

//10、为事件传播器注册事件监听器.

this.registerListeners();

//11、初始化所有剩余的单例 Bean

this.finishBeanFactoryInitialization(beanFactory);

//12、完成 BeanFactory 的初始化创建工作,IOC容器就创建完成

this.finishRefresh();

} catch (BeansException var10) {

if (this.logger.isWarnEnabled()) {

this.logger.warn("Exception encountered during context initialization - cancelling refresh attempt: " + var10);

}

//13、销毁已创建的Bean

this.destroyBeans();

//14、取消refresh操作,重置容器的同步标识.

this.cancelRefresh(var10);

throw var10;

} finally {

this.resetCommonCaches();

}

}

}

上面每个方法的大致含义就是这样,下面我们选择 onRefresh 方法进去看源码:

这里选择 ServletWebServerApplicationContext 类里面的方法

点击 this.createWebServer的方法进去,源码如下:

private void createWebServer() {

//第一次过来,这个为 null

WebServer webServer = this.webServer;

//这里也为 null

ServletContext servletContext = this.getServletContext();

//第一次进来上面 webServer servletContext 都是null,会进到if分支里面

if (webServer == null && servletContext == null) {

//这里不需要关注,只是标记一下

StartupStep createWebServer = this.getApplicationStartup().start("spring.boot.webserver.create");

//获取 Servlet 服务器工厂

ServletWebServerFactory factory = this.getWebServerFactory();

createWebServer.tag("factory", factory.getClass().toString());

//工厂方法,获取 Servlet 服务器,并作为 AbstractApplicationContext 的一个属性进行设置

this.webServer = factory.getWebServer(new ServletContextInitializer[]{this.getSelfInitializer()});

} else if (servletContext != null) {

try {

this.getSelfInitializer().onStartup(servletContext);

} catch (ServletException var5) {

throw new ApplicationContextException("Cannot initialize servlet context", var5);

}

}

//初始化一些ConfigurableEnvironment中的 ServletContext信息

this.initPropertySources();

}

我们选择 factory.getWebServer 点击进入看源码:

getWebServer 方法主要处理的是Tomcat 容器对象的创建、环境配置和启动。

public WebServer getWebServer(ServletContextInitializer... initializers) {

if (this.disableMBeanRegistry) {

Registry.disableRegistry();

}

//实例化 Tomcat,可以理解为 Server 组件

Tomcat tomcat = new Tomcat();

//创建一个Tomcat 临时文件路径

File baseDir = this.baseDirectory != null ? this.baseDirectory : this.createTempDir("tomcat");

tomcat.setBaseDir(baseDir.getAbsolutePath());

//创建连接协议,默认使用HTTP1.1协议,NIO网络模型

Connector connector = new Connector(this.protocol);

connector.setThrowOnFailure(true);

tomcat.getService().addConnector(connector);

this.customizeConnector(connector);

tomcat.setConnector(connector);

//创建主机,并关闭热部署

tomcat.getHost().setAutoDeploy(false);

// 配置引擎

this.configureEngine(tomcat.getEngine());

Iterator var5 = this.additionalTomcatConnectors.iterator();

while(var5.hasNext()) {

Connector additionalConnector = (Connector)var5.next();

tomcat.getService().addConnector(additionalConnector);

}

//初始化TomcatEmbeddedContext

this.prepareContext(tomcat.getHost(), initializers);

//启动tomcat并返回TomcatWebServer对象

return this.getTomcatWebServer(tomcat);

}

从上面的源码中,在getTomcatWebServer这步才是完成 Tomcat,其他部分都是在配置 Tomcat。我们进去方法里面看看。

protected TomcatWebServer getTomcatWebServer(Tomcat tomcat) {

return new TomcatWebServer(tomcat, this.getPort() >= 0, this.getShutdown());

}

TomcatWebServer 对象是 Spring Boot 对 Tomcat 对象的封装,内部存了 Tomcat 实例的引用,这里执行的是TomcatWebServer 的构造方法:

public TomcatWebServer(Tomcat tomcat, boolean autoStart, Shutdown shutdown) {

this.monitor = new Object();

this.serviceConnectors = new HashMap();

Assert.notNull(tomcat, "Tomcat Server must not be null");

this.tomcat = tomcat;

this.autoStart = autoStart;

this.gracefulShutdown = shutdown == Shutdown.GRACEFUL ? new GracefulShutdown(tomcat) : null;

this.initialize();

}

这里我们主要看一下 this.initialize() ,在这个方法里面调用了 tomcat.start() 启动 Tomcat。

private void initialize() throws WebServerException {

logger.info("Tomcat initialized with port(s): " + this.getPortsDescription(false));

synchronized(this.monitor) {

try {

//给Engine命名

this.addInstanceIdToEngineName();

//获取 Host 中的 Context

Context context = this.findContext();

//绑定 Context 的生命周期监听器

context.addLifecycleListener((event) -> {

if (context.equals(event.getSource()) && "start".equals(event.getType())) {

this.removeServiceConnectors();

}

});

//启动 Tomcat,触发初始化监听器

this.tomcat.start();

//启动过程中子线程的异常从主线程抛出

this.rethrowDeferredStartupExceptions();

try {

//给当前 Context 绑定类加载器

ContextBindings.bindClassLoader(context, context.getNamingToken(), this.getClass().getClassLoader());

} catch (NamingException var5) {

}

//Tomcat 的所有线程都是守护线程,这里启动一个阻塞的非守护线程来确保 Tomcat 能及时停止

this.startDaemonAwaitThread();

} catch (Exception var6) {

this.stopSilently();

this.destroySilently();

throw new WebServerException("Unable to start embedded Tomcat", var6);

}

}

}

好了,关于 Tomcat 的解析就到这里为止,希望对小伙伴有一点点的帮助。