SpringBoot如何内置Tomcat

SpringBoot如何内置Tomcat

一、前言

在初次接触 SpringBoot 的时候,就很奇怪为什么直接运行主类的main方法就可以启动程序,但却一直没有深究,直到前段时间,突然听说了一个词,叫做 Cargo Cult ,才开始对自己有所反省,这的确是对我目前状态的一种描述,为了从微小的细节开始,尽自己的努力摆脱这个魔鬼一样的词语,今天决定探究一下 SpringBoot 究竟是如何内置 Tomcat 的(能力限制,目前只尝试进行浅易地“溯源”)。

二、正文

1. 启动类

从我们开始学会新建第一个 SpringBoot 项目开始,我们就一定会看到这样一个类。

package com.xfc;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

/**
 * 启动
 *
 * @Auther: ErDong
 * @Email: xfc_exclave@163.com
 * @Date: 2019/11/30 11:51
 * @Description:
 */
@SpringBootApplication
public class MuYiApplication {

    public static void main(String[] args) {
        SpringApplication.run(MuYiApplication.class, args);// 进入run()
    }

}

我们把这个类叫做 主类 ,或者说是 启动类 ,于是,我们进入 run() 方法。

2. SpringApplication类注释

这里我们首先阅读一下 SpringApplication 的类注释:

// SpringApplication.class 类注释

/**
 * Class that can be used to bootstrap and launch a Spring application from a Java main
 * method. By default class will perform the following steps to bootstrap your
 * application:
 *
 * <ul>
 * <li>Create an appropriate {@link ApplicationContext} instance (depending on your
 * classpath)</li>
 * <li>Register a {@link CommandLinePropertySource} to expose command line arguments as
 * Spring properties</li>
 * <li>Refresh the application context, loading all singleton beans</li>
 * <li>Trigger any {@link CommandLineRunner} beans</li>
 * </ul>
 *
 * In most circumstances the static {@link #run(Class, String[])} method can be called
 * directly from your {@literal main} method to bootstrap your application:
 *
 * <pre class="code">
 * &#064;Configuration
 * &#064;EnableAutoConfiguration
 * public class MyApplication  {
 *
 *   // ... Bean definitions
 *
 *   public static void main(String[] args) throws Exception {
 *     SpringApplication.run(MyApplication.class, args);
 *   }
 * }
 * </pre>
 *
 * <p>
 * For more advanced configuration a {@link SpringApplication} instance can be created and
 * customized before being run:
 *
 * <pre class="code">
 * public static void main(String[] args) throws Exception {
 *   SpringApplication application = new SpringApplication(MyApplication.class);
 *   // ... customize application settings here
 *   application.run(args)
 * }
 * </pre>
 *
 * {@link SpringApplication}s can read beans from a variety of different sources. It is
 * generally recommended that a single {@code @Configuration} class is used to bootstrap
 * your application, however, you may also set {@link #getSources() sources} from:
 * <ul>
 * <li>The fully qualified class name to be loaded by
 * {@link AnnotatedBeanDefinitionReader}</li>
 * <li>The location of an XML resource to be loaded by {@link XmlBeanDefinitionReader}, or
 * a groovy script to be loaded by {@link GroovyBeanDefinitionReader}</li>
 * <li>The name of a package to be scanned by {@link ClassPathBeanDefinitionScanner}</li>
 * </ul>
 *
 * Configuration properties are also bound to the {@link SpringApplication}. This makes it
 * possible to set {@link SpringApplication} properties dynamically, like additional
 * sources ("spring.main.sources" - a CSV list) the flag to indicate a web environment
 * ("spring.main.web-application-type=none") or the flag to switch off the banner
 * ("spring.main.banner-mode=off").
 */

我们知道了 SpringApplication 是用于从 Java main 方法引导和启动Spring应用程序,默认情况下,将执行下面几个步骤来引导我们的应用程序:

  1. 创建一个恰当的ApplicationContext实例(取决于类路径)
  2. 注册CommandLinePropertySource,将命令行参数公开为Spring属性。
  3. 刷新应用程序上下文,加载所有单例bean。
  4. 触发全部CommandLineRunner bean。

