4. [SpringBoot source code] SpringBoot built-in Tomcat startup process

Table of contents

1. Introduction

2. Built-in Tomcat startup process

2.1、createApplicationContext()

2.2、refreshContext(context) 

2.2.1、getWebServerFactory()

2.2.2、factory.getWebServer()

2.2.3、 finishRefresh()

3. Summary

The source code of this article is analyzed based on the spring-boot-2.1.0.RELEASE version, and there may be some differences in each version.

1. Introduction

A large part of the reason why the SpringBoot project is easy to deploy is because you don't have to toss about Tomcat-related configurations yourself, because it has various built-in Servlet containers. How SpringBoot can start the container and deploy itself to it by simply running a main function. Next, we will analyze how Tomcat is automatically started during the SpringBoot startup process from the perspective of source code.

2. Built-in Tomcat startup process

Start the analysis from the main method of the SpringBoot project startup entry:

public class SampleTomcatApplication {

	public static void main(String[] args) {
		SpringApplication.run(SampleTomcatApplication.class, args);
	}

}

Go directly to the run() method: 

// run方法是一个静态方法,用于启动SpringBoot
public static ConfigurableApplicationContext run(Class<?>[] primarySources,
        String[] args) {
    // 构建一个SpringApplication对象,并调用其run方法来启动
    return new SpringApplication(primarySources).run(args);
}

First, a SpringApplication object will be instantiated, look at its construction method: 

// 创建一个新的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();
}

Here is a detailed description of what each step does. Here we mainly focus on things related to Tomcat startup:

// 推导出当前启动的项目的类型
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;
}

 Currently we are a SERVLET environment:

Then execute the run() method:

// 运行 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;
}

Here we focus on the two steps related to Tomcat startup:

2.1、createApplicationContext()

Create different ApplicaitonContext context objects according to the webApplicationType calculated earlier.

// 根据不同类型创建不同类型的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);
}

As analyzed earlier, the current environment is a SERVLET environment, so AnnotationConfigServletWebServerApplicationContext is created here , and an AnnotationConfigServletWebServerApplicationContext object is instantiated through reflection. As shown below:

Note that AnnotationConfigServletWebServerApplicationContext inherits from ServletWebServerApplicationContext, and ServletWebServerApplicationContext indirectly inherits from AbstractApplicationContext.

2.2、refreshContext(context) 

// 刷新应用上下文。完成Spring IOC容器的初始化
refreshContext(context);

The refreshContext() method calls the refresh(context) method internally:

private void refreshContext(ConfigurableApplicationContext context) {
    refresh(context);
    if (this.registerShutdownHook) {
        try {
            context.registerShutdownHook();
        }
        catch (AccessControlException ex) {
            // Not allowed in some environments.
        }
    }
}

The source code of the refresh() method is as follows:

protected void refresh(ApplicationContext applicationContext) {
    Assert.isInstanceOf(AbstractApplicationContext.class, applicationContext);
    ((AbstractApplicationContext) applicationContext).refresh();
}

As you can see, the refresh() method of the previously instantiated applicationContext will be called here, because this is AnnotationConfigServletWebServerApplicationContext, because it does not override the refresh() method, so let's look at the refresh() method of its parent class ServletWebServerApplicationContext:

// org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext#refresh
public final void refresh() throws BeansException, IllegalStateException {
    try {
        super.refresh();
    }
    catch (RuntimeException ex) {
        stopAndReleaseWebServer();
        throw ex;
    }
}

Here, the refresh() method of the abstract AbstractApplicationContext is actually called directly:

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

This is actually the core process of Spring IOC container startup. Here we only focus on the Tomcat startup process.

/**
 * 9、模板方法,留给子类初始化其他的bean
 */
onRefresh();

The onRefresh() method is actually a template method, leaving some logic processing for subclass extensions. The creation of the Tomcat web container is actually extended here.

As mentioned earlier, the currently created context is of the AnnotationConfigServletWebServerApplicationContext type. After viewing its source code, we found that it did not override the onRefresh() method. Then we continued to search from its parent class ServletWebServerApplicationContext and found that the onRefresh() method was rewritten. The source code is as follows:

protected void onRefresh() {
    super.onRefresh();
    try {
        // 创建WebServer实例 例如创建Tomcat容器
        createWebServer();
    }
    catch (Throwable ex) {
        throw new ApplicationContextException("Unable to start web server", ex);
    }
}

We see that the onRefresh() method of the parent class is first called, and then createWebServer() is called to create a WebServer instance.

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

There are two main steps:

2.2.1、getWebServerFactory()

Get the web container factory.

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

It can be seen that by looking for the ServletWebServerFactory factory in the spring container, there is one and only one web container factory.

So where is this container factory injected?

This is related to automatic assembly. When SpringBoot starts, it will load the configuration classes corresponding to EnableAutoConfiguration in the spring.factories file, including several related to Tomcat: EmbeddedWebServerFactoryCustomizerAutoConfiguration, ServletWebServerFactoryAutoConfiguration, Spring will meet ConditionalXXX conditions case, inject the corresponding container factory.

The details are as follows:

We can see that in the current Tomcat environment, what is obtained is TomcatServletWebServerFactory.

2.2.2、factory.getWebServer()

Get a specific web container.

SpringBoot integrates several web containers by default, such as Tomcat, Undertow, Jetty, etc. SpringBoot defines an abstract web container with a WebServer interface, which provides methods for starting, stopping, and obtaining ports. The related class diagram is as follows:

Then analyze how to obtain the specific web container:

// 获取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);
}

 As you can see, the Tomcat container object is created here, and the connector, configuration engine, context, etc. are created, and finally the getTomcatWebServer() method is called:

protected TomcatWebServer getTomcatWebServer(Tomcat tomcat) {
    return new TomcatWebServer(tomcat, getPort() >= 0);
}

 Check out the constructor of the TomcatWebServer class:

public TomcatWebServer(Tomcat tomcat, boolean autoStart) {
    Assert.notNull(tomcat, "Tomcat Server must not be null");
    this.tomcat = tomcat;
    this.autoStart = autoStart;
    // Tomcat容器初始化
    initialize();
}

As you can see, in the constructor of the TomcatWebServer class, the initialize() method is called to initialize the Tomcat container:

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

The above is the creation and initialization process of the Tomcat embedded web container. The whole process is actually completed in the onRefresh() method.

2.2.3、 finishRefresh()

After the onRefresh() method is executed, there is one last step: finishRefresh(), which mainly completes some additional processing after the Spring IOC container is refreshed.

By looking at the source code, we found that the ServletWebServerApplicationContext class also rewrites the finishRefresh() method:

@Override
protected void finishRefresh() {
    super.finishRefresh();
    WebServer webServer = startWebServer();
    if (webServer != null) {
        publishEvent(new ServletWebServerInitializedEvent(webServer, this));
    }
}

The startWebServer() method is called here, which also starts the Tomcat container:

private WebServer startWebServer() {
    WebServer webServer = this.webServer;
    if (webServer != null) {
        webServer.start();
    }
    return webServer;
}

Finally, the start() method of TomcatWebServer will be executed:

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

The above is the startup process of SpringBoot embedded Tomcat container.

3. Summary

The complete calling link is:

The startup process of SpringBoot's built-in Tomcat starts with the main function, and the run() method in the main function actually calls the run() method of SpringApplication. In the run() method, first create a ConfigurableApplicationContext object and create it through the createApplicationContext() object. This object is actually the ApplicationContext object of JavaWeb. Then call the refreshContext() method. In this method, the refresh() method is called. This method defines the Tomcat creation process, calls the onRefresh() method of ServletWebServerApplicationContext, and calls the createWebServer() method in this method. In the method, first obtain the ServletWebServerFactory, and then obtain the specific webServer according to the factory. At this time, the factory TomcatServletWebServerFactory is obtained, and then in the getWebServer() method, some core components of Tomcat are created. Then call the getTomcatWebServer() method to initialize Tomcat. Finally, call the finishRefresh() method in refresh(), which is rewritten by the ServletWebServerApplicationContext subclass, and call the start() method in this method to start Tomcat.

Guess you like

Origin blog.csdn.net/Weixiaohuai/article/details/128898277