SpringBoot是如何启动的

基于SpringBoot2.6.2版本

我们的启动类一般都是这样的:

从上面代码看,调用了SpringApplication的静态方法run。这个run方法会构造一个SpringApplication的实例,然后再调用这里实例的run方法就表示启动SpringBoot。

构造SpringApplication的实例

SpringApplication#run(java.lang.Class<?>, java.lang.String...)

SpringApplication(org.springframework.core.io.ResourceLoader, java.lang.Class<?>...)

public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
    this.resourceLoader = resourceLoader;
    Assert.notNull(primarySources, "PrimarySources must not be null");
    this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
    this.webApplicationType = WebApplicationType.deduceFromClasspath();
    //加载META-INF/spring.factories路径ApplicationContextInitializer.class
    this.bootstrapRegistryInitializers = new ArrayList<>(
        getSpringFactoriesInstances(BootstrapRegistryInitializer.class));
    setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
    //加载META-INF/spring.factories路径ApplicationListener.class
    setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
    this.mainApplicationClass = deduceMainApplicationClass();
}

分析:

  • 通过ClassLoader.getResources加载META-INF/spring.factories路径下的 文件信息,从中找key为ApplicationContextInitializer.class,并实例化。
  • 通过ClassLoader.getResources加载META-INF/spring.factories路径下的 文件信息ApplicationListener.class对应类,并实例化。

SpringApplication#run(java.lang.String...)

SpringApplication实例构造好后,就会调用SpringApplication的run方法。

SpringApplication#run(java.lang.String...)

  • 找到所有SpringApplicationRunListener的实现类并将其实例化
  • 发布应用开始启动事件ApplicationStartedEvent
  • 初始化应用参数
  • 创建并配置当前SpringBoot应用将要使用的Environment
  • 打印banner
  • 根据WebApplicationType创建应用上下文ConfigurableApplicationContext
  • 预处理上下文:加载environment,执行ApplicationContextInitializer的initialize()方法,SpringApplicationRunListener的contextLoaded()方法
  • 刷新应用上下文:跟Spring的流程大体一致,这里就包括通过@EnableAutoConfiguration导入的各种自动配置类
  • 再一次刷新上下文,其实是空方法,可能是为了后续扩展。
  • 发布应用已经启动的事件
  • 遍历所有注册的ApplicationRunner和CommandLineRunner,并执行其run()方法
