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

image.png
看一下这个方法的注释,大概意思就是说,该方法用于加载或者刷新各种形式的配置(如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

image.png

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());
        }
    }
}

image.png
第一个参数是没有值的,之前我们分析过了,该参数是由@HandlesTypes注解传入,那这里没有使用到该注解所以就没有值,接着就是遍历之前找到的3个initializer并执行它们的onStartup方法
image.png
执行完#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

image.png
这里获取到了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容器的整个启动流程