Spring Boot 如何确定当前要使用哪个容器呢?

Spring Boot 的启动入口:

/**
 * 启动应用程序。
 */
public ConfigurableApplicationContext run(String... args) {
    
    ...
    
    try {
        // 解析命令行参数,提供给应用程序环境使用
        ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
        // 配置和准备应用程序环境
        ConfigurableEnvironment environment = this.prepareEnvironment(listeners, bootstrapContext, applicationArguments);
        // 打印应用程序的欢迎信息
        Banner printedBanner = this.printBanner(environment);
        // 创建容器
        context = this.createApplicationContext();
        // 设置应用程序启动对象,用于跟踪启动过程中的关键事件
        context.setApplicationStartup(this.applicationStartup);
        // 预备容器,包括配置环境、监听器等
        this.prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);
        // 刷新容器,用于重新加载容器中的所有bean,包括重新初始化它们的依赖关系。
        this.refreshContext(context);
        // 在容器刷新后执行额外的配置或初始化工作
        this.afterRefresh(context, applicationArguments);
        // 标记应用程序启动完成
        startup.started();
        // 如果启用了启动信息日志,则记录应用程序启动时间等信息
        if (this.logStartupInfo) {
            (new StartupInfoLogger(this.mainApplicationClass)).logStarted(this.getApplicationLog(), startup);
        }

        // 通知监听器应用程序已成功启动
        listeners.started(context, startup.timeTakenToStarted());
        // 调用自定义的运行时处理器,例如 CommandLineRunner 或 ApplicationRunner
        this.callRunners(context, applicationArguments);
    } catch (Throwable var10) {
        ex = var10;
        // 处理应用程序启动失败的情况,如抛出异常
        throw this.handleRunFailure(context, ex, listeners);
    }

    ...
    
}

重点看一下 this.refreshContext(context) 方法

/**
 * 刷新容器。
 * 此方法用于初始化或重新初始化容器。它首先检查是否需要注册关闭钩子,
 * 然后调用刷新方法来实际执行容器的刷新操作。
 *
 * @param context 容器,它是配置化、可刷新的容器实例。
 *                刷新容器将更新其内部状态,包括加载或重新加载配置,
 *                并使任何已注册的bean生效。
 */
private void refreshContext(ConfigurableApplicationContext context) {
    // 如果注册关闭钩子被启用,则注册容器的关闭钩子
    if (this.registerShutdownHook) {
        shutdownHook.registerApplicationContext(context);
    }

    // 刷新容器,这将包括加载配置、初始化bean等过程
    this.refresh(context);
}
protected void refresh(ConfigurableApplicationContext applicationContext) {
    applicationContext.refresh();
}

refresh() 是一个接口方法,我们看一下 AbstractApplicationContext 中对它的具体实现:

/**
 * 刷新容器,执行初始化和准备操作。
 * 这个方法是容器生命周期的关键部分,它负责:
 * 1. 获取锁,确保线程安全的刷新过程。
 * 2. 准备刷新过程,包括创建新的bean工厂。
 * 3. 处理bean工厂的后处理程序,注册bean后处理器等。
 * 4. 初始化消息源和应用事件多播器。
 * 5. 完成bean工厂的初始化和刷新过程。
 * 6. 在出现异常时,销毁已初始化的bean并取消刷新过程。
 * 7. 释放锁,结束刷新过程。
 *
 * @throws BeansException           如果bean创建过程中出现异常。
 * @throws IllegalStateException    如果容器处于不合法的状态。
 */