public ConfigurableApplicationContext run(String... args) {
    long startTime = System.nanoTime();
    DefaultBootstrapContext bootstrapContext = createBootstrapContext();
    ConfigurableApplicationContext context = null;
    /*
     * 配置属性:
     * 设置系统属性 java.awt.headless,为 true 则启用headless模式
     * headless模式是应用的一种配置模式,在服务器缺少显示设备、键盘、鼠标等外设的情况下可以使用该模式
     * 比如我们使用的Linux服务器就是缺少前述的这些设备,但是又需要使用这些设备提供的能力
     */
    configureHeadlessProperty();
    /*
     * 1、获取监听器
     * 通过SpringFactoriesLoader检索META-INF/spring.factories,
     * 找到声明的所有SpringApplicationRunListener的实现类并将其实例化。
     * 实际上只有EventPublishingRunListener一个实现类,以下实际调用的都是调用EventPublishingRunListener对应的方法。
     */
    SpringApplicationRunListeners listeners = getRunListeners(args);
    // 2、发布应用开始启动事件ApplicationStartedEvent
    listeners.starting(bootstrapContext, this.mainApplicationClass);
    try {
        /* 初始化参数 */
        ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
        /*
         * 3、配置环境
         * 创建并配置当前SpringBoot应用将要使用的Environment(包括配置要使用的PropertySource以及Profile),
         * 并遍历调用所有的SpringApplicationRunListener的environmentPrepared()方法,广播Environment准备完毕。
         */
        ConfigurableEnvironment environment = prepareEnvironment(listeners, bootstrapContext, applicationArguments);
        //4、配置需要忽略哪些Bean
        configureIgnoreBeanInfo(environment);
        //5、打印banner,如果在resources目录下创建了我们自己的banner就会进行打印,否则默认使用spring的
        Banner printedBanner = printBanner(environment);
        //6、根据WebApplicationType创建应用上下文ConfigurableApplicationContext
        context = createApplicationContext();
        context.setApplicationStartup(this.applicationStartup);
        /*
         * 7、预处理上下文
         * 为ApplicationContext加载environment,之后逐个执行ApplicationContextInitializer的initialize()方法来进一步封装ApplicationContext,
         * 并调用所有的SpringApplicationRunListener的contextPrepared()方法,【EventPublishingRunListener只提供了一个空的contextPrepared()方法】,
         * 之后初始化IoC容器,并调用SpringApplicationRunListener的contextLoaded()方法,广播ApplicationContext的IoC加载完成。
         */
        prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);
        //8、重要!!! 刷新应用上下文,跟Spring的流程大体一致,这里就包括通过@EnableAutoConfiguration导入的各种自动配置类。
        refreshContext(context);
        //再一次刷新上下文,其实是空方法,可能是为了后续扩展。
        afterRefresh(context, applicationArguments);
        Duration timeTakenToStartup = Duration.ofNanos(System.nanoTime() - startTime);
        if (this.logStartupInfo) {
            new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), timeTakenToStartup);
        }
        //9、发布应用已经启动的事件
        listeners.started(context, timeTakenToStartup);
        /* 10、遍历所有注册的ApplicationRunner和CommandLineRunner,并执行其run()方法。
         * 我们可以实现自己的ApplicationRunner或者CommandLineRunner,来对SpringBoot的启动过程进行扩展。
         */
        callRunners(context, applicationArguments);
    } catch (Throwable ex) {
        handleRunFailure(context, ex, listeners);
        throw new IllegalStateException(ex);
    }
    try {
        Duration timeTakenToReady = Duration.ofNanos(System.nanoTime() - startTime);
        //11、发布应用已经启动完成的监听事件
        listeners.ready(context, timeTakenToReady);
    } catch (Throwable ex) {
        handleRunFailure(context, ex, null);
        throw new IllegalStateException(ex);
    }
    return context;
}

下面我们就对其中最主要的一些步骤进行讲解。

第1步 SpringApplicationRunListeners listeners = getRunListeners(args);

获取监听器。

通过SpringFactoriesLoader检索META-INF/spring.factories,找到声明的所有SpringApplicationRunListener的实现类并将其实例化。实际上只有EventPublishingRunListener一个实现类,以下实际调用的都是调用EventPublishingRunListener对应的方法。

第2步 listeners.starting(bootstrapContext, this.mainApplicationClass);

org.springframework.boot.context.event.EventPublishingRunListener#starting

发布应用开始启动事件ApplicationStartedEvent。

第3步 ConfigurableEnvironment environment = prepareEnvironment(listeners, bootstrapContext, applicationArguments);

创建并配置当前SpringBoot应用将要使用的Environment(包括配置要使用的PropertySource以及Profile),并遍历调用所有的SpringApplicationRunListener的environmentPrepared()方法,广播Environment准备完毕。

private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners,
                                                   DefaultBootstrapContext bootstrapContext, ApplicationArguments applicationArguments) {
    // Create and configure the environment
    // 得到环境对象ConfigurableEnvironment
    ConfigurableEnvironment environment = getOrCreateEnvironment();
    // 并配置环境信息;对listeners初始化环境属性
    configureEnvironment(environment, applicationArguments.getSourceArgs());
    ConfigurationPropertySources.attach(environment);
    // 发布ApplicationEnvironmentPreparedEvent事件
    listeners.environmentPrepared(bootstrapContext, environment);
    DefaultPropertiesPropertySource.moveToEnd(environment);
    Assert.state(!environment.containsProperty("spring.main.environment-prefix"),
                 "Environment prefix cannot be set via properties.");
    bindToSpringApplication(environment);
    if (!this.isCustomEnvironment) {
        environment = convertEnvironment(environment);
    }
    ConfigurationPropertySources.attach(environment);
    return environment;
}

第4步 configureIgnoreBeanInfo(environment)

配置哪些Bean是需要被忽略的。

第5步 Banner printedBanner = printBanner(environment);

打印banner,如果在resources目录下创建了我们自己的banner就会进行打印,否则默认使用spring的。

#有console、log、off三个配置项,默认是有console
spring.main.banner-mode=off

第6步 context = createApplicationContext();

