SpringBoot启动流程

Spring Boot启动流程的核心代码:

Java 复制代码
public ConfigurableApplicationContext run(String... args) {
    // 记录应用程序启动时间(纳秒)
    long startTime = System.nanoTime();

    // 创建默认的引导上下文(BootstrapContext),包含一些启动所需的基本信息和资源
    DefaultBootstrapContext bootstrapContext = createBootstrapContext();

    // 初始化Spring应用上下文(ApplicationContext)变量,初始值为null
    ConfigurableApplicationContext context = null;

    // 配置无头(Headless)属性,如在没有图形界面的环境中运行
    configureHeadlessProperty();

    // 获取Spring应用启动监听器集合,用于在启动过程中触发相关事件
    SpringApplicationRunListeners listeners = getRunListeners(args);

    // 触发"starting"事件,通知所有监听器Spring应用启动开始
    listeners.starting(bootstrapContext, this.mainApplicationClass);

    try {
        // 创建包含命令行参数的应用程序参数对象
        ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);

        // 准备应用环境(Environment),包括加载配置文件、设置系统属性等
        ConfigurableEnvironment environment = prepareEnvironment(listeners, bootstrapContext, applicationArguments);

        // 打印启动时的Banner信息(如果有)
        Banner printedBanner = printBanner(environment);

        // 创建Spring应用上下文实例,具体实现取决于所选的ApplicationContext类型(如AnnotationConfigApplicationContext、XmlApplicationContext等)
        context = createApplicationContext();

        // 将应用启动追踪器(ApplicationStartup)设置到上下文中,用于记录启动过程中的耗时信息
        context.setApplicationStartup(this.applicationStartup);

        // 预处理上下文,可能包括添加BeanPostProcessor、设置Environment、初始化监听器等
        prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);

        // 刷新上下文,完成Bean的定义、初始化、依赖注入、生命周期回调等操作
        refreshContext(context);

        // 应用上下文刷新后执行的操作,如触发CommandLineRunner、ApplicationRunner接口实现类
        afterRefresh(context, applicationArguments);

        // 计算启动耗时(纳秒)
        Duration timeTakenToStartup = Duration.ofNanos(System.nanoTime() - startTime);

        // 如果配置了打印启动信息,则输出启动日志和启动耗时
        if (this.logStartupInfo) {
            new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), timeTakenToStartup);
        }

        // 触发"started"事件,通知所有监听器Spring应用启动完成,传递上下文和启动耗时
        listeners.started(context, timeTakenToStartup);

        // 执行所有CommandLineRunner和ApplicationRunner接口实现类的run方法
        callRunners(context, applicationArguments);
    }
    catch (AbandonedRunException ex) {
        // 直接抛出AbandonedRunException异常,不进行处理
        throw ex;
    }
    catch (Throwable ex) {
        // 处理启动失败,如清理资源、记录错误日志等
        handleRunFailure(context, ex, listeners);

        // 包装异常为IllegalStateException,向上抛出
        throw new IllegalStateException(ex);
    }

    try {
        // 如果应用上下文仍在运行,则触发"ready"事件,通知所有监听器Spring应用已准备就绪
        if (context.isRunning()) {
            Duration timeTakenToReady = Duration.ofNanos(System.nanoTime() - startTime);
            listeners.ready(context, timeTakenToReady);
        }
    }
    catch (AbandonedRunException ex) {
        // 直接抛出AbandonedRunException异常,不进行处理
        throw ex;
    }
    catch (Throwable ex) {
        // 处理启动失败,如清理资源、记录错误日志等
        handleRunFailure(context, ex, null);

        // 包装异常为IllegalStateException,向上抛出
        throw new IllegalStateException(ex);
    }

    // 返回已创建并刷新完成的Spring应用上下文
    return context;
}

Spring Boot的启动流程解析:

1. 启动入口 : Spring Boot应用的启动始于main()方法,其中通常包含如下代码:

Java 复制代码
public static void main(String[] args) {
    SpringApplication.run(Application.class, args);
}

这里,SpringApplication.run()方法是整个启动流程的起点。

2. 初始化SpringApplication : 在run()方法内部,首先会创建一个SpringApplication对象。这个过程中会完成一些基础配置,如设置默认的Banner、初始化异常报告器等。同时,会根据当前环境和类路径检测确定是否启用Web特性以及具体使用哪种Web容器(如Tomcat、Jetty等)。

Java 复制代码
public static ConfigurableApplicationContext run(Class<?> primarySource, String... args) {
    return new SpringApplication(primarySource).run(args);
}