public void refresh() throws BeansException, IllegalStateException {
    // 获取锁,以确保刷新过程的线程安全。
    this.startupShutdownLock.lock();

    try {
        // 记录当前线程,因为在刷新过程中可能会启动其他线程。
        this.startupShutdownThread = Thread.currentThread();
        // 开始一个StartupStep,用于跟踪容器刷新的性能。
        StartupStep contextRefresh = this.applicationStartup.start("spring.context.refresh");
        // 准备刷新过程,包括设置初始配置等。
        this.prepareRefresh();
        // 创建一个新的bean工厂,这是容器的核心组件。
        ConfigurableListableBeanFactory beanFactory = this.obtainFreshBeanFactory();
        // 配置bean工厂,准备进行bean的实例化。
        this.prepareBeanFactory(beanFactory);

        try {
            // 处理bean工厂的后处理程序,这些程序可以修改bean的定义和实例。
            this.postProcessBeanFactory(beanFactory);
            // 开始跟踪bean后处理程序的性能。
            StartupStep beanPostProcess = this.applicationStartup.start("spring.context.beans.post-process");
            // 实例化并调用所有的bean后处理器。
            this.invokeBeanFactoryPostProcessors(beanFactory);
            // 注册bean后处理器,这些处理器将在bean实例化后应用。
            this.registerBeanPostProcessors(beanFactory);
            // 结束bean后处理程序的性能跟踪。
            beanPostProcess.end();
            // 初始化消息源,用于国际化支持。
            this.initMessageSource();
            // 初始化应用事件多播器,用于事件驱动的编程模型。
            this.initApplicationEventMulticaster();
            // 执行自定义的刷新回调,允许用户在容器刷新时进行干预。
            this.onRefresh();
            // 注册监听器,这些监听器可以监听应用事件。
            this.registerListeners();
            // 完成bean工厂的初始化,包括实例化所有的单例bean。
            this.finishBeanFactoryInitialization(beanFactory);
            // 完成刷新过程,标记容器为活动状态。
            this.finishRefresh();
        } catch (Error | RuntimeException ex) {
            // 在初始化过程中遇到严重异常时,记录异常并取消刷新。
            Throwable exWrapper = ex;
            if (this.logger.isWarnEnabled()) {
                this.logger.warn("Exception encountered during context initialization - cancelling refresh attempt: " + ex);
            }
            // 销毁已初始化的bean,以防止资源泄漏。
            this.destroyBeans();
            // 取消刷新过程,释放相关资源。
            this.cancelRefresh(exWrapper);
            // 重新抛出异常,让调用者知道刷新失败。
            throw ex;
        } finally {
            // 结束容器刷新的性能跟踪。
            contextRefresh.end();
        }
    } finally {
        // 释放锁,允许其他线程进行刷新操作。
        this.startupShutdownThread = null;
        this.startupShutdownLock.unlock();
    }
}

/**
 * 当组件状态刷新时执行的方法。
 *
 * 该方法在组件状态刷新时被调用,用于执行与组件刷新相关的操作。
 * 子类可以重写此方法以提供特定的刷新逻辑。
 * 
 * @throws BeansException 如果在刷新过程中遇到bean相关的异常
 */
protected void onRefresh() throws BeansException {
    // 空实现,提供给子类重写。在基类中留空是为了允许子类根据需要实现特定的刷新逻辑。
}

我们重点看一下 中对 this.onRefresh() 的重写

/**
 * 当刷新上下文时执行此方法,旨在创建并启动一个Web服务器。
 * 此方法是在父类方法基础上的扩展,旨在在容器刷新时,提供额外的Web服务器启动逻辑。
 * 如果Web服务器启动失败,将抛出一个容器异常。
 */
protected void onRefresh() {
    // 调用父类的onRefresh方法,以确保完成基本的容器刷新操作。
    super.onRefresh();

    try {
        // 尝试创建并启动Web服务器。
        this.createWebServer();
    } catch (Throwable var2) {
        // 捕获在启动Web服务器过程中可能抛出的任何异常。
        // 将异常包装并抛出为ApplicationContextException,以便上层处理。
        Throwable ex = var2;
        throw new ApplicationContextException("Unable to start web server", ex);
    }
}

这里的 createWebServer() 就是真正创建容器的位置

/**
 * 创建Web服务器。根据当前容器条件决定是创建一个新的Web服务器还是初始化已存在的Servlet容器。
 *
 * 如果既没有现有的Web服务器实例,也没有Servlet容器,则创建一个新的Web服务器。
 * 如果存在Servlet容器,则只需初始化Servlet容器。
 * 此方法还负责注册Web服务器的优雅关闭和启动停止生命周期监听器。
 */
private void createWebServer() {
    // 检查是否已经存在Web服务器实例或Servlet容器
    WebServer webServer = this.webServer;
    ServletContext servletContext = this.getServletContext();
    if (webServer == null && servletContext == null) {
        // 当既没有Web服务器实例,也没有Servlet容器时,开始创建Web服务器的过程
        StartupStep createWebServer = this.getApplicationStartup().start("spring.boot.webserver.create");
        ServletWebServerFactory factory = this.getWebServerFactory();
        createWebServer.tag("factory", factory.getClass().toString());
        // 使用工厂创建并配置新的Web服务器
        this.webServer = factory.getWebServer(new ServletContextInitializer[]{this.getSelfInitializer()});
        createWebServer.end();
        // 注册Web服务器的优雅关闭监听器
        this.getBeanFactory().registerSingleton("webServerGracefulShutdown", new WebServerGracefulShutdownLifecycle(this.webServer));
        // 注册Web服务器的启动停止监听器
        this.getBeanFactory().registerSingleton("webServerStartStop", new WebServerStartStopLifecycle(this, this.webServer));
    } else if (servletContext != null) {
        // 如果存在Servlet容器,但没有Web服务器实例,则只需初始化Servlet容器
        try {
            this.getSelfInitializer().onStartup(servletContext);
        } catch (ServletException var5) {
            // 将Servlet初始化过程中的异常转换为ApplicationContextException,以保持一致的异常处理
            ServletException ex = var5;
            throw new ApplicationContextException("Cannot initialize servlet context", ex);
        }
    }

    // 初始化属性源
    this.initPropertySources();
}