大多数情况下,静态 run() 方法可以在我们的启动类的 main() 方法中调用。

SpringApplication可以从各种不同的源读取bean。 通常建议使用单个@Configuration类来引导,但是我们也可以通过以下方式来设置资源:

  1. 通过AnnotatedBeanDefinitionReader加载完全限定类名。
  2. 通过XmlBeanDefinitionReader加载XML资源位置,或者是通过GroovyBeanDefinitionReader加载groovy脚本位置。
  3. 通过ClassPathBeanDefinitionScanner扫描包名称。

3. 根据注释逐步进入代码查看

从主类进入 SpringApplication.run() 方法:

// SpringApplication.class

public static ConfigurableApplicationContext run(Class<?> primarySource, String... args) {
	return run(new Class[]{primarySource}, args);// 进入run()
}

继续进入 run() 方法:

// SpringApplication.class

public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {
	return (new SpringApplication(primarySources)).run(args);// 进入run()
}

从上面两端代码,我们知道,程序将我们创建的 MuYiApplication.class 添加进一个名为 primarySources 的数组,并且使用当前数创建了一个 SpringApplication 实例。

接着,进入 SpringBoot 启动的主要逻辑代码段:

// SpringApplication.class

public ConfigurableApplicationContext run(String... args) {
    // 使用StopWatch对程序部分代码进行计时
	StopWatch stopWatch = new StopWatch();
	stopWatch.start();// 开始计时
    ConfigurableApplicationContext context = null;
    // 使用Collection收集错误报告并处理
    Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList();
    this.configureHeadlessProperty();
    SpringApplicationRunListeners listeners = this.getRunListeners(args);
    listeners.starting();

    Collection exceptionReporters;
    try {
        // 解析参数args
        ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
        // 从这里进入prepareEnvironment()后,可查看注册CommandLinePropertySource,并将命令行参数公开为Spring属性的逻辑,并返回当前程序的配置环境,这里暂不扩展说明
        ConfigurableEnvironment environment = this.prepareEnvironment(listeners, applicationArguments);
        // 读取程序配置中的spring.beaninfo.ignore内容
        this.configureIgnoreBeanInfo(environment);
        // 打印资源目录下banner.txt文件中的内容
        Banner printedBanner = this.printBanner(environment);
        // 根据应用类型创建应用上下文
        context = this.createApplicationContext();
        exceptionReporters = this.getSpringFactoriesInstances(SpringBootExceptionReporter.class, new Class[]{ConfigurableApplicationContext.class}, context);
        this.prepareContext(context, environment, listeners, applicationArguments, printedBanner);
        // 进入refreshContext()进行扩展
        this.refreshContext(context);
        // 允许上下文子类对bean工厂进行后置处理
        this.afterRefresh(context, applicationArguments);
        stopWatch.stop();// 计时结束
        if (this.logStartupInfo) {
            (new StartupInfoLogger(this.mainApplicationClass)).logStarted(this.getApplicationLog(), stopWatch);
        }

        listeners.started(context);
        this.callRunners(context, applicationArguments);
    } catch (Throwable var10) {
        this.handleRunFailure(context, var10, exceptionReporters, listeners);
        throw new IllegalStateException(var10);
    }

    try {
        listeners.running(context);
        return context;
    } catch (Throwable var9) {
	this.handleRunFailure(context, var9, exceptionReporters, (SpringApplicationRunListeners)null);
	throw new IllegalStateException(var9);
	}
}

进入 refreshContext() 刷新应用上下文:

// SpringApplication.class

private void refreshContext(ConfigurableApplicationContext context) {
    this.refresh(context);// 进入refresh()
    if (this.registerShutdownHook) {
        try {
            context.registerShutdownHook();
        } catch (AccessControlException var3) {
        }
    }
}

继续进入 refresh() 方法:

// SpringApplication.class

protected void refresh(ApplicationContext applicationContext) {
    Assert.isInstanceOf(AbstractApplicationContext.class, applicationContext);
    // 转换为AbstractApplicationContext并调用刷新。
    ((AbstractApplicationContext)applicationContext).refresh();// 进入refresh()
}