根据WebApplicationType创建应用上下文AnnotationConfigServletWebServerApplicationContext,它是ServletWebServerApplicationContext的子类。

这里使用的是 函数式接口@FunctionalInterface

this.webApplicationType在构建SpringApplication实例的时候由WebApplicationType.deduceFromClasspath();进行赋值了。

第7步 prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);

预处理上下文。

为ApplicationContext加载environment,之后逐个执行ApplicationContextInitializer的initialize()方法来进一步封装ApplicationContext,并调用所有的SpringApplicationRunListener的contextPrepared()方法,【EventPublishingRunListener只提供了一个空的contextPrepared()方法】,之后初始化IoC容器,并调用SpringApplicationRunListener的contextLoaded()方法,广播ApplicationContext的IoC加载完成。

private void prepareContext(DefaultBootstrapContext bootstrapContext, ConfigurableApplicationContext context,
                            ConfigurableEnvironment environment, SpringApplicationRunListeners listeners,
                            ApplicationArguments applicationArguments, Banner printedBanner) {
    // 对ApplicationContext设置环境变量;
    context.setEnvironment(environment);
    // 配置属性ResourceLoader和ClassLoader属性;
    postProcessApplicationContext(context);
    // 循环ApplicationContextInitializer,执行initialize方法
    applyInitializers(context);
    // 发布上下文预准备事件
    listeners.contextPrepared(context);
    bootstrapContext.close(context);
    if (this.logStartupInfo) {
        logStartupInfo(context.getParent() == null);
        logStartupProfileInfo(context);
    }
    // Add boot specific singleton beans
    ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
    beanFactory.registerSingleton("springApplicationArguments", applicationArguments);
    if (printedBanner != null) {
        beanFactory.registerSingleton("springBootBanner", printedBanner);
    }
    if (beanFactory instanceof AbstractAutowireCapableBeanFactory) {
        ((AbstractAutowireCapableBeanFactory) beanFactory).setAllowCircularReferences(this.allowCircularReferences);
        if (beanFactory instanceof DefaultListableBeanFactory) {
            ((DefaultListableBeanFactory) beanFactory)
            .setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding);
        }
    }
    if (this.lazyInitialization) {
        context.addBeanFactoryPostProcessor(new LazyInitializationBeanFactoryPostProcessor());
    }
    // Load the sources
    Set<Object> sources = getAllSources();
    Assert.notEmpty(sources, "Sources must not be empty");
    //创建BeanDefinitionLoader,调用其load()方法
    load(context, sources.toArray(new Object[0]));
    //发布上下文loaded事件
    listeners.contextLoaded(context);
}

第8步 refreshContext(context)

刷新应用上下文,跟Spring的流程大体一致,这里就包括通过@EnableAutoConfiguration导入的各种自动配置类。

org.springframework.boot.SpringApplication#refresh

这个applicationContext是refreshContext(context)传进来的,在第6步中,我们可以看到创建的是AnnotationConfigServletWebServerApplicationContext实例,它的父类是ServletWebServerApplicationContext。

所以,refresh()方法调用的是org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext#refresh,如下:

org.springframework.context.support.AbstractApplicationContext#refresh

终于看到我们熟悉的方法,上下文刷新,跟Spring的流程基本一致。

第①个红圈会去查找SpringBoot的自动配置化类,并组装成BeanDefinition,注册加入到ioc容器中。

如何组装自动化配置类,请参考:SpringBoot是如何将自动化配置类加入到ioc容器中

第②个红圈会调用org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext#onRefresh,其中 createWebServer() 方法是用来启动web服务的,但是还没有真正启动 Tomcat,只是通过ServletWebServerFactory 创建了一个 WebServer。

org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext#getSelfInitializer

prepareWebApplicationContext方法会初始化Root WebApplicationContext(这里有个疑问:Spring中是有父子容器的,那DispatcherServlet的mvc环境是如何创建呢?)。

获取到一个org.springframework.boot.web.servlet.ServletContextInitializer后,因为内嵌webserver默认是Tomcat,是进入org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory#getWebServer方法。

