目录
2.1、createApplicationContext()
本篇源码基于spring-boot-2.1.0.RELEASE版本进行分析,各个版本可能存在一些差别。
一、简介
SpringBoot 项目之所以部署简单,其很大一部分原因就是因为不用自己折腾 Tomcat 相关配置,因为其本身内置了各种 Servlet 容器。SpringBoot 是怎么通过简单运行一个 main 函数,就能将容器启动起来,并将自身部署到其上 ,接下来,我们就从源码的角度分析一下SpringBoot启动过程中Tomcat是如果自动启动的。
二、内置Tomcat启动流程
从SpringBoot项目启动入口main方法开始进行分析:
public class SampleTomcatApplication {
public static void main(String[] args) {
SpringApplication.run(SampleTomcatApplication.class, args);
}
}
直接进入run()方法:
// run方法是一个静态方法,用于启动SpringBoot
public static ConfigurableApplicationContext run(Class<?>[] primarySources,
String[] args) {
// 构建一个SpringApplication对象,并调用其run方法来启动
return new SpringApplication(primarySources).run(args);
}
首先会实例化一个SpringApplication对象,看下其构造方法:
// 创建一个新的SpringApplication实例。应用程序上下文将从指定的主要来源加载bean
public SpringApplication(Class<?>... primarySources) {
this(null, primarySources);
}
public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
// 传递的resourceLoader为null
this.resourceLoader = resourceLoader;
Assert.notNull(primarySources, "PrimarySources must not be null");
// 记录主方法的配置类名称
this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
// 推导出当前启动的项目的类型
this.webApplicationType = WebApplicationType.deduceFromClasspath();
// 加载配置在spring.factories文件中的ApplicationContextInitializer对应的类型并实例化. 并将加载的数据存储在了 initializers 成员变量中。
setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
// 初始化监听器,并将加载的监听器实例对象存储在了listeners成员变量中
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
// 反推出main方法所在的Class对象
this.mainApplicationClass = deduceMainApplicationClass();
}
这里详细说明了每个步骤做了哪些事情,这里我们主要关注跟Tomcat启动相关的即可:
// 推导出当前启动的项目的类型
this.webApplicationType = WebApplicationType.deduceFromClasspath();
// WebApplicationType.java
/**
* 根据classpath推导出web项目的类型。Servlet项目或者Reactive项目
*
* @return
*/
static WebApplicationType deduceFromClasspath() {
// 一些绑定的Java类的全类路径
// ClassUtils.isPresent(): 通过反射的方式获取对应的类型的Class对象,如果存在返回true,否则返回false
if (ClassUtils.isPresent(WEBFLUX_INDICATOR_CLASS, null)
&& !ClassUtils.isPresent(WEBMVC_INDICATOR_CLASS, null)
&& !ClassUtils.isPresent(JERSEY_INDICATOR_CLASS, null)) {
return WebApplicationType.REACTIVE;
}
for (String className : SERVLET_INDICATOR_CLASSES) {
if (!ClassUtils.isPresent(className, null)) {
return WebApplicationType.NONE;
}
}
return WebApplicationType.SERVLET;
}
当前我们是SERVLET环境:
接着执行run()方法:
// 运行 Spring 应用程序,创建并刷新一个新的ApplicationContext
public ConfigurableApplicationContext run(String... args) {
// 创建一个任务执行观察器,用于统计run启动过程花了多少时间
StopWatch stopWatch = new StopWatch();
// 记录开始时间
stopWatch.start();
ConfigurableApplicationContext context = null;
// exceptionReporters集合用来存储异常报告器,用来报告SpringBoot启动过程的异常
Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
// 设置了一个名为java.awt.headless的系统属性, 其实是想设置该应用程序,即使没有检测到显示器,也允许其启动. 对于服务器来说,是不需要显示器的,所以要这样设置.
configureHeadlessProperty();
// 从spring.factories配置文件中加载到EventPublishingRunListener对象并赋值给SpringApplicationRunListeners
// EventPublishingRunListener对象主要用来发布SpringBoot启动过程中内置的一些生命周期事件,标志每个不同启动阶段
SpringApplicationRunListeners listeners = getRunListeners(args);
// 发布启动事件
listeners.starting();
try {
// 创建ApplicationArguments对象,封装了args参数
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
// 获取环境变量,包括系统变量、环境变量、命令行参数、默认变量等
ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
// 配置需要忽略的BeanInfo信息
configureIgnoreBeanInfo(environment);
// 启动时控制台打印Banner
Banner printedBanner = printBanner(environment);
// 根据不同类型创建不同类型的spring容器ApplicationContext应用程序上下文
context = createApplicationContext();
// 加载spring.factories配置文件配置的异常报告器
exceptionReporters = getSpringFactoriesInstances(
SpringBootExceptionReporter.class,
new Class[] { ConfigurableApplicationContext.class }, context);
// 刷新容器前的一些操作
prepareContext(context, environment, listeners, applicationArguments, printedBanner);
// 刷新应用上下文。完成Spring IOC容器的初始化
refreshContext(context);
// 在刷新上下文后调用的钩子,这个方法是一个模板方法
afterRefresh(context, applicationArguments);
// 停止记录执行时间
stopWatch.stop();
if (this.logStartupInfo) {
new StartupInfoLogger(this.mainApplicationClass)
.logStarted(getApplicationLog(), stopWatch);
}
// 事件广播,启动完成了
listeners.started(context);
// 执行ApplicationRunner、CommandLineRunner的run方法,实现spring容器启动成功后需要执行的一些逻辑
callRunners(context, applicationArguments);
} catch (Throwable ex) {
handleRunFailure(context, ex, exceptionReporters, listeners);
throw new IllegalStateException(ex);
}
try {
listeners.running(context);
} catch (Throwable ex) {
handleRunFailure(context, ex, exceptionReporters, null);
throw new IllegalStateException(ex);
}
return context;
}
这里重点关注跟Tomcat启动相关的两个步骤:
2.1、createApplicationContext()
根据前面推算出来的webApplicationType类型创建不同的ApplicaitonContext上下文对象。
// 根据不同类型创建不同类型的spring容器ApplicationContext应用程序上下文
context = createApplicationContext();
protected ConfigurableApplicationContext createApplicationContext() {
Class<?> contextClass = this.applicationContextClass;
if (contextClass == null) {
try {
switch (this.webApplicationType) {
case SERVLET:
contextClass = Class.forName(DEFAULT_SERVLET_WEB_CONTEXT_CLASS);
break;
case REACTIVE:
contextClass = Class.forName(DEFAULT_REACTIVE_WEB_CONTEXT_CLASS);
break;
default:
contextClass = Class.forName(DEFAULT_CONTEXT_CLASS);
}
}
catch (ClassNotFoundException ex) {
throw new IllegalStateException(
"Unable create a default ApplicationContext, "
+ "please specify an ApplicationContextClass",
ex);
}
}
return (ConfigurableApplicationContext) BeanUtils.instantiateClass(contextClass);
}
前面分析到,当前是SERVLET环境,所以这里创建的是AnnotationConfigServletWebServerApplicationContext,通过反射并实例化了一个AnnotationConfigServletWebServerApplicationContext对象。如下图:
注意,AnnotationConfigServletWebServerApplicationContext继承自ServletWebServerApplicationContext,ServletWebServerApplicationContext又间接继承自AbstractApplicationContext。
2.2、refreshContext(context)
// 刷新应用上下文。完成Spring IOC容器的初始化
refreshContext(context);
refreshContext()方法内部又调用了refresh(context)方法:
private void refreshContext(ConfigurableApplicationContext context) {
refresh(context);
if (this.registerShutdownHook) {
try {
context.registerShutdownHook();
}
catch (AccessControlException ex) {
// Not allowed in some environments.
}
}
}
refresh()方法的源码如下:
protected void refresh(ApplicationContext applicationContext) {
Assert.isInstanceOf(AbstractApplicationContext.class, applicationContext);
((AbstractApplicationContext) applicationContext).refresh();
}
可以看到,这里会调用前面实例化的applicationContext的refresh()方法,因为这里是AnnotationConfigServletWebServerApplicationContext,因为它没有重写refresh()方法,所以我们看它的父类ServletWebServerApplicationContext的refresh()方法:
// org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext#refresh
public final void refresh() throws BeansException, IllegalStateException {
try {
super.refresh();
}
catch (RuntimeException ex) {
stopAndReleaseWebServer();
throw ex;
}
}
这里其实也是直接调用了抽象AbstractApplicationContext的refresh()方法:
public void refresh() throws BeansException, IllegalStateException {
//private final Object startupShutdownMonitor = new Object();
//加同步监视器锁,确保只有一个线程在初始化IOC容器,不然就乱套了
synchronized (this.startupShutdownMonitor) {
/**
* 1、容器刷新前的一些准备工作:
* a.设置容器的启动时间
* b.设置活跃状态active为true
* c.设置关闭状态为false
* d.获取Environment环境对象,并加载当前系统的属性值到Environment环境对象中,在Spring启动的时候提前对必需的变量进行存在性验证
* e.准备监听器和事件的集合对象,默认为空的集合
*/
prepareRefresh();
/**
* 2、创建容器对象:DefaultListableBeanFactory,并进行XML文件读取
* 经过obtainFreshBeanFactory()后,xml文件中的bean定义信息已经被解析封装到返回的是DefaultListableBeanFactory的成员属性中了, 如beanDefinitionMap、beanDefinitionNames
*/
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
/**
* 3、准备Bean工厂,给Bean工厂的属性赋值
*/
prepareBeanFactory(beanFactory);
try {
/**
* 4、空实现,留给子类去扩展额外的处理
*/
postProcessBeanFactory(beanFactory);
/**
* 5、执行BeanFactoryPostProcessor后置处理器的postProcessBeanFactory()增强方法
*/
invokeBeanFactoryPostProcessors(beanFactory);
/**
* 6、注册BeanPostProcessor,注意,这里还不会执行BeanPostProcessor对应的增强方法
* 真正调用是在bean初始化前、初始化后
*/
registerBeanPostProcessors(beanFactory);
/**
* 7、为上下文初始化MessageSource,即不同语言的消息体,国际化处理
*/
initMessageSource();
/**
* 8、初始化事件多播器
*/
initApplicationEventMulticaster();
/**
* 9、模板方法,留给子类初始化其他的bean
*/
onRefresh();
/**
* 10、注册监听器
*/
registerListeners();
/**
* 11、实例化所有剩下的非懒加载的单例Bean
*/
finishBeanFactoryInitialization(beanFactory);
/**
* 12、完成上下文的刷新
*/
finishRefresh();
}
catch (BeansException ex) {
if (logger.isWarnEnabled()) {
logger.warn("Exception encountered during context initialization - " +
"cancelling refresh attempt: " + ex);
}
// Destroy already created singletons to avoid dangling resources.
destroyBeans();
// Reset 'active' flag.
cancelRefresh(ex);
// Propagate exception to caller.
throw ex;
}
finally {
// Reset common introspection caches in Spring's core, since we
// might not ever need metadata for singleton beans anymore...
resetCommonCaches();
}
}
}
这里其实是Spring IOC容器启动的核心流程了,这里我们暂且只关注与Tomcat启动流程有关的。
/**
* 9、模板方法,留给子类初始化其他的bean
*/
onRefresh();
onRefresh()方法其实就是一个模板方法,留给子类扩展一些逻辑处理。Tomcat web容器的创建其实就是在这里扩展的。
前面提到,当前创建的上下文是AnnotationConfigServletWebServerApplicationContext类型,查看其源码后发现其没有重写onRefresh()方法,那么我们继续从它的父类ServletWebServerApplicationContext去查找,发现重写了onRefresh()方法,源码如下:
protected void onRefresh() {
super.onRefresh();
try {
// 创建WebServer实例 例如创建Tomcat容器
createWebServer();
}
catch (Throwable ex) {
throw new ApplicationContextException("Unable to start web server", ex);
}
}
我们看到,首先调用了父类的onRefresh()方法,然后调用createWebServer()创建WebServer实例。
// 创建web容器
private void createWebServer() {
WebServer webServer = this.webServer;
ServletContext servletContext = getServletContext();
if (webServer == null && servletContext == null) {
// 获取到web容器工厂。
// 实际上就是从ServletWebServerFactoryAutoConfiguration这个自动配置类导入的EmbeddedTomcat这个内嵌Tomcat类中注入的TomcatServletWebServerFactory
ServletWebServerFactory factory = getWebServerFactory();
// 获取具体的web容器,例如TomcatWebServer等
/** {@link TomcatServletWebServerFactory#getWebServer(ServletContextInitializer...)} */
this.webServer = factory.getWebServer(getSelfInitializer());
}
else if (servletContext != null) {
try {
getSelfInitializer().onStartup(servletContext);
}
catch (ServletException ex) {
throw new ApplicationContextException("Cannot initialize servlet context",
ex);
}
}
initPropertySources();
}
主要有两个步骤:
2.2.1、getWebServerFactory()
获取到web容器工厂。
protected ServletWebServerFactory getWebServerFactory() {
// Use bean names so that we don't consider the hierarchy
String[] beanNames = getBeanFactory()
.getBeanNamesForType(ServletWebServerFactory.class);
if (beanNames.length == 0) {
throw new ApplicationContextException(
"Unable to start ServletWebServerApplicationContext due to missing "
+ "ServletWebServerFactory bean.");
}
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);
}
可以看到,通过在spring容器中查找ServletWebServerFactory工厂,并且有且只有一个web容器工厂。
那么这个容器工厂是在哪里注入的呢?
这个就跟自动装配有关了,SpringBoot在启动的时候,会加载spring.factories文件中EnableAutoConfiguration对应的那些配置类,其中就包括了跟Tomcat有关的几个:EmbeddedWebServerFactoryCustomizerAutoConfiguration、ServletWebServerFactoryAutoConfiguration,Spring会在满足ConditionalXXX条件的情况下,注入对应的容器工厂。
具体如下图:
我们看到,当前Tomcat环境,获取到的就是TomcatServletWebServerFactory。
2.2.2、factory.getWebServer()
获取具体的web容器。
SpringBoot默认集成了几种web容器,比如Tomcat、Undertow、Jetty等。SpringBoot定义了一个WebServer接口抽象web容器,提供了启动、停止、获取端口等方法。相关类图如下:
接着分析如何获取到具体的web容器的:
// 获取Tomcat web容器
@Override
public WebServer getWebServer(ServletContextInitializer... initializers) {
// 创建Tomcat容器
Tomcat tomcat = new Tomcat();
// 创建Tomcat工作目录
File baseDir = (this.baseDirectory != null) ? this.baseDirectory
: createTempDir("tomcat");
tomcat.setBaseDir(baseDir.getAbsolutePath());
// 创建连接对象。Connector是Tomcat的重要组件,主要负责处理客户端连接,以及请求处理
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);
}
// 准备Tomcat上下文
prepareContext(tomcat.getHost(), initializers);
// 返回创建的TomcatWebServer
return getTomcatWebServer(tomcat);
}
可以看到,这里创建了Tomcat容器对象,并且创建了连接器、配置引擎、上下文等,最后调用getTomcatWebServer()方法:
protected TomcatWebServer getTomcatWebServer(Tomcat tomcat) {
return new TomcatWebServer(tomcat, getPort() >= 0);
}
查看TomcatWebServer类的构造方法:
public TomcatWebServer(Tomcat tomcat, boolean autoStart) {
Assert.notNull(tomcat, "Tomcat Server must not be null");
this.tomcat = tomcat;
this.autoStart = autoStart;
// Tomcat容器初始化
initialize();
}
可以看到,在TomcatWebServer类的构造方法中,调用initialize()方法对Tomcat容器进行了初始化:
private void initialize() throws WebServerException {
// 这个日志就是我们springboot项目启动日志中看到的
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())) {
// Remove service connectors so that protocol binding doesn't
// happen when the service is started.
removeServiceConnectors();
}
});
// Start the server to trigger initialization listeners
// 启动Tomcat服务器触发初始化监听器
this.tomcat.start();
// We can re-throw failure exception directly in the main thread
rethrowDeferredStartupExceptions();
try {
ContextBindings.bindClassLoader(context, context.getNamingToken(),
getClass().getClassLoader());
}
catch (NamingException ex) {
// Naming is not enabled. Continue
}
// Unlike Jetty, all Tomcat threads are daemon threads. We create a
// blocking non-daemon to stop immediate shutdown
startDaemonAwaitThread();
}
catch (Exception ex) {
stopSilently();
throw new WebServerException("Unable to start embedded Tomcat", ex);
}
}
}
以上就是Tomcat内嵌web容器的创建、初始化流程,整个过程其实是在onRefresh()方法中完成的。
2.2.3、 finishRefresh()
onRefresh()方法执行完成后,最后还有一步: finishRefresh(),这里主要是完成Spring IOC容器刷新完成后的一些额外处理。
通过查看源码,我们发现ServletWebServerApplicationContext类也重写了finishRefresh()方法:
@Override
protected void finishRefresh() {
super.finishRefresh();
WebServer webServer = startWebServer();
if (webServer != null) {
publishEvent(new ServletWebServerInitializedEvent(webServer, this));
}
}
这里调用了startWebServer()方法,也是启动Tomcat容器:
private WebServer startWebServer() {
WebServer webServer = this.webServer;
if (webServer != null) {
webServer.start();
}
return webServer;
}
最后会执行到TomcatWebServer的start()方法:
public void start() throws WebServerException {
synchronized (this.monitor) {
if (this.started) {
return;
}
try {
addPreviouslyRemovedConnectors();
Connector connector = this.tomcat.getConnector();
if (connector != null && this.autoStart) {
performDeferredLoadOnStartup();
}
checkThatConnectorsHaveStarted();
this.started = true;
// springboot项目启动成功就会输出这条日志
logger.info("Tomcat started on port(s): " + getPortsDescription(true)
+ " with context path '" + getContextPath() + "'");
}
catch (ConnectorStartFailedException ex) {
stopSilently();
throw ex;
}
catch (Exception ex) {
throw new WebServerException("Unable to start embedded Tomcat server",
ex);
}
finally {
Context context = findContext();
ContextBindings.unbindClassLoader(context, context.getNamingToken(),
getClass().getClassLoader());
}
}
}
以上就是SpringBoot内嵌Tomcat容器的启动过程。
三、总结
完整调用链路为:
SpringBoot内置Tomcat启动流程要从main函数入手,而main函数中的run()方法实际上是调用SpringApplication的run()方法。在run()方法中,先创建一个ConfigurableApplicationContext对象,通过createApplicationContext()对象进行创建,这个对象实际上就是JavaWeb的ApplicationContext对象。然后调用refreshContext()方法,在该方法中,又调用了refresh()方法,此方法中定义了Tomcat创建流程,调用ServletWebServerApplicationContext的onRefresh()方法,在该方法中调用了createWebServer()方法,在该方法中,先获取ServletWebServerFactory,再根据工厂获取具体的webServer,此时获取的是TomcatServletWebServerFacotry这个工厂,然后在getWebServer()方法中,创建Tomcat的一些核心组件。然后调用getTomcatWebServer()方法,进行初始化Tomcat。最后调用refresh()中的finishRefresh()方法,该方法被ServletWebServerApplicationContext子类重写,在该方法中调用start()方法将Tomcat启动。