4. 进入AbstractApplicationContext

继续进入 refresh() 方法:

// AbstractApplicationContext.class

public void refresh() throws BeansException, IllegalStateException {
    synchronized(this.startupShutdownMonitor) {
        this.prepareRefresh();
        ConfigurableListableBeanFactory beanFactory = this.obtainFreshBeanFactory();
        this.prepareBeanFactory(beanFactory);

        try {
            this.postProcessBeanFactory(beanFactory);
            this.invokeBeanFactoryPostProcessors(beanFactory);
            this.registerBeanPostProcessors(beanFactory);
            this.initMessageSource();
            this.initApplicationEventMulticaster();
            // 这里暂时只关注onRefresh()方法
            this.onRefresh();// 进入onRefresh()
            this.registerListeners();
            this.finishBeanFactoryInitialization(beanFactory);
            this.finishRefresh();
        } catch (BeansException var9) {
            if (this.logger.isWarnEnabled()) {
                this.logger.warn("Exception encountered during context initialization - cancelling refresh attempt: " + var9);
            }

            this.destroyBeans();
            this.cancelRefresh(var9);
            throw var9;
        } finally {
            this.resetCommonCaches();
        }

    }
}

进入 onRefresh() 方法:

// AbstractApplicationContext.class

protected void onRefresh() throws BeansException {// 进入其子类ServletWebServerApplicationContext.class重写的onRefresh()
}

到这里,我们看到抽象类 AbstractApplicationContextonRefresh() 方法被以下子类重写:

  1. AbstractRefreshableWebApplicationContext
  2. GenericWebApplicationContext
  3. ReactiveWebServerApplicationContext
  4. ServletWebServerApplicationContext
  5. StaticWebApplicationContext

4. 揭晓

我们重点关注 ServletWebServerApplicationContext ,进入该实现类:

// ServletWebServerApplicationContext.class

protected void onRefresh() {
    super.onRefresh();
    try {
        // 创建web服务
        this.createWebServer();// 进入createWebServer()
    } catch (Throwable var2) {
        throw new ApplicationContextException("Unable to start web server", var2);
    }
}

根据名称,我们知道,这里即将进入一个与服务创建相关的方法,进入 createWebServer()

// ServletWebServerApplicationContext.class

private void createWebServer() {
    WebServer webServer = this.webServer;
    ServletContext servletContext = this.getServletContext();
    if (webServer == null && servletContext == null) {
        ServletWebServerFactory factory = this.getWebServerFactory();// 进入ServletWebServerFactory
        this.webServer = factory.getWebServer(new ServletContextInitializer[]{this.getSelfInitializer()});
    } else if (servletContext != null) {
        try {
            this.getSelfInitializer().onStartup(servletContext);
        } catch (ServletException var4) {
            throw new ApplicationContextException("Cannot initialize servlet context", var4);
        }
    }

    this.initPropertySources();
}

进入ServletWebServerFactory:

@FunctionalInterface
public interface ServletWebServerFactory {
    WebServer getWebServer(ServletContextInitializer... initializers);// 查看getWebServer()的所有实现
}

至此,我们可以看到有三个类均实现了 ServletWebServerFactory 接口中的 getWebServer 方法,他们分别是:

JettyServletWebServerFactory.class (org.springframework.boot.web.embedded.jetty)

TomcatServletWebServerFactory.class (org.springframework.boot.web.embedded.tomcat)

UndertowServletWebServerFactory.class (org.springframework.boot.web.embedded.undertow)

5. 继续探究

从这个方向继续探究下去,我们还可以在源代码中找到更多内容。

当然,从此前的任何一个分支探究下去,都同样会使我们获益匪浅。

按照这样的方法,我们可以找到很多问题的答案,例如:

  • SpringBoot 如何加载 application.yml
  • 自定义的 MyServletInitializer 何时被加载?
  • 启动类 main() 方法中的 args 有什么用?
  • logback.xml 在什么地方被加载?
  • 等等等……
posted @ 2020-07-09 07:23  Chinmoku  阅读(2418)  评论(0编辑  收藏  举报