这里可以看到,使用哪个类型的 web 服务器是获取什么类型的 web 服务器工厂决定的,也就是 this.getWebServerFactory() 方法

/**
 * 获取ServletWebServerFactory实例。
 * 
 * 此方法用于在应用程序上下文中查找并返回唯一的ServletWebServerFactory bean。如果找不到任何bean,
 * 或找到多个这样的bean,都会抛出相应的异常。
 * 
 * @return ServletWebServerFactory的实例,用于创建和管理Servlet容器。
 * @throws MissingWebServerFactoryBeanException 如果没有找到ServletWebServerFactory类型的bean。
 * @throws ApplicationContextException 如果找到多个ServletWebServerFactory类型的bean。
 */
protected ServletWebServerFactory getWebServerFactory() {
    // 获取所有ServletWebServerFactory类型的bean名称
    String[] beanNames = this.getBeanFactory().getBeanNamesForType(ServletWebServerFactory.class);
    
    // 检查是否没有找到ServletWebServerFactory类型的bean
    if (beanNames.length == 0) {
        throw new MissingWebServerFactoryBeanException(this.getClass(), ServletWebServerFactory.class, WebApplicationType.SERVLET);
    }
    // 检查是否找到了多个ServletWebServerFactory类型的bean
    else if (beanNames.length > 1) {
        throw new ApplicationContextException("Unable to start ServletWebServerApplicationContext due to multiple ServletWebServerFactory beans : " + StringUtils.arrayToCommaDelimitedString(beanNames));
    }
    // 返回找到的唯一ServletWebServerFactory类型的bean
    else {
        return (ServletWebServerFactory)this.getBeanFactory().getBean(beanNames[0], ServletWebServerFactory.class);
    }
}

这里我们就可以发现,Spring Boot 是在容器中查找出注册的 web 服务器的工厂来确定使用哪个 web 服务器的,那 web 服务器的工厂又是再哪里注册到容器中的呢?

我们看一下 ServletWebServerFactoryAutoConfiguration

@AutoConfiguration(
    after = {SslAutoConfiguration.class}
)
@AutoConfigureOrder(Integer.MIN_VALUE)
@ConditionalOnClass({ServletRequest.class})
@ConditionalOnWebApplication(
    type = Type.SERVLET
)
@EnableConfigurationProperties({ServerProperties.class})
@Import({BeanPostProcessorsRegistrar.class, ServletWebServerFactoryConfiguration.EmbeddedTomcat.class, ServletWebServerFactoryConfiguration.EmbeddedJetty.class, ServletWebServerFactoryConfiguration.EmbeddedUndertow.class})
public class ServletWebServerFactoryAutoConfiguration {
    public ServletWebServerFactoryAutoConfiguration() {}

    ...
}

这个类中配置了 tomcat、jetty、undertow 的工厂 bean,我们以 ServletWebServerFactoryConfiguration.EmbeddedJetty.class 为例

@Configuration(
    proxyBeanMethods = false
)
@ConditionalOnClass({Servlet.class, Server.class, Loader.class, WebAppContext.class})
@ConditionalOnMissingBean(
    value = {ServletWebServerFactory.class},
    search = SearchStrategy.CURRENT
)
static class EmbeddedJetty {
    EmbeddedJetty() {
    }

    @Bean
    JettyServletWebServerFactory jettyServletWebServerFactory(ObjectProvider<JettyServerCustomizer> serverCustomizers) {
        JettyServletWebServerFactory factory = new JettyServletWebServerFactory();
        factory.getServerCustomizers().addAll(serverCustomizers.orderedStream().toList());
        return factory;
    }
}

我们可以看到上面的条件注解 @ConditionalOnClass 表明了,只有当 Servlet 类和 jetty 服务器的一些类存在时,才会将 jetty 服务器工厂注册到容器中

总结:Spring Boot 使用哪个类型的服务器决定于容器中存在与哪个类型的服务器的工厂,而服务器的工厂的创建首先就要判断是否存在改类型的服务器的核心类