// 在SpringApplication构造函数中:
this.bannerMode = Banner.Mode.CONSOLE;
this.mainApplicationClass = deduceMainApplicationClass();
this.resourceLoader = new DefaultResourceLoader(getClassLoader());
this.initializers.add(new LoggingApplicationListener());
// ... 其他初始化操作

3. 加载外部配置 : 接下来,Spring Boot会加载应用的外部配置。这包括从命令行参数、系统属性、环境变量、application.propertiesapplication.yml文件,以及特定于环境的 profiles 文件中获取配置信息。这些配置会被合并到一个ConfigurableEnvironment对象中,它是Spring Environment接口的实现,用于后续提供配置属性给应用上下文。

Java 复制代码
// 在prepareEnvironment()方法中:
ConfigurableEnvironment environment = getOrCreateEnvironment();
configurePropertySources(environment, args);
configureProfiles(environment, args);
// ... 其他配置操作

private ConfigurableEnvironment getOrCreateEnvironment() {
   if (this.environment != null) {
      return this.environment;
   }
   ConfigurableEnvironment environment = this.applicationContextFactory.createEnvironment(this.webApplicationType);
   if (environment == null && this.applicationContextFactory != ApplicationContextFactory.DEFAULT) {
      environment = ApplicationContextFactory.DEFAULT.createEnvironment(this.webApplicationType);
   }
   return (environment != null) ? environment : new ApplicationEnvironment();
}

4. 创建SpringApplicationRunListeners : Spring Boot会创建一组SpringApplicationRunListener对象,它们负责监听并响应启动过程中的各个事件,如开始启动、环境准备完毕、上下文创建、刷新、失败等。这些监听器可以是内置的,也可以由用户自定义并通过SPI机制注册。

Java 复制代码
private SpringApplicationRunListeners getRunListeners(SpringApplicationArguments args) {
   Class<?>[] listeners = getSpringFactoriesInstances(SpringApplicationRunListener.class,
            new Class<?>[] { SpringApplication.class, this.mainApplicationClass }, args);
    return new SpringApplicationRunListeners(logger, listeners);
}

// 使用SpringFactoriesLoader加载监听器:
static <T> T[] getSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, Object... args) {
    // 从META-INF/spring.factories加载对应类型的实现类列表
}

5. 准备环境 : 在此阶段,Spring Boot会触发SpringApplicationRunListenersstarting()事件,并打印启动Banner(如果未禁用)。然后,它会准备应用环境,包括设置JVM的java.awt.headless属性(如果适用),以及可能的其他环境特定配置。

Java 复制代码
protected void prepareEnvironment(ConfigurableEnvironment environment, SpringApplicationArguments args) {
    configureIgnoreBeanInfo(environment);
    configurePropertySources(environment, args);
    configureConversionService(environment);
    configureResourceLoader(environment);
    configureValidatorFactory(environment);
    configureMessageSource(environment);
    configureApplicationEventMulticaster(environment);
    configureDefaultCharset(environment);
    if (!this.isWebApplication()) {
        configureWebEnvironment(environment, null);
    }
    // 触发SpringApplicationRunListeners的starting()事件
    this.listeners.starting(environment);
}

6. 创建ApplicationContext : 基于应用类型(如常规Spring应用、Web应用、Reactive Web应用等)、用户配置和自动检测结果,Spring Boot会选择合适的ApplicationContext子类(如AnnotationConfigApplicationContextReactiveWebApplicationContext)来创建应用上下文,即IoC容器。

Java 复制代码
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);
            }
        }
        // ...
    }
    return (ConfigurableApplicationContext) BeanUtils.instantiateClass(contextClass);
}

7. 扫描和加载Bean定义 : Spring Boot会扫描指定的包(默认为主程序类所在的包及其子包)以查找@Component、@Configuration等注解标记的类,并将它们作为Bean定义加载到ApplicationContext中。同时,会处理@EnableAutoConfiguration注解,进行自动配置,根据已有的类路径依赖和环境配置动态注册所需的Bean。

  • 使用ClassPathBeanDefinitionScanner扫描指定包下的类。
  • @ComponentScan@SpringBootApplication等注解驱动的组件扫描。
  • 处理@Configuration类,将其转化为BeanDefinition并加载到BeanFactory中。
  • 处理@EnableAutoConfiguration注解,通过SpringFactoriesLoader加载自动配置类并注册Bean定义。

8. 刷新ApplicationContext : 最关键的一步是调用ApplicationContextrefresh()方法。这个方法完成了IoC容器的完整初始化,包括:

  • Bean定义的注册:将所有扫描到的Bean定义注册到BeanFactory中。
  • Bean依赖解析与BeanPostProcessor注册:解析Bean之间的依赖关系,注册BeanPostProcessor和其他Bean生命周期回调接口的实现。
  • Bean的实例化与初始化 :按照依赖关系顺序实例化单例Bean,并调用其初始化方法(如@PostConstruct注解的方法)。
  • 生命周期回调 :触发ApplicationContextAwareBeanFactoryAware等接口方法的调用,以及SmartInitializingSingleton等生命周期回调。
