4.【SpringBoot源码】SpringBoot内置Tomcat启动流程

目录

一、简介

二、内置Tomcat启动流程

2.1、createApplicationContext()

2.2、refreshContext(context) 

2.2.1、getWebServerFactory()

2.2.2、factory.getWebServer()

2.2.3、 finishRefresh()

三、总结

本篇源码基于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启动。

猜你喜欢

转载自blog.csdn.net/Weixiaohuai/article/details/128898277