SpringBoot 启动入口深度解析:main方法执行全流程

发布于:2025-07-01 ⋅ 阅读:(20) ⋅ 点赞:(0)

一、main方法的启动本质

Spring Boot应用的启动入口是标准的Java main方法,但它的特殊之处在于:

@SpringBootApplication
public class App {
    public static void main(String[] args) {
        SpringApplication.run(App.class, args);
    }
}

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
		@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {//.....}

关键点

  1. JVM加载主类时不会处理类上的注解(如@SpringBootApplication
  2. 所有注解处理都由Spring容器在初始化阶段完成
  3. main方法本质是Spring容器的启动触发器

二、main方法执行瞬间的关键动作

当JVM执行main方法时,在SpringApplication.run()调用瞬间发生以下关键操作:

1. SpringApplication实例化

// SpringApplication.run()内部实现
public static ConfigurableApplicationContext run(Class<?> primarySource, String... args) {
    return run(new Class[]{primarySource}, args);
}

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

public ConfigurableApplicationContext run(String... args) {
    // 记录应用启动时间戳和初始化指标收集器
    Startup startup = Startup.create();

    // 如果配置了关闭钩子(默认true),则注册JVM关闭时清理资源的钩子
    if (this.properties.isRegisterShutdownHook()) {
        SpringApplication.shutdownHook.enableShutdownHookAddition();
    }

    // 创建引导上下文(BootstrapContext),用于早期初始化组件
    DefaultBootstrapContext bootstrapContext = createBootstrapContext();

    // 应用上下文
    ConfigurableApplicationContext context = null;

    // 强制设置awt.headless模式(确保无图形界面的服务器环境正常工作)
    configureHeadlessProperty();

    // 获取并初始化SpringApplicationRunListener集合(从spring.factories加载)
    SpringApplicationRunListeners listeners = getRunListeners(args);

    // 发布ApplicationStartingEvent事件(最早的生命周期事件)
    listeners.starting(bootstrapContext, this.mainApplicationClass);

    try {
        // 解析命令行参数(封装为ApplicationArguments对象)
        ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);

        // 准备环境配置(加载properties/yml,合并命令行参数,发布ApplicationEnvironmentPreparedEvent事件)
        ConfigurableEnvironment environment = prepareEnvironment(listeners, bootstrapContext, applicationArguments);

        // 打印Banner(控制台图标,可通过spring.banner.*配置)
        Banner printedBanner = printBanner(environment);

        // 根据应用类型创建对应的ApplicationContext(Servlet/Reactive/普通)
        context = createApplicationContext();
        
        // 设置应用启动指标收集器
        context.setApplicationStartup(this.applicationStartup);

        // 准备应用上下文(配置Bean工厂,注册单例,发布ApplicationContextInitializedEvent事件)
        prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);

        // ★核心:刷新上下文(加载配置类,初始化所有Bean,启动嵌入式服务器)
        refreshContext(context);

        // 上下文刷新后的扩展点(默认空实现,可被子类覆盖)
        afterRefresh(context, applicationArguments);

        // 记录启动完成时间点
        startup.started();

        // 如果配置了启动日志(默认true),打印应用启动信息(版本、端口等)
        if (this.properties.isLogStartupInfo()) {
            new StartupInfoLogger(this.mainApplicationClass, environment)
                .logStarted(getApplicationLog(), startup);
        }

        // 发布ApplicationStartedEvent事件(上下文已刷新但未调用Runner)
        listeners.started(context, startup.timeTakenToStarted());

        // 执行ApplicationRunner和CommandLineRunner实现类
        callRunners(context, applicationArguments);
    }
    catch (Throwable ex) {
        // 处理启动失败:发布ApplicationFailedEvent事件并抛出异常
        throw handleRunFailure(context, ex, listeners);
    }

    try {
        // 检查上下文是否在运行状态(正常流程应处于运行中)
        if (context.isRunning()) {
            // 发布ApplicationReadyEvent事件(应用完全就绪,可接收请求)
            listeners.ready(context, startup.ready());
        }
    }
    catch (Throwable ex) {
        // 处理就绪事件阶段的异常
        throw handleRunFailure(context, ex, null);
    }

    // 返回已初始化的应用上下文
    return context;
}

执行步骤

  1. 主类App.class作为primarySources保存
  2. 推断应用类型(Servlet/Reactive/None)
  3. 加载META-INF/spring.factories中的扩展点:
    • BootstrapRegistryInitializer
    • ApplicationContextInitializer
    • ApplicationListener
  4. 记录主应用类(含@SpringBootApplication的类)

2. 主类注解的识别时机