@Override
public WebServer getWebServer(ServletContextInitializer... initializers) {
    if (this.disableMBeanRegistry) {
        Registry.disableRegistry();
    }
    /** 1、创建Tomcat实例 **/
    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);
    /** 2、给创建好的tomcat设置连接器connector **/
    tomcat.setConnector(connector);
    /** 设置不自动部署 **/
    tomcat.getHost().setAutoDeploy(false);
    /** 3、配置Tomcat容器引擎 **/
    configureEngine(tomcat.getEngine());
    for (Connector additionalConnector : this.additionalTomcatConnectors) {
        tomcat.getService().addConnector(additionalConnector);
    }
    /**
     * 准备Tomcat的StandardContext,并添加到Tomcat中,同时把initializers 注册到类型为
     * TomcatStarter的ServletContainerInitializer中
     */
    prepareContext(tomcat.getHost(), initializers);
    /** 将创建好的Tomcat包装成WebServer返回**/
    return getTomcatWebServer(tomcat);
}

getWebServer() 这个方法创建了 Tomcat 对象,并且做了两件重要的事情:

  • 把连接器 Connector 对象添加到 Tomcat 中;
  • 配置容器引擎,configureEngine(tomcat.getEngine());

首先说一下这个 Connector 连接器,Tomcat 有两个核心功能:

  • 处理 Socket 连接,负责网络字节流与 Request 和 Response 对象的转化。
  • 加载和管理 Servlet,以及具体处理 Request 请求。

针对这两个功能,Tomcat 设计了两个核心组件来分别完成这两件事,即:连接器(Connector)和容器(Container)。

整个过程大致就是:Connector 连接器接收连接请求,创建Request和Response对象用于和请求端交换数据,然后分配线程让Engine(也就是Servlet容器)来处理这个请求,并把产生的Request和Response对象传给Engine。当Engine处理完请求后,也会通过Connector将响应返回给客户端。

这里面提到了 Engine,这个是 Tomcat 容器里的顶级容器(Container),我们可以通过 Container 类查看其他的子容器:Engine、Host、Context、Wrapper。

4者的关系是:Engine 是最高级别的容器,Engine 子容器是 Host,Host 的子容器是 Context,Context 子容器是 Wrapper,所以这4个容器的关系就是父子关系,即:Wrapper > Context > Host > Engine (>表示继承)。

至此我们了解了 Engine 这个就是个容器,然后我们再看一下这个 configureEngine(tomcat.getEngine()) 具体干了啥:

private void configureEngine(Engine engine) {
    engine.setBackgroundProcessorDelay(this.backgroundProcessorDelay);
    Iterator var2 = this.engineValves.iterator();
    while(var2.hasNext()) {
        Valve valve = (Valve)var2.next();
        engine.getPipeline().addValve(valve);
    }
}

其中 engine.setBackgroundProcessorDelay(this.backgroundProcessorDelay) 是指定背景线程的执行间隔,例如背景线程会在每隔多长时间后判断session是否失效之类。

再回到 getWebServer() 方法,最终 getWebServer() 方法返回了 TomcatWebServer。

return this.getTomcatWebServer(tomcat);

通过 getTomcatWebServer() 方法,继续下沉:

/**
 * 构造函数实例化 TomcatWebServer
 **/
public TomcatWebServer(Tomcat tomcat, boolean autoStart, Shutdown shutdown) {
    Assert.notNull(tomcat, "Tomcat Server must not be null");
    this.tomcat = tomcat;
    this.autoStart = autoStart;
    this.gracefulShutdown = (shutdown == Shutdown.GRACEFUL) ? new GracefulShutdown(tomcat) : null;
    initialize();
}

private void initialize() throws WebServerException {
    /** 我们在启动 Spring Boot 时经常看到打印这句话 **/
    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();
                }
            });
            /** 启动 tomcat **/
            // 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 启动。

第9步 listeners.started(context, timeTakenToStartup);

发布应用已经启动的事件。

第10步 callRunners(context, applicationArguments);

遍历所有注册的ApplicationRunner和CommandLineRunner,并执行其run()方法。 我们可以实现自己的ApplicationRunner或者CommandLineRunner,来对SpringBoot的启动过程进行扩展。

第11步 listeners.ready(context, timeTakenToReady);

发布应用已经启动完成的监听事件

 

posted @ 2022-01-19 20:48  残城碎梦  阅读(356)  评论(0编辑  收藏  举报