SpringBoot内嵌Tomcat启动过程
与SpringMVC不同的是,内嵌Tomcat的启动过程中并没有使用到Spring定义的SpringServletContainerInitializer
类,而是用到了TomcatStarter
这个类。我们从SpringApplication#run开始分析
SpringApplication#run
该方法就是用于启动一个SpringBoot项目,前面的文章已经分析过了,这里就看与本文相关的内容
public ConfigurableApplicationContext run(String... args) {
// ..
ConfigurableApplicationContext context = null;
// ..
try {
// ..
context = createApplicationContext();
// ..
refreshContext(context);
// ..
}
catch (Throwable ex) {
// ..
throw new IllegalStateException(ex);
}
// ..
return context;
}
这里我们重点关注ConfigurableApplicationContext的相关操作,该接口被大部分的Context实现,提供了包括从ApplicationContext中继承的功能和一些额外的功能,同时它也封装了一些配置和生命周期的方法
context = createApplicationContext();
这行代码会根据this.webApplicationType来创建对应的Context
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);
}
根据webApplicationType的不同会实例化不同的Context,分别为:
- Servlet:AnnotationConfigServletWebServerApplicationContext
- Reactive:AnnotationConfigReactiveWebServerApplicationContext
- None:AnnotationConfigApplicationContext
一般我们编写的Web应用程序都是基于Servlet的,所以这里会调用BeanUtils#instantiateClass
并通过反射的方式创建一个AnnotationConfigServletWebServerApplicationContext,创建好context之后,就是刷新context的过程了,也就是下面的#refreshContext
,非常重要
refreshContext();
private void refreshContext(ConfigurableApplicationContext context) {
// 调用实例方法refresh
refresh(context);
if (this.registerShutdownHook) {
try {
context.registerShutdownHook();
}
catch (AccessControlException ex) {
// Not allowed in some environments.
}
}
}
protected void refresh(ApplicationContext applicationContext) {
Assert.isInstanceOf(AbstractApplicationContext.class, applicationContext);
// 将ApplicationContext转换为AbstractApplicationContext并调用其refresh方法
((AbstractApplicationContext) applicationContext).refresh();
}
AbstractApplicationContext#refresh
看一下这个方法的注释,大概意思就是说,该方法用于加载或者刷新各种形式的配置(如xml,properties或者是database等形式),该方法作为一个启动方法,如果它执行失败,那么所有在执行过程中的单例bean都应该被销毁
@Override
public void refresh() throws BeansException, IllegalStateException {
synchronized (this.startupShutdownMonitor) {
// ..
try {
// ..
// Initialize other special beans in specific context subclasses.
onRefresh();
// Last step: publish corresponding event.
finishRefresh();
// ..
}
// ..
}
}
这里的onRefresh方法会调用ServletWebServerApplicationContext#onRefresh
ServletWebServerApplicationContext#onRefresh
@Override
protected void onRefresh() {
super.onRefresh();
try {
// 创建webServer
createWebServer();
}
catch (Throwable ex) {
throw new ApplicationContextException("Unable to start web server", ex);
}
}
ServletWebServerApplicationContext#createWebServer
private void createWebServer() {
WebServer webServer = this.webServer;
ServletContext servletContext = getServletContext();
if (webServer == null && servletContext == null) {
ServletWebServerFactory factory = getWebServerFactory();
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();
}
- 获取ServletContext
- 如果ServletContext为空并且webServer为空则调用ServletWebServerFactory#getWebServer获取webServer,第一次启动时该条件都为真
- getSelfInitializer()用于获取ServletContextInitializer
TomcatServletWebServerFactory#getWebServer
@Override
public WebServer getWebServer(ServletContextInitializer... initializers) {
if (this.disableMBeanRegistry) {
Registry.disableRegistry();
}
Tomcat tomcat = new Tomcat();
File baseDir = (this.baseDirectory != null) ? this.baseDirectory : createTempDir("tomcat");
tomcat.setBaseDir(baseDir.getAbsolutePath());
Connector connector = new Connector(this.protocol);
connector.setThrowOnFailure(true);
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);
return getTomcatWebServer(tomcat);
}
该方法用于获取一个WebServer,这里由于我们使用的是Tomcat,因此我们使用的是TomcatWebServer
- 创建一个tomcat实例
- 然后获取Connector
- 接着将connector设置到tomcat中
- 然后调用#prepareContext来构建并配置一个TomcatEmbeddedContext
TomcatEmbededContext
TomcatWebServer会使用该context来支持延迟初始化
TomcatServletWebServerFactory#prepareContext
protected void prepareContext(Host host, ServletContextInitializer[] initializers) {
File documentRoot = getValidDocumentRoot();
TomcatEmbeddedContext context = new TomcatEmbeddedContext();
if (documentRoot != null) {
context.setResources(new LoaderHidingResourceRoot(context));
}
context.setName(getContextPath());
context.setDisplayName(getDisplayName());
context.setPath(getContextPath());
File docBase = (documentRoot != null) ? documentRoot : createTempDir("tomcat-docbase");
context.setDocBase(docBase.getAbsolutePath());
context.addLifecycleListener(new FixContextListener());
context.setParentClassLoader((this.resourceLoader != null) ? this.resourceLoader.getClassLoader()
: ClassUtils.getDefaultClassLoader());
resetDefaultLocaleMapping(context);
addLocaleMappings(context);
try {
context.setCreateUploadTargets(true);
}
catch (NoSuchMethodError ex) {
// Tomcat is < 8.5.39. Continue.
}
configureTldSkipPatterns(context);
WebappLoader loader = new WebappLoader();
loader.setLoaderClass(TomcatEmbeddedWebappClassLoader.class.getName());
loader.setDelegate(true);
context.setLoader(loader);
if (isRegisterDefaultServlet()) {
addDefaultServlet(context);
}
if (shouldRegisterJspServlet()) {
addJspServlet(context);
addJasperInitializer(context);
}
context.addLifecycleListener(new StaticResourceConfigurer(context));
// (1)
ServletContextInitializer[] initializersToUse = mergeInitializers(initializers);
host.addChild(context);
// (2)
configureContext(context, initializersToUse);
postProcessContext(context);
}
- 对context设置名字,路径,DocBase,LifecycleListener,classLoader等
- 1)处:这里会将ServletContextInitializer合并,得到一个能够被使用的initializer列表
- 2)处:配置context,这里会将TomcatStarter创建并放入context的initializers列表中,配置errorPage,增加其他的LifecycleListener,配置mineMapping,配置session等
AbstractServletWebServerFactory#mergeInitializers
protected final ServletContextInitializer[] mergeInitializers(ServletContextInitializer... initializers) {
List<ServletContextInitializer> mergedInitializers = new ArrayList<>();
mergedInitializers.add((servletContext) -> this.initParameters.forEach(servletContext::setInitParameter));
mergedInitializers.add(new SessionConfiguringInitializer(this.session));
mergedInitializers.addAll(Arrays.asList(initializers));
mergedInitializers.addAll(this.initializers);
return mergedInitializers.toArray(new ServletContextInitializer[0]);
}
这是一个工具类的方法,子类可以重写该方法来达到组合特定ServletContextInitializer的目的,当我们代码运行完该方法后,initializersToUse会有3个,分别是
- AbstractServletWebServerFactory$lambda
- AbstractServletWebServerFactory$SessionConfiguringInitializer
- ServletWebServerApplicationContext$lambda
TomcatServletWebServerFactory#configureContext
protected void configureContext(Context context, ServletContextInitializer[] initializers) {
TomcatStarter starter = new TomcatStarter(initializers);
if (context instanceof TomcatEmbeddedContext) {
TomcatEmbeddedContext embeddedContext = (TomcatEmbeddedContext) context;
embeddedContext.setStarter(starter);
embeddedContext.setFailCtxIfServletStartFails(true);
}
context.addServletContainerInitializer(starter, NO_CLASSES);
for (LifecycleListener lifecycleListener : this.contextLifecycleListeners) {
context.addLifecycleListener(lifecycleListener);
}
for (Valve valve : this.contextValves) {
context.getPipeline().addValve(valve);
}
for (ErrorPage errorPage : getErrorPages()) {
org.apache.tomcat.util.descriptor.web.ErrorPage tomcatErrorPage = new org.apache.tomcat.util.descriptor.web.ErrorPage();
tomcatErrorPage.setLocation(errorPage.getPath());
tomcatErrorPage.setErrorCode(errorPage.getStatusCode());
tomcatErrorPage.setExceptionType(errorPage.getExceptionName());
context.addErrorPage(tomcatErrorPage);
}
for (MimeMappings.Mapping mapping : getMimeMappings()) {
context.addMimeMapping(mapping.getExtension(), mapping.getMimeType());
}
configureSession(context);
new DisableReferenceClearingContextCustomizer().customize(context);
for (TomcatContextCustomizer customizer : this.tomcatContextCustomizers) {
customizer.customize(context);
}
}
配置TomcatEmbeddedContext,然后遍历成员变量tomcatContextCustomizers,并调用其#customize
private Set tomcatContextCustomizers = new LinkedHashSet<>();
一个元素类型为TomcatContexCustomizer的Set
TomcatContextCustomizer
@FunctionalInterface
public interface TomcatContextCustomizer {
/**
* Customize the context.
* @param context the context to customize
*/
void customize(Context context);
}
一个回调接口,它的实现类只有一个,DisableReferenceClearingContextCustomizer
class DisableReferenceClearingContextCustomizer implements TomcatContextCustomizer {
@Override
public void customize(Context context) {
if (!(context instanceof StandardContext)) {
return;
}
StandardContext standardContext = (StandardContext) context;
try {
standardContext.setClearReferencesObjectStreamClassCaches(false);
standardContext.setClearReferencesRmiTargets(false);
standardContext.setClearReferencesThreadLocals(false);
}
catch (NoSuchMethodError ex) {
// Earlier version of Tomcat (probably without
// setClearReferencesThreadLocals). Continue.
}
}
}
创建一个StandardContext,清除该context中的threadLocal、rmiTargets、ObjectStreamClassCaches引用
到这里,#prepareContext就执行完了,接着我们看TomcatServletWebServerFactory#getTomcatWebServer
TomcatServletWebServerFactory#getTomcatWebServer
protected TomcatWebServer getTomcatWebServer(Tomcat tomcat) {
return new TomcatWebServer(tomcat, getPort() >= 0);
}
创建一个TomcatWebServer,并将刚刚配置好的tomcat传入,该类实现了WebServer接口,用于控制tomcat的行为
public TomcatWebServer(Tomcat tomcat, boolean autoStart) {
Assert.notNull(tomcat, "Tomcat Server must not be null");
this.tomcat = tomcat;
this.autoStart = autoStart;
initialize();
}
构造函数初始化成员变量tomcat,autoStart,然后调用了#initialize
TomcatWebServer#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())) {
// Remove service connectors so that protocol binding doesn't
// happen when the service is started.
removeServiceConnectors();
}
});
// Start the server to trigger initialization listeners
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();
destroySilently();
throw new WebServerException("Unable to start embedded Tomcat", ex);
}
}
}
流程执行到这里,Tomcat就初始化完毕了,此时控制台中应该打印出
2020-09-05 17:41:49.735 INFO 39372 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat initialized with port(s): 8080 (http)
接着会执行tomat.start()方法来启动tomcat和tomcat engine,控制台打印出下面的日志
2020-09-05 17:43:06.702 INFO 39372 --- [ main] o.apache.catalina.core.StandardService : Starting service [Tomcat]
2020-09-05 17:43:06.703 INFO 39372 --- [ main] org.apache.catalina.core.StandardEngine : Starting Servlet engine: [Apache Tomcat/9.0.34]
接着就会调用TomcatStarter#onStartup方法,TomcatStarter实现了ServletContainerInitializer接口,之前我们分析过了,当Servlet容器启动时首先会执行该接口的实现类的onStartup方法
TomcatStarter#onStartup
@Override
public void onStartup(Set<Class<?>> classes, ServletContext servletContext) throws ServletException {
try {
for (ServletContextInitializer initializer : this.initializers) {
initializer.onStartup(servletContext);
}
}
catch (Exception ex) {
this.startUpException = ex;
// Prevent Tomcat from logging and re-throwing when we know we can
// deal with it in the main thread, but log for information here.
if (logger.isErrorEnabled()) {
logger.error("Error starting Tomcat context. Exception: " + ex.getClass().getName() + ". Message: "
+ ex.getMessage());
}
}
}
第一个参数是没有值的,之前我们分析过了,该参数是由@HandlesTypes注解传入,那这里没有使用到该注解所以就没有值,接着就是遍历之前找到的3个initializer并执行它们的onStartup方法
执行完#onStartup之后,方法跳转到ServletWebServerApplicationContext#selfInitialize
ServletWebServerApplicationContext#selfInitialize
private void selfInitialize(ServletContext servletContext) throws ServletException {
prepareWebApplicationContext(servletContext);
registerApplicationScope(servletContext);
WebApplicationContextUtils.registerEnvironmentBeans(getBeanFactory(), servletContext);
for (ServletContextInitializer beans : getServletContextInitializerBeans()) {
beans.onStartup(servletContext);
}
}
- 调用#prepareWebApplicationContext,该方法根据已经被完整加载的ServletContext来配置WebApplicationContext
- 调用#registerApplicationScope,注册应用的作用域
- 调用WebApplicationContextUtils#registerEnvironmentBeans
- 对ServletConextInitlializers进行排序并再次调用#onStartup
ServletWebServerApplicationContext#prepareWebApplicationContext
该方法如果打印出下面的日志就表示执行成功
2020-09-05 17:48:52.839 INFO 39372 --- [ main] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring embedded WebApplicationContext
2020-09-05 17:48:52.842 INFO 39372 --- [ main] o.s.web.context.ContextLoader : Root WebApplicationContext: initialization completed in 1540008 ms
ServletWebServerApplicationContext#getServletContextInitializerBeans
这里获取到了Servlet和Filter的RegistrationBean,然后分别调用它们的#onStartup
到这里,#onRefresh的流程就执行完毕了,接着会调用ServletWebServerApplicationContext#finishRefresh
ServletWebServerApplicationContext#finishRefresh
@Override
protected void finishRefresh() {
super.finishRefresh();
WebServer webServer = startWebServer();
if (webServer != null) {
publishEvent(new ServletWebServerInitializedEvent(webServer, this));
}
}
- 首先会调用父类AbstractApplicationContext#finishRefresh来发布ContextRefreshedEvent事件,执行LifecycleProcessor#onRefresh
- 然后就会启动WebServer并发布ServletWebServerInitializedEvent事件
ServletWebServerApplicationContext#startWebServer
该方法用于启动一个WebServer,这里我们启动的是TomcatWebServer
private WebServer startWebServer() {
WebServer webServer = this.webServer;
if (webServer != null) {
webServer.start();
}
return webServer;
}
TomcatWebServer#start
@Override
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;
logger.info("Tomcat started on port(s): " + getPortsDescription(true) + " with context path '"
+ getContextPath() + "'");
}
catch (ConnectorStartFailedException ex) {
stopSilently();
throw ex;
}
catch (Exception ex) {
PortInUseException.throwIfPortBindingException(ex, () -> this.tomcat.getConnector().getPort());
throw new WebServerException("Unable to start embedded Tomcat server", ex);
}
finally {
Context context = findContext();
ContextBindings.unbindClassLoader(context, context.getNamingToken(), getClass().getClassLoader());
}
}
}
该方法执行完成之后内嵌的Tomcat容器就启动成功了,同时打印出熟悉的日志
2020-09-05 18:09:20.432 INFO 39372 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port(s): 8080 (http) with context path ''
至此,我们就分析完了SpringBoot内嵌的Tomcat容器的整个启动流程