当前位置: 首页 > news >正文

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

一、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 {//*****略*****// 处理@ComponentScanSet<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 immediatelySet<BeanDefinitionHolder> scannedBeanDefinitions =this.componentScanParser.parse(componentScan, sourceClass.getMetadata().getClassName());// Check the set of scanned definitions for any further config classes and parse recursively if neededfor (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 原生的注解处理机制完全分离。

http://www.dtcms.com/a/263551.html

相关文章:

  • Android Telephony 网络状态中的 NAS 信息
  • 反射,枚举和lambda表达式
  • 《垒球百科》老年俱乐部有哪些项目·垒球1号位
  • 从零到一通过Web技术开发一个五子棋
  • 【MySQL基础】MySQL索引全面解析:从原理到实践
  • 人形机器人_双足行走动力学:MIT机器人跌落自恢复算法及应用
  • 使用Verilog设计模块输出中位数,尽可能较少资源使用
  • 本周股指想法
  • 产品背景知识——API、SDK、Library、Framework、Protocol
  • 10.【C语言学习笔记】指针(二)
  • Python 数据分析与机器学习入门 (八):用 Scikit-Learn 跑通第一个机器学习模型
  • stm32达到什么程度叫精通?
  • 百度文心大模型4.5系列正式开源,开源会给百度带来什么?
  • docker-compose部署Nacos、Seata、MySQL
  • API接口安全-1:身份认证之传统Token VS JWT
  • 【甲方安全建设】SDL基线建设及审计评估
  • 从设计到开发一个小程序页面
  • JavaScript异步编程的五种方式
  • RocketMQ第五节(springboot整合MQ)
  • C语言main函数的原理:程序入口的奥秘
  • docker使用容器网络
  • 华为云Flexus+DeepSeek征文 | 对接华为云ModelArts Studio大模型:AI赋能投资理财分析与决策
  • 【软考高项论文】信息系统项目的人力资源管理
  • springboot中多个定时任务(@Scheduled)如何互不影响
  • P1967 [NOIP 2013 提高组] 货车运输
  • localStorage 和 sessionStorage
  • 编译原理——运行时存储组织与内存管理
  • Zookeeper安装使用教程
  • 回写缓存为何需要脏位?
  • SimLOD代码精读(二)建立Octree之Splitting Pass分裂阶段