2026/10/10 6:20:49

SpringBoot项目的启动

SpringBoot项目的启动 Spring是什么Spring 是一个 Java 框架。它解决的核心问题是Java 代码里到处都是 new 对象导致类和类之间紧紧耦合在一起改一个地方要动十个地方IoC控制反转是什么publicclassOrderService{// 不自己 new由外部注入进来AutowiredprivateUserDaouserDao;}「控制反转」 把「创建对象的控制权」从你的代码反转给 Spring 框架。 Spring 帮你 new、帮你组装、帮你管理这些对象的生死Bean是什么被 Spring 管理的对象就叫 Bean记住Bean 交给 Spring 创建和管理的 Java 对象Component// ← 加上这个Spring 就会创建一个 UserService 的 BeanpublicclassUserService{}Service// 其实 Service 内部就是 Component只是语义更清晰publicclassOrderService{}容器ApplicationContext是什么容器 一个装满 Bean 的大盒子 一堆管理这些 Bean 的能力负责扫描类、创建 Bean处理 Bean 和 Bean 之间的依赖关系管理 Bean 的生命周期创建 → 初始化 → 销毁处理配置文件、事件、国际化、AOP 等Spring Boot 启动的本质就是把这个容器造出来、填满、然后启动起来从main方法看起SpringBootApplicationpublicclassDemoApplication{publicstaticvoidmain(String[]args){SpringApplication.run(DemoApplication.class,args);}}SpringBootApplication其实是三个注解叠在一起的Target(ElementType.TYPE)Retention(RetentionPolicy.RUNTIME)DocumentedInheritedSpringBootConfiguration// ①EnableAutoConfiguration// ②ComponentScan// ③publicinterfaceSpringBootApplication{// 一些过滤属性比如 exclude、scanBasePackages}SpringBootConfigurationEnableAutoConfigurationComponentScanSpringBootConfigurationConfiguration// ← 它的本体就是这个publicinterfaceSpringBootConfiguration{}其实就是configuration作用标记这个类是一个配置类Spring 需要知道每个对象该怎么造出来配置类就是告诉它这件事的地方Configuration// 告诉 Spring下面这个类是说明书照着它造对象publicclassMyConfig{Bean// 告诉 Spring这个方法是「一条制造配方」publicDataSourcedataSource(){returnnewDruidDataSource();}}Configuration把 MyConfig 标记为配置类。Spring 启动时会读取这个类里的所有Bean 方法Bean修饰一个方法意思是执行这个方法把返回的对象放进容器方法名 dataSource → 默认就是这个 Bean 的名字返回类型 DataSource → 就是这个 Bean 的类型return new DruidDataSource() → 这就是实际造出来的对象于是容器里就有了一个 DataSource 类型的 BeanComponentScan组件扫描作用告诉 Spring 去指定的包下面扫描把带Component/Service/Repository/Controller 的类都注册成 BeanEnableAutoConfiguration自动配置解决的痛点假设没有自动配置你想在项目里用 Redis。你要做的事ConfigurationpublicclassRedisConfig{BeanpublicRedisConnectionFactoryredisConnectionFactory(){LettuceConnectionFactoryfnewLettuceConnectionFactory();f.setHostName(localhost);f.setPort(6379);// 连接池、序列化器、超时时间……几十行配置returnf;}BeanpublicRedisTemplateString,ObjectredisTemplate(){...}}用 MyBatis 再写一堆用 Druid 再写一堆……每个第三方组件都要自己写样板代码而 Spring Boot 的目标是加一个依赖就直接能用dependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-data-redis/artifactId/dependency依赖一加RedisTemplate 就自动出现在容器里Autowired 直接用Spring Boot 在它的 jar 包里提前替你把配置类写好了。EnableAutoConfiguration 的职责就是把这些预写好的配置类找出来、批量注册回到代码Import(AutoConfigurationImportSelector.class)// ← 关键publicinterfaceEnableAutoConfiguration{}Import 会把 AutoConfigurationImportSelector 这个类「导入」进来Spring 会调用它的 selectImports() 方法让它返回一大堆需要被注册的配置类这些类名再jar包内名单里的类内部是如何的ConfigurationConditionalOnClass(RedisOperations.class)// ← 条件classpath 里有 Redis 相关类才生效EnableConfigurationProperties(RedisProperties.class)// ← 绑定 application.yml 里的配置publicclassRedisAutoConfiguration{BeanConditionalOnMissingBean(nameredisTemplate)// ← 条件容器里还没有才造publicRedisTemplateObject,ObjectredisTemplate(RedisConnectionFactoryfactory){RedisTemplateObject,ObjecttnewRedisTemplate();t.setConnectionFactory(factory);returnt;}}那为什么不是把所有东西都注册为Bean呢ConditionalOnClass你引了 redis 依赖才有 Redis 相关类 → 才配置 RedisConditionalOnMissingBean你自己写了 Bean RedisTemplate → Spring 就不造了ConditionalOnProperty配置文件里某属性满足才生效spring.redis.enabledtrue启动的一系列流程你写的 main() 方法 │ ▼ SpringApplication.run() │ ├── 1. 创建 SpringApplication 对象推断应用类型、加载监听器 ├── 2. 发布 ApplicationStartingEvent ├── 3. 准备 Environment读 application.yml、命令行参数、环境变量 ├── 4. 打印 Banner那个大大的 SPRING 图 ├── 5. 创建 IoC 容器 ApplicationContext ├── 6. 准备容器注入环境、发布 ApplicationContextInitializedEvent ├── 7. 加载配置类你的 SpringBootApplication 类 ├── 8. ★ refresh() ★ ←— 整个启动最核心的一步 │ ├── 解析 ComponentScan、Import │ ├── ★ 自动配置生效几百个 AutoConfiguration 被条件过滤★ │ ├── 创建并启动内嵌 Tomcat │ └── 实例化所有单例 Bean ├── 9. 发布 ApplicationStartedEvent ├── 10. 执行 CommandLineRunner / ApplicationRunner └── 11. 发布 ApplicationReadyEvent → 应用可用启动分为new SpringApplication()步骤和run()步骤new SpringApplication() 阶段不启动任何容器、不扫描任何 Bean它只是在填表格——登记三件事我是谁primarySources / mainClass、我运行在什么环境webApplicationType、我带了哪些扩展initializers / listeners真正的启动动作全在后面的 run() 里先看一下run()的源码publicstaticConfigurableApplicationContextrun(Class?primarySource,String...args){returnrun(newClass?[]{primarySource},args);}publicstaticConfigurableApplicationContextrun(Class?[]primarySources,String[]args){// ① 先创建 SpringApplication 对象returnnewSpringApplication(primarySources).run(args);}run()就是先new然后再调用run()new SpringApplication()干的事情我是谁primarySources / mainClass我运行在什么环境webApplicationType我带了哪些扩展initializers / listenersSpringApplication 的构造函数不启动任何东西它只是把你希望 Spring 加载哪些类/资源登记到一张去重、保序的名单里primarySources等 run() 真正启动时才把这张名单拿出来用我是谁primarySources / mainClassthis.primarySourcesnewLinkedHashSet(Arrays.asList(primarySources));用LinkedHashSet去重run(DemoApp.class, OtherConfig.class, DemoApp.class) 里 DemoApp 传了两次只该注册一次。写成 new ArrayList(…) 就会重复注册保序多个配置类注册成 BeanDefinition 的先后顺序是确定的方便排查问题、也避免不同 JVM 运行结果不一样。写成 HashSet 顺序就随哈希值走了传的是一个.Class不是一个对象我运行在什么环境webApplicationType// 推断应用类型SERVLET / REACTIVE / NONE ★this.webApplicationTypeWebApplicationType.deduceFromClasspath();WebApplicationType.deduceFromClasspath() 的逻辑很简单看 classpath 上有什么类检查条件判定结果对应容器DispatcherHandler存在 且DispatcherServlet不存在REACTIVEWebFluxAnnotationConfigReactiveWebServerApplicationContextServlet 相关类存在SERVLET普通 WebAnnotationConfigServletWebServerApplicationContext都不满足NONE普通 Java 应用AnnotationConfigApplicationContext我带了哪些扩展initializers / listeners//加载 ApplicationContextInitializer从 spring.factoriessetInitializers(getSpringFactoriesInstances(ApplicationContextInitializer.class));// 加载 ApplicationListener从 spring.factoriessetListeners(getSpringFactoriesInstances(ApplicationListener.class));run()干的事情简化的源码publicConfigurableApplicationContextrun(String...args){// 计时器最后打印 Started DemoApplication in 2.345 secondsStopWatchstopWatchnewStopWatch();stopWatch.start();ConfigurableApplicationContextcontextnull;CollectionSpringApplicationRunListenerlistenersgetRunListeners();try{// ─── 1. 准备启动参数 ───ApplicationArgumentsapplicationArgumentsnewDefaultApplicationArguments(args);// ─── 2. 准备 Environment配置环境───ConfigurableEnvironmentenvironmentprepareEnvironment(listeners,applicationArguments);configureIgnoreBeanInfo(environment);// ─── 3. 打印 Banner ───BannerprintedBannerprintBanner(environment);// ─── 4. ★ 创建 IoC 容器 ★ ───contextcreateApplicationContext();exceptionReportersgetSpringFactoriesInstances(SpringBootExceptionReporter.class,newClass[]{ConfigurableApplicationContext.class},context);// ─── 5. 准备容器 ───prepareContext(context,environment,listeners,applicationArguments,printedBanner);// ─── 6. ★★★ 刷新容器最核心★★★ ───refreshContext(context);// ─── 7. 刷新后置处理空实现留给子类───afterRefresh(context,applicationArguments);stopWatch.stop();if(this.logStartupInfo){newStartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(),stopWatch);}// ─── 8. 发布 ApplicationStartedEvent ───listeners.started(context);// ─── 9. 执行 Runner ───callRunners(context,applicationArguments);}catch(Throwableex){handleRunFailure(context,ex,listeners);thrownewIllegalStateException(ex);}// ─── 10. 发布 ApplicationReadyEvent应用正式可用 ───listeners.running(context);returncontext;}变成人话就是开始计时 ↓ 告诉所有监听器我要开始启动了 → ApplicationStartingEvent ↓ 读配置文件、读环境变量、读命令行参数 → ApplicationEnvironmentPreparedEvent ↓ 打印 Banner ↓ 创建一个空容器此时容器里还没有 Bean ↓ 往容器里塞环境对象、主配置类 ↓ ★ 刷新容器 ★在这里把 Bean 全部创建出来Tomcat 也在这里启动 ↓ 停止计时打印启动耗时 ↓ 告诉监听器容器准备好了 → ApplicationStartedEvent ↓ 执行 CommandLineRunner / ApplicationRunner ↓ 告诉监听器应用可以接客了 → ApplicationReadyEvent配置是怎么被读进来的privateConfigurableEnvironmentprepareEnvironment(SpringApplicationRunListenerlisteners,ApplicationArgumentsapplicationArguments){// 1. 根据应用类型创建 Environment 对象ConfigurableEnvironmentenvironmentgetOrCreateEnvironment();// 2. 把命令行参数--server.port8081放进 EnvironmentconfigureEnvironment(environment,applicationArguments.getSourceArgs());// 3. ★ 通知监听器环境准备好了 ★// 这里会发布 ApplicationEnvironmentPreparedEvent// ↓// ConfigFileApplicationListenerBoot 2.3 之前/// ConfigDataEnvironmentPostProcessorBoot 2.4会在这里被触发// ↓// 它负责读取 application.yml / application.properties / bootstrap.ymllisteners.environmentPrepared(environment);...returnenvironment;}Environment 里的配置来源有优先级命令行参数 --server.port8081 ↑ 高于 SPRING_APPLICATION_JSON 环境变量 ↑ 高于 系统属性 System.getProperties() ↑ 高于 环境变量如 SERVER_PORT8081 ↑ 高于 application-{profile}.yml ↑ 高于 application.yml ↑ 高于 PropertySource 注解 ↑ 高于 默认属性容器怎么被选出来的