核心结论:主类上的@SpringBootApplication注解在 容器刷新阶段(refresh() 才被真正解析,由ConfigurationClassPostProcessor触发:

  1. @ComponentScan → 扫描当前包下的@Component@Service等组件。
  2. @EnableAutoConfiguration → 通过AutoConfigurationImportSelector加载AutoConfiguration.imports中的配置类。

在这里插入图片描述

三、@SpringBootApplication注解处理流程

主类注解的真正处理发生在容器刷新阶段,由ConfigurationClassPostProcessor完成:

1、配置类识别

  • invokeBeanFactoryPostProcessors()阶段,扫描所有@Configuration类(主类因@SpringBootApplication@SpringBootConfiguration@Configuration被识别)

2、注解元数据解析

解析@SpringBootApplication组合注解中的@ComponentScan@EnableAutoConfiguration

//org.springframework.context.annotation.ConfigurationClassParser#doProcessConfigurationClass
protected final SourceClass doProcessConfigurationClass(
			ConfigurationClass configClass, SourceClass sourceClass, Predicate<String> filter)
			throws IOException {
    //*****略*****
    // 处理@ComponentScan
    Set<AnnotationAttributes> componentScans = AnnotationConfigUtils.attributesForRepeatable(
				sourceClass.getMetadata(), ComponentScan.class, ComponentScans.class,
				MergedAnnotation::isDirectlyPresent);
    //*****略*****
    for (AnnotationAttributes componentScan : componentScans) {
        // The config class is annotated with @ComponentScan -> perform the scan immediately
        Set<BeanDefinitionHolder> scannedBeanDefinitions =
                this.componentScanParser.parse(componentScan, sourceClass.getMetadata().getClassName());
        // Check the set of scanned definitions for any further config classes and parse recursively if needed
        for (BeanDefinitionHolder holder : scannedBeanDefinitions) {
            BeanDefinition bdCand = holder.getBeanDefinition().getOriginatingBeanDefinition();
            if (bdCand == null) {
                bdCand = holder.getBeanDefinition();
            }
            if (ConfigurationClassUtils.checkConfigurationClassCandidate(bdCand, this.metadataReaderFactory)) {
                parse(bdCand.getBeanClassName(), holder.getBeanName());
            }
        }
    }
    // Process any @Import annotations
    // 处理@Import(包含@EnableAutoConfiguration)
	processImports(configClass, sourceClass, getImports(sourceClass), filter, true);
    //*****略*****
}

3、三大核心注解处理

注解组件 处理方式 作用
@SpringBootConfiguration 作为@Configuration处理 标记主类为配置类
@ComponentScan 触发包扫描 扫描当前包及子包的@Component@Service等组件
@EnableAutoConfiguration 通过AutoConfigurationImportSelector 加载META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports

4、自动配置加载

// AutoConfigurationImportSelector逻辑
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
   // 从spring.factories加载自动配置类
   List<String> configurations = ImportCandidates.load(AutoConfiguration.class, getBeanClassLoader())
         .getCandidates();
   return configurations;
}

四、main方法启动时的完整执行序列

阶段 关键操作 是否处理主类注解
JVM加载主类 1. 加载静态字段 2. 执行静态初始化块
main方法执行 1. 创建SpringApplication实例 2. 存储主类引用
SpringApplication.run() 1. 准备环境 2. 创建ApplicationContext 3. 注册主类Bean定义
refreshContext() 1. invokeBeanFactoryPostProcessors() 2. 触发ConfigurationClassPostProcessor
配置类处理 1. 解析@SpringBootApplication 2. 执行包扫描 3. 加载自动配置类

五、核心处理组件协作图

在这里插入图片描述

六、关键设计解析

  1. 延迟注解处理
    • Spring Boot 3.5.0使用延迟注解解析策略
    • 主类在refresh()阶段才被真正处理
    • 优点:允许环境准备完成后进行条件化配置
  2. 条件注解处理
// 自动配置类的条件检查
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)
@AutoConfiguration(after = ServletWebServerFactoryAutoConfiguration.class)
@ConditionalOnWebApplication(type = Type.SERVLET)
@ConditionalOnClass(DispatcherServlet.class)
public class DispatcherServletAutoConfiguration {
    // 仅当类路径存在Servlet和DispatcherServlet时生效
}
  1. 配置类解析顺序
1. 主配置类(@SpringBootApplication)
2. 自动配置类(AutoConfiguration.imports)
3. @ComponentScan扫描到的配置类
4. @Import引入的配置类

七、总结

核心结论:main 方法只是 Spring Boot 启动的"点火器",真正的注解解析发生在容器刷新阶段,由 Spring 的 ConfigurationClassPostProcessor引擎完成,与 Java 原生的注解处理机制完全分离。