前言
为什么 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);
........省略.......
}
上面省略部分的源码在我分享的上一篇文章中已经讲过,这里就不在重复了,感兴趣的小伙伴可以去看一下:
这里面我们主要关心的是 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 的解析就到这里为止,希望对小伙伴有一点点的帮助。