13、SpringBoot启动过程

SpringBoot启动过程:加载配置,初始化环境,创建应用上下文,执行自动配置,扫描组件,初始化Bean,启动内嵌服务器,完成应用启动,最后运行CommandLineRunner或ApplicationRunner接口的实现类。


1、启动入口

SpringBoot应用的启动入口通常是main方法,调用SpringApplication.run()

java 复制代码
@SpringBootApplication
public class MyApplication {
    public static void main(String[] args) {
        SpringApplication.run(MyApplication.class, args);
    }
}

2、SpringApplication初始化

SpringApplication的初始化过程:

java 复制代码
public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
    this.resourceLoader = resourceLoader;
    this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
    this.webApplicationType = WebApplicationType.deduceFromClasspath();
    setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
    setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
    this.mainApplicationClass = deduceMainApplicationClass();
}

关键步骤:

  1. 推断应用类型(WEB/NONE)
  2. 加载META-INF/spring.factories中的ApplicationContextInitializerApplicationListener
  3. 推断主配置类

3、run()方法执行流程

run()方法是整个启动过程的核心:

java 复制代码
public ConfigurableApplicationContext run(String... args) {
    // 1. 创建并启动计时器
    StopWatch stopWatch = new StopWatch();
    stopWatch.start();

    // 2. 准备环境
    ConfigurableApplicationContext context = null;
    ConfigurableEnvironment environment = prepareEnvironment(...);

    // 3. 打印Banner
    Banner printedBanner = printBanner(environment);

    // 4. 创建应用上下文
    context = createApplicationContext();

    // 5. 准备上下文
    prepareContext(context, environment, listeners, applicationArguments, printedBanner);

    // 6. 刷新上下文(核心)
    refreshContext(context);

    // 7. 启动后处理
    afterRefresh(context, applicationArguments);

    // 8. 触发启动完成事件
    listeners.started(context);

    // 9. 执行Runner
    callRunners(context, applicationArguments);

    stopWatch.stop();
    return context;
}

4、关键步骤详解

4.1 环境准备

prepareEnvironment()方法:

  • 创建环境对象(StandardServletEnvironment/StandardEnvironment)
  • 配置PropertySources和Profiles
  • 处理命令行参数

4.2 创建应用上下文

createApplicationContext()根据应用类型创建不同的上下文:

  • Web应用:AnnotationConfigServletWebServerApplicationContext
  • 非Web应用:AnnotationConfigApplicationContext

4.3 刷新上下文

refreshContext()最终调用AbstractApplicationContext.refresh(),这是Spring容器的核心初始化过程:

java 复制代码
public void refresh() throws BeansException, IllegalStateException {
    synchronized (this.startupShutdownMonitor) {
        // 1. 准备刷新
        prepareRefresh();

        // 2. 获取BeanFactory
        ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

        // 3. 准备BeanFactory
        prepareBeanFactory(beanFactory);

        try {
            // 4. 后置处理BeanFactory
            postProcessBeanFactory(beanFactory);

            // 5. 调用BeanFactoryPostProcessor
            invokeBeanFactoryPostProcessors(beanFactory);

            // 6. 注册BeanPostProcessor
            registerBeanPostProcessors(beanFactory);

            // 7. 初始化MessageSource
            initMessageSource();

            // 8. 初始化事件广播器
            initApplicationEventMulticaster();

            // 9. 初始化特殊Bean
            onRefresh();

            // 10. 注册监听器
            registerListeners();

            // 11. 初始化非延迟加载的单例Bean
            finishBeanFactoryInitialization(beanFactory);

            // 12. 完成刷新
            finishRefresh();
        }
        // ...
    }
}

4.4 SpringBoot的自动配置

invokeBeanFactoryPostProcessors()会调用ConfigurationClassPostProcessor处理@Configuration类,其中关键的是处理@SpringBootApplication

java 复制代码
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = { /* ... */ })
public @interface SpringBootApplication {
    // ...
}