Java 复制代码
private void refreshContext(ConfigurableApplicationContext context) {
   if (this.registerShutdownHook) {
      shutdownHook.registerApplicationContext(context);
   }
   refresh(context);
}

@Override
public void refresh() throws BeansException, IllegalStateException {
   synchronized (this.startupShutdownMonitor) {
      StartupStep contextRefresh = this.applicationStartup.start("spring.context.refresh");

      // Prepare this context for refreshing.
      prepareRefresh();

      // Tell the subclass to refresh the internal bean factory.
      ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

      // Prepare the bean factory for use in this context.
      prepareBeanFactory(beanFactory);

      try {
         // Allows post-processing of the bean factory in context subclasses.
         postProcessBeanFactory(beanFactory);

         StartupStep beanPostProcess = this.applicationStartup.start("spring.context.beans.post-process");
         // Invoke factory processors registered as beans in the context.
         invokeBeanFactoryPostProcessors(beanFactory);

         // Register bean processors that intercept bean creation.
         registerBeanPostProcessors(beanFactory);
         beanPostProcess.end();

         // Initialize message source for this context.
         initMessageSource();

         // Initialize event multicaster for this context.
         initApplicationEventMulticaster();

         // Initialize other special beans in specific context subclasses.
         onRefresh();

         // Check for listener beans and register them.
         registerListeners();

         // Instantiate all remaining (non-lazy-init) singletons.
         finishBeanFactoryInitialization(beanFactory);

         // Last step: publish corresponding event.
         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();
         contextRefresh.end();
      }
   }
}

9. 启动完成后的工作 : 当ApplicationContext刷新完毕,Spring Boot会触发SpringApplicationRunListenersstarted()事件,表示应用已成功启动。随后,如果有配置CommandLineRunnerApplicationRunner接口的实现类,它们会被依次调用来执行自定义的启动后任务。

Java 复制代码
private void callRunners(ApplicationContext context, ApplicationArguments args) {
    List<Object> runners = new ArrayList<>();
    runners.addAll(context.getBeansOfType(CommandLineRunner.class).values());
    runners.addAll(context.getBeansOfType(ApplicationRunner.class).values());
    // 按照Bean定义顺序执行runners
    AnnotationAwareOrderComparator.sort(runners);
    for (Object runner : new LinkedHashSet<>(runners)) {
        invokeRunners(runner, args);
    }
}

10. 就绪通知(可选) : 如果Spring Boot应用支持"应用就绪"(Application Ready)信号,那么在所有必要的初始化工作完成且应用已经可以接受请求之后,会触发SpringApplicationRunListenersready()事件。这对于监控系统或服务发现组件来说非常重要,它们可以据此判断应用是否已经完全启动并准备好对外提供服务。

Java 复制代码
private void triggerAfterStart(ApplicationArguments args) {
2    // 触发SpringApplicationRunListeners的started()事件
3    this.listeners.started(context);
4    // 如果有ApplicationReadyEvent发布者,则发布ApplicationReadyEvent
5    publishEvent(new ApplicationReadyEvent(this, args));
6}

综上所述,Spring Boot启动流程涵盖了从入口点到完全运行状态的全过程,包括初始化环境、加载配置、创建和刷新ApplicationContext、执行启动后任务以及发出就绪通知等关键步骤,确保了应用的平稳启动和高效运行。

相关推荐
用户908324602731 天前
Spring AI 1.1.2 + Neo4j:用知识图谱增强 RAG 检索(上篇:图谱构建)
java·spring boot
用户8307196840822 天前
Spring Boot 集成 RabbitMQ :8 个最佳实践,杜绝消息丢失与队列阻塞
spring boot·后端·rabbitmq
Java水解2 天前
Spring Boot 视图层与模板引擎
spring boot·后端
Java水解2 天前
一文搞懂 Spring Boot 默认数据库连接池 HikariCP
spring boot·后端
洋洋技术笔记2 天前
Spring Boot Web MVC配置详解
spring boot·后端
初次攀爬者3 天前
Kafka 基础介绍
spring boot·kafka·消息队列
用户8307196840823 天前
spring ai alibaba + nacos +mcp 实现mcp服务负载均衡调用实战
spring boot·spring·mcp
Java水解3 天前
SpringBoot3全栈开发实战:从入门到精通的完整指南
spring boot·后端