@EnableAutoConfiguration会导入AutoConfigurationImportSelector,它从META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports加载自动配置类。


5、自定义启动过程

自定义Banner

在resources目录下创建banner.txt:

复制代码
  ___ _ __   ___| |_ ___ _ __ 
 / __| '_ \ / _ \ __/ _ \ '__|
 \__ \ |_) |  __/ ||  __/ |   
 |___/ .__/ \___|\__\___|_|   
     |_|

这个图案大家想放什么都可以。

自定义ApplicationContextInitializer:

java 复制代码
public class MyInitializer implements ApplicationContextInitializer<ConfigurableApplicationContext> {
    @Override
    public void initialize(ConfigurableApplicationContext applicationContext) {
        System.out.println("MyInitializer initialize...");
    }
}

在META-INF/spring.factories中注册:

复制代码
org.springframework.context.ApplicationContextInitializer=com.example.MyInitializer

自定义SpringApplicationRunListener:

java 复制代码
public class MyRunListener implements SpringApplicationRunListener {
    public MyRunListener(SpringApplication application, String[] args) {}

    @Override
    public void starting() {
        System.out.println("MyRunListener starting...");
    }

    // 实现其他方法...
}

在META-INF/spring.factories中注册:

复制代码
org.springframework.boot.SpringApplicationRunListener=com.example.MyRunListener

自定义自动配置

创建自动配置类:

java 复制代码
@Configuration
@ConditionalOnClass(MyService.class)
@EnableConfigurationProperties(MyProperties.class)
public class MyAutoConfiguration {

    @Bean
    @ConditionalOnMissingBean
    public MyService myService(MyProperties properties) {
        return new MyService(properties);
    }
}

在 META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports 中注册:

复制代码
com.example.MyAutoConfiguration

6、启动过程关键扩展点

  1. ApplicationContextInitializer: 在ConfigurableApplicationContext刷新之前执行
  2. ApplicationListener: 监听各种应用事件
  3. BeanFactoryPostProcessor: 修改BeanDefinition
  4. BeanPostProcessor: 修改Bean实例
  5. CommandLineRunner/ApplicationRunner: 应用启动后执行

7、关于调试的技巧

要深入理解启动过程,可以:

  1. 在关键类上设置断点:
    • SpringApplication.run()
    • AbstractApplicationContext.refresh()
    • ConfigurationClassPostProcessor.processConfigBeanDefinitions()
  2. 查看自动配置报告:
    • 启动时添加--debug参数
    • 或设置logging.level.org.springframework.boot.autoconfigure=DEBUG

通过以上分析,你应该对SpringBoot的启动过程有了全面的了解。实际开发中,可以根据需要自定义各个扩展点来满足特定需求。

相关推荐
Rust研习社7 分钟前
组合真的优于继承吗?为什么 Rust 和 Go 都拥抱组合舍弃继承?
后端·rust·编程语言
IT_陈寒31 分钟前
JavaScript的闭包把我坑惨了,说好的内存会自动回收呢?
前端·人工智能·后端
CaffeinePro1 小时前
Pydantic深度使用:数据校验、枚举、ORM映射
后端·fastapi
Chenyiax2 小时前
从 Chat 到 Responses:OpenAI API 抽象为什么变了?
后端
MariaH2 小时前
Koa和Express的区别
后端
MariaH2 小时前
Koa框架的使用
后端
luckdewei3 小时前
那个用 passlib 做认证的新同事,上线第一天就把用户密码写进了日志
后端
ping某4 小时前
为什么 Nginx 明明监听了 80,转发后端时却用了 4xxxx 端口?
后端·nginx
JustHappy4 小时前
我汇总了身边朋友的经历才发现,其实第一份实习是最难找的......
前端·后端·面试
uhakadotcom5 小时前
在python 的 工程化架构中 ,什么是 薄包装器层?
后端·面试·github