跳到主要内容 Spring Boot 4 核心启动流程详解 | 极客日志
Java java
Spring Boot 4 核心启动流程详解 Spring Boot 4 启动流程基于 SpringApplication.run() 入口,涵盖环境准备、容器创建、刷新及自动配置。核心涉及 spring.factories 扩展加载、AOT 提前编译优化、类加载器双亲委派机制及内嵌服务器启动。文章解析了从推断应用类型到发布就绪事件的生命周期,对比了新旧版本差异,并提供了 JDK 选型建议。
怪力乱神 发布于 2026/3/30 更新于 2026/4/13 1 浏览本文根据 Spring Boot 3/4 的源代码,针对 Spring Boot 核心启动流程进行了总结梳理。
Spring Boot 3/4 的启动核心在于自动化配置与事件驱动。它通过 SpringApplication.run() 入口,利用 spring.factories、AutoConfiguration、SPI、AOT 等加载扩展点。流程历经环境准备、容器刷新(含自动配置与条件化装配)、内嵌 Web 服务器启动,最后通过 Runner 回调完成就绪,全程由事件发布 - 监听机制串联。
准备【Run 方法执行前】 即执行 SpringApplication 的构造函数。
入口推断 :通过 WebApplicationType.deduceFromClasspath() 推断
如果有 Reactive 相关包,推断为 响应式应用
如果没有 Servlet 相关包,推断为普通应用
其它情况,推断为 WEB 应用
加载工厂配置 :利用 SpringFactoriesLoader 机制,读取 META-INF/spring.factories 文件
加载所有的 ApplicationContextInitializer(上下文初始化器)
加载所有的 ApplicationListener(事件监听器)
推断启动类 :SpringApplication 的 deduceMainApplicationClass
这是 Spring Boot 实现自动配置扩展的关键机制 AnnotationConfigServletWebServerApplicationContext
static WebApplicationType deduceFromClasspath () {
if (ClassUtils.isPresent(WEBFLUX_INDICATOR_CLASS, null ) && !ClassUtils.isPresent(WEBMVC_INDICATOR_CLASS, null ) && !ClassUtils.isPresent(JERSEY_INDICATOR_CLASS, null )) {
return WebApplicationType.REACTIVE;
}
for (String className : SERVLET_INDICATOR_CLASSES) {
if (!ClassUtils.isPresent(className, null )) {
return WebApplicationType.NONE;
}
}
return WebApplicationType.SERVLET;
}
核心启动流程(Run 方法内部) 调用 SpringApplication.run(YourApp.class, args) 时,真正的启动流程开始了。这个过程主要包含以下几个核心步骤。
启动监听与环境构建
启动监听器 :首先会启动一个 Startup 来记录启动耗时
准备环境 :创建 Environment 对象(如 StandardServletEnvironment)。
它会加载 application.properties 或 application.yml 配置文件。
它会收集 JVM 系统属性、操作系统环境变量等。
这个环境对象后续会被注入到容器中,供 @Value 等注解读取。
创建并准备应用上下文
创建容器 :根据第一阶段推断出的应用类型,通过反射创建具体的 ApplicationContext 实例【WEB 是 AnnotationConfigServletWebServerApplicationContext】。
前置处理 :将之前准备好的 Environment 关联到容器中,执行 ApplicationContextInitializer 的初始化逻辑。
打印 Banner :如果你配置了自定义的 Banner,它会在日志中打印出来
刷新容器(核心中的核心) :这是 Spring 框架最核心的逻辑,也是 Bean 生命开始的地方。
BeanFactory 预准备 :配置类加载器、后置处理器等。
注册 BeanPostProcessor :注册拦截 Bean 创建过程的后置处理器。
初始化 MessageSource :为国际化做准备。
初始化 ApplicationEventMulticaster :初始化事件广播器。
调用 onRefresh() :在这里,Spring Boot 会启动内嵌的 Web 服务器(Tomcat/Jetty/Undertow) 。这是 Web 应用能接收请求的关键。
注册监听器并发布事件 :将应用监听器注册到容器中,并广播之前的事件
自动配置与 Bean 加载
扫描与注册 :根据 @ComponentScan 扫描路径,将带有 @Component、@Service、@Controller 等注解的类解析为 BeanDefinition,注册到 BeanFactory 中。
自动配置生效 :这是 Spring Boot 的灵魂。
通过 @EnableAutoConfiguration 导入 AutoConfigurationImportSelector。
它再次利用 spring.factories 加载大量的 xxxAutoConfiguration 类。
这些配置类利用 @ConditionalOnXXX 注解,根据类路径下的依赖(比如是否有 Redis 的 jar 包)来决定是否要创建对应的 Bean(比如 RedisTemplate)。这就是'开箱即用'的原理
启动完成与收尾
执行 Runner :调用所有实现了 CommandLineRunner 和 ApplicationRunner 接口的 Bean 的 run 方法。这通常用于在应用启动后执行一些特定的逻辑(如预热缓存、打印启动完成日志等)。
发布就绪事件 :发布 ApplicationReadyEvent 事件,标志着应用已经准备好接收外部请求了。
核心启动流程汇总 阶段 关键动作 核心组件/概念 1. 推断与加载 推断应用类型,加载初始化器和监听器 SpringFactoriesLoader, WebApplicationType2. 环境准备 加载配置文件、环境变量,创建 Environment ConfigurableEnvironment, PropertySources3. 容器创建 根据类型创建 ApplicationContext ApplicationContext4. 刷新容器 核心 :准备上下文、注册 Bean、启动 Web 服务器refreshContext(), onRefresh()5. 自动配置 扫描组件、加载 spring.factories 中的自动配置类 @EnableAutoConfiguration, @Conditional6. 启动完成 执行 Runner,发布就绪事件 CommandLineRunner, ApplicationReadyEvent
核心流程讲解
如何加载工厂配置 在 Spring Boot 的启动过程中,'加载工厂配置' (即加载 META-INF/spring.factories)是一个非常基础且高频的操作。为了保证启动性能和运行时效率,Spring 框架在这一过程中设计了非常精妙的缓存机制 。
加载工厂配置主要由 SpringFactoriesLoader 这个类负责。它的主要工作是从类路径(classpath)下的所有 jar 包中,搜寻 META-INF/spring.factories 文件,并将其解析为 Map<String, List<String>> 的结构。
加载时的镜像缓存 spring.factories 借鉴了 SPI 的'思想' (即'约定优于配置',通过配置文件让框架自动发现扩展实现),但在实现手段 上完全重写,以适应 Spring Boot 的快速启动和条件化配置需求。
Spring Boot 通过 ClassLoader 级别的静态缓存 和 JDK 资源定位缓存 ,确保了 spring.factories 这种关键配置文件在应用生命周期内只被读取和解析一次,极大地提升了启动速度。
对资源加载过程的完全镜像缓存 ,标志着 Spring 框架在模块化 和云原生 支持上迈出了关键一步,为了适应云原生 和动态化 而进化的结果,更好的支持 Java Platform Module System (JPMS) 和 GraalVM Native Image 。
镜像缓存 :
ClassLoader 级别缓存 :维护一个 Map<ClassLoader, Map<resourceLocation, Factories>> 缓存,
factory 接口级别缓存 :维护各类接口与实现类的缓存 Map<FactoryInterface, List<factoryImplClassName>>
通过 ClassLoader 隔离不同模块或插件的加载环境。不同的 ClassLoader 加载的类是互相隔离的,所以缓存必须以 ClassLoader 为根
通过 Factories 存储 spring.factories 文件内的原始键值对
与 SPI 的区别 特性 Java SPI (JDK 原生) Spring Factories (Spring Boot) 配置文件路径 META-INF/services/META-INF/spring.factories配置文件格式 纯文本 ,每行一个实现类全限定名Properties 格式 (Key=Value),支持多 Key加载工具类 java.util.ServiceLoaderorg.springframework.core.io.support.SpringFactoriesLoader加载机制 懒加载 (Lazy Loading),利用 Iterator 饿汉加载 (Eager Loading),启动时全量加载 缓存机制 无 (每次获取都是新的迭代器) 有 (基于 ClassLoader 的静态缓存)实例化时机 获取时实例化 (On-Demand) 启动时或获取时批量实例化 条件化支持 无 (只要配置了就会加载) 有 (配合 @Conditional 注解)
如何推断主启动类 Spring Boot 3.x 以上版本利用 Java(Java 9+)的特性来优化底层性能。
使用 StackWalker 高效遍历调用栈,RETAIN_CLASS_REFERENCE 选项使得直接获取 Class 对象成为可能
Spring Boot 拿到这个类后,就知道从哪里开始扫描 @Component、@Service 等注解了
Spring boot 2.x private Class<?> deduceMainApplicationClass() {
try {
StackTraceElement[] stackTrace = (new RuntimeException ()).getStackTrace();
for (StackTraceElement stackTraceElement : stackTrace) {
if ("main" .equals(stackTraceElement.getMethodName())) {
return Class.forName(stackTraceElement.getClassName());
}
}
} catch (ClassNotFoundException e) {
}
return null ;
}
Spring boot 3/4 private @Nullable Class<?> deduceMainApplicationClass() {
return StackWalker.getInstance(StackWalker.Option.RETAIN_CLASS_REFERENCE).walk(this ::findMainClass).orElse(null );
}
private Optional<Class<?>> findMainClass(Stream<StackFrame> stack) {
return stack.filter((frame) -> Objects.equals(frame.getMethodName(), "main" )).findFirst().map(StackWalker.StackFrame::getDeclaringClass);
}
新旧版本对比 特性 旧方式 (new RuntimeException()) 新方式 (StackWalker) 原理 创建一个异常对象,利用其填充的堆栈信息 使用 JVM 原生的堆栈遍历 API 性能 较差 。需要生成完整的堆栈数组,涉及大量的字符串操作和对象创建,对 GC 有压力。优秀 。支持惰性加载,不需要一次性拷贝整个堆栈,内存友好,速度快。功能 只能获取堆栈信息的快照。 可以按需获取类引用(配合 RETAIN_CLASS_REFERENCE)。 适用性 适用于所有 Java 版本。 仅适用于 Java 9+ 。
AutoConfiguration 如何加载?针对 META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports 加载发生在 Spring Boot 启动流程的 准备环境之后、刷新容器之前 。
触发点
调用 SpringApplication.run()
解析注解 :Spring 容器开始解析主启动类上的注解
解析到 @SpringBootApplication 注解时,由于它组合了 @EnableAutoConfiguration,进而触发了自动配置机制
核心加载器:AutoConfigurationImportSelector
**@EnableAutoConfiguration** 注解上标注了 @Import(AutoConfigurationImportSelector.class)。
当 Spring 解析 @Import 注解时,会调用 AutoConfigurationImportSelector 的 内部 类 AutoConfigurationGroup 的 process(AnnotationMetadata annotationMetadata, DeferredImportSelector deferredImportSelector) 方法。
正是这个 process() 方法,负责去查找并读取 AutoConfiguration.imports 文件。
这种方式比 spring.factories 更快,因为它不需要解析键值对(Key-Value),只需要按行读取类名,且避免了 spring.factories 的全量反射扫描。
AutoConfigurationEntry autoConfigurationEntry = autoConfigurationImportSelector.getAutoConfigurationEntry(annotationMetadata);
protected AutoConfigurationEntry getAutoConfigurationEntry (AnnotationMetadata annotationMetadata) {
if (!isEnabled(annotationMetadata)) {
return EMPTY_ENTRY;
}
AnnotationAttributes attributes = getAttributes(annotationMetadata);
List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
configurations = removeDuplicates(configurations);
Set<String> exclusions = getExclusions(annotationMetadata, attributes);
checkExcludedClasses(configurations, exclusions);
configurations.removeAll(exclusions);
configurations = getConfigurationClassFilter().filter(configurations);
fireAutoConfigurationImportEvents(configurations, exclusions);
return new AutoConfigurationEntry (configurations, exclusions);
}
protected List<String> getCandidateConfigurations (AnnotationMetadata metadata, @Nullable AnnotationAttributes attributes) {
ImportCandidates importCandidates = ImportCandidates.load(this .autoConfigurationAnnotation, getBeanClassLoader());
List<String> configurations = importCandidates.getCandidates();
Assert.state(!CollectionUtils.isEmpty(configurations), "No auto configuration classes found in " + "META-INF/spring/" + this .autoConfigurationAnnotation.getName() + ".imports. If you " + "are using a custom packaging, make sure that file is correct." );
return configurations;
}
与 META-INF/spring.factories 加载对比 特性 META-INF/spring.factoriesMETA-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports核心用途 SPI 扩展机制:用于加载框架扩展点(Initializer, Listener)和旧版自动配置。 自动配置专用:仅用于加载 @EnableAutoConfiguration 对应的配置类。 加载时机 早:SpringApplication 构造器阶段。 晚:SpringApplication.run() 刷新容器阶段(解析 @Import 时)。 加载驱动 SpringFactoriesLoader:主动扫描类路径下的所有该文件并合并。ImportCandidates / AutoConfigurationImportSelector:被动响应 @EnableAutoConfiguration 的导入需求。触发条件 只要创建 SpringApplication 实例就会加载。 必须在配置类上标注 @EnableAutoConfiguration(或 @SpringBootApplication)才会加载。 底层原理 基于 ClassLoader.getResources() 的全量扫描。 基于 @Import 注解的条件化导入。
SPI 的使用 Spring Boot 利用标准 SPI 主要是为了打破'双亲委派'加载机制 ,解决 '父 ClassLoader 加载的类需要调用子 ClassLoader 加载的实现' 这一难题。
JDBC 驱动的自动注册(最经典的案例),各个数据库的驱动 jar 都会定义 /META-INF/services/java.sql.Driver 文件
Logging 日志系统的桥接与实现,
logback-classic jar 中有 org.slf4j.spi.SLF4JServiceProvider 配置
ch.qos.logback.classic.spi.Configurator spring-boot-4.0.0.jar
org.apache.logging.log4j.util.PropertySource spring-boot-4.0.0.jar
jakarta.servlet.ServletContainerInitializer servlet 初始化:spring-web-7.0.1.jar
javax.annotation.processing.Processor javax 注解处理:spring-boot-configuration-processor-4.0.0.jar
jakarta.enterprise.inject.spi.Extension 企业级扩展 spring-data-redis-4.0.0.jar
reactor.blockhound.integration.BlockHoundIntegration spring-core-7.0.1.jar
Spring Boot 使用标准 SPI (META-INF/services) 主要是为了兼容底层 Java 规范(如 JDBC、Servlet)或第三方标准如 BlockHound 框架 以及解决类加载器的隔离问题 ;而使用 spring.factories 是为了实现 Spring 应用层面的自动化配置 。
类加载器:双亲委派机制
解决的问题
双亲委派 双亲委派机制的核心解决方案 :规定类加载器加载类时,优先委派给父类加载器,只有父类加载器无法加载时,才由当前类加载器自行加载。其核心价值体现在三点:
保证类的唯一性:同一类仅由一个类加载器加载(通常是最顶层的启动类加载器),避免类冲突;
保护核心类库:JDK 核心类(如 java.lang.*)由顶层类加载器加载,自定义类加载器无法加载同名类,防止核心类被篡改;
提升加载效率:避免重复加载,父类加载器已加载的类,子类加载器直接复用,减少内存占用。
层级顺序 :启动类加载器 BootstrapClassLoader → 扩展类加载器 ExtClassLoader → 应用程序类加载器 AppClassLoader → 自定义类加载器(自上而下,父加载器在上)
在标准的双亲委派机制下,类加载器的层级是单向的:子加载器可以访问父加载器加载的类,但父加载器无法访问子加载器加载的类 。
优势
安全性:保护 JDK 核心类库不被恶意代码替换,防止 '伪核心类' 引发的安全风险(如自定义 java.lang.System 类篡改系统方法);
唯一性:确保同一类仅被加载一次,避免类冲突(如不同模块的同名类不会重复加载);
高效性:缓存机制 + 委派复用,减少重复加载,提升 JVM 运行效率;
规范性:明确类加载器的职责边界,形成统一的类加载秩序,便于 JVM 管理类的生命周期。
局限性
灵活性不足 :严格的委派机制限制了类加载的灵活性,无法满足 '父类加载器需要加载子类加载器范围的类' 的场景(如 Tomcat 的 Web 应用类加载);
无法加载异构类 :若需要加载不在默认加载范围的类(如加密的.class 文件、网络传输的类),默认类加载器无法实现,需自定义类加载器打破委派;
热部署支持有限 :默认类加载器加载类后,无法卸载已加载的类(JVM 不支持),需自定义类加载器实现热部署(如 OSGi 框架)。
打破双亲委派
WEB 容器 需隔离多个 Web 应用的类(避免冲突),同时共享公共依赖(如 Spring),采用自定义加载器(如 WebAppClassLoader)绕过双亲委派。
每个 Web 应用对应一个独立的 WebAppClassLoader(自定义类加载器)
加载类时,先检查自身缓存,若未加载,优先加载 WEB-INF/classes 和 WEB-INF/lib 下的类;
若自身无法加载,再委派给父类加载器(Tomcat 的 Common 类加载器)
确保每个 Web 应用的类独立加载,避免版本冲突
JNDI/SPI/JDBC Java Naming and Directory Interface
启动类加载器需加载用户实现的接口(如 JNDI 服务)。
通过线程上下文类加载器(Thread.setContextClassLoader())让父加载器请求子加载器完成加载
核心 JDBC 类(rt.jar 加载)需动态加载数据库驱动(如 mysql-connector.jar),依赖线程上下文类加载器打破委派链
Java 的核心库(rt.jar)中定义了 java.sql.Driver 接口,这个接口由启动类加载器 (Bootstrap ClassLoader)加载。
具体的数据库驱动实现(如 com.mysql.cj.jdbc.Driver)是由第三方提供的(放在应用的 classpath 中),只能由应用程序类加载器 (App ClassLoader)加载。
线程上下文 ClassLoader 是 java.lang.Thread 类的一个实例变量(contextClassLoader),如果没有手动设置,线程会继承其父线程的上下文类加载器,在应用程序运行的初始线程(main 线程)中,这个值默认就是应用程序类加载器 (App ClassLoader)。
设置 :应用程序启动时,main 线程的上下文类加载器被设为 AppClassLoader。
调用 :DriverManager(由 BootstrapClassLoader 加载)需要加载驱动。
获取 :DriverManager 不再用自己的加载器去加载,而是通过 Thread.currentThread().getContextClassLoader() 拿到了 AppClassLoader。
加载 :DriverManager 使用拿到的 AppClassLoader 去加载 com.mysql...Driver。
结果 :核心库(父)成功加载了应用库(子)中的类,打破了双亲委派。
加密类加载 默认类加载器仅能加载本地文件系统中的.class 文件,若需要加载加密的.class 文件(防止反编译),需自定义类加载器,并重写 loadClass 方法打破双亲委派。
自定义类加载器需继承 ClassLoader 类,并重写 loadClass 方法(打破委派)或 findClass 方法(遵循委派,仅扩展加载路径)
打破双亲委派的核心是:改变 '先委派父类加载器' 的逻辑,优先加载自定义范围的类,失败后再委派;
defineClass 方法是关键:将解密后的字节数组转换为 JVM 可识别的 Class 对象,该方法由父类 ClassLoader 提供,不可重写
import java.io.ByteArrayOutputStream;
import java.io.FileInputStream;
import java.io.IOException;
public class EncryptClassLoader extends ClassLoader {
private static final byte KEY = 0x99 ;
private final String encryptClassPath;
public EncryptClassLoader (String encryptClassPath) {
super (ClassLoader.getSystemClassLoader());
this .encryptClassPath = encryptClassPath;
}
@Override
protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
synchronized (getClassLoadingLock(name)) {
Class<?> c = findLoadedClass(name);
if (c != null ) {
return c;
}
if (name.startsWith("io.renren" )) {
try {
byte [] classData = loadEncryptClassData(name);
c = defineClass(name, classData, 0 , classData.length);
if (resolve) {
resolveClass(c);
}
return c;
} catch (IOException e) {
return super .loadClass(name, resolve);
}
}
return super .loadClass(name, resolve);
}
}
private byte [] loadEncryptClassData(String className) throws IOException {
String filePath = encryptClassPath + "/" + className.replace("." , "/" ) + ".class" ;
try (FileInputStream fis = new FileInputStream (filePath); ByteArrayOutputStream bos = new ByteArrayOutputStream ()) {
int b;
while ((b = fis.read()) != -1 ) {
bos.write(b ^ KEY);
}
return bos.toByteArray();
}
}
}
public class CustomClassLoaderTest {
public static void main (String[] args) throws Exception {
String encryptClassPath = "D:/encryptClasses" ;
EncryptClassLoader encryptClassLoader = new EncryptClassLoader (encryptClassPath);
Class<?> userClass = encryptClassLoader.loadClass("io.renren.User" );
Object user = userClass.newInstance();
userClass.getMethod("sayHello" ).invoke(user);
ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader();
systemClassLoader.loadClass("io.renren.User" );
}
}
AOT Ahead-of-Time,提前编译 : Spring Boot 3.0 版本发布后引入的最核心、最具变革性的特性之一。它主要是为了解决传统 Java 应用启动慢 和内存占用高 的痛点,特别是在云原生和 Serverless 场景下。
核心目标是:让 Spring Boot 应用不再依赖传统的 JVM 运行时解释执行,而是提前把'不确定的'动态逻辑转化为'确定的'静态逻辑 。
GraalVM Native Image
Spring AOT Engine
RuntimeHints
传统实现方式 传统的 Spring Boot 应用(JIT 模式)启动时,JVM 需要做大量的工作:
加载类 :扫描 @Component, @Service 等注解。
创建代理 :为 @Transactional, @Cacheable 等创建动态代理。
解析配置 :读取 application.yml,绑定配置属性。
实例化 Bean :通过反射创建对象。
AOT 方式 Spring Boot AOT 则是在编译阶段(Build Time)就完成了上述大部分工作:
源码分析 :在构建(Maven/Gradle)时,AOT 插件会分析你的字节码。
静态化处理 :它会生成大量的 Java 源代码,这些代码直接包含了 Bean 的注册、代理的创建、配置的绑定等逻辑。
Bean 注册静态化 :不再通过 @ComponentScan 扫描包,而是生成一个 BeanFactoryInitializationAotProcessor,在启动时直接注册所有已知的 Bean 定义
代理提前生成 :AOP 代理(如事务)不再使用运行时动态代理(CGLIB/JDK Proxy),而是尽可能在编译期生成代理类
资源/配置预绑定 :将 @ConfigurationProperties 的绑定逻辑生成为直接的 setter 调用,避免运行时使用反射进行属性填充
移除不可达代码 :GraalVM 会进行严格的'可达性分析',所有没有被引用到的代码(死代码)都会被直接从最终的二进制文件中移除,这也是体积变小的原因
原生编译 :利用 GraalVM,将这些生成的代码和你的业务代码一起编译成本地机器码(Native Image) 。
AOT 带来的三大变革 维度 传统 JIT 模式 (HotSpot) AOT 模式 (Native Image) 启动速度 慢(秒级甚至分钟级) 极快(毫秒级) ,冷启动通常在 100ms 以内内存占用 高(需要堆内存存储类元数据、JIT 编译代码) 极低(降低 40%-60%) ,不需要 JIT 编译器运行时性能 预热后性能极高(JIT 优化) 启动即巅峰(没有预热时间),但极致峰值性能略低于预热后的 JIT 部署包 JAR 包(包含字节码) 可执行文件(EXE/Linux 二进制文件,不依赖 JRE)
内嵌服务器是如何启动的 在 refreshContext 阶段的 onRefresh() 方法中,Spring Boot 会去寻找 ServletWebServerFactory(如 TomcatServletWebServerFactory)并调用其 getWebServer() 方法,从而启动 Tomcat 并将 Servlet 上下文关联进去。
启动过程如下:
构造 SpringApplication 对象 -> 执行 run() 方法 -> 准备环境 -> 创建容器 -> 刷新容器(执行各种 PostProcessor 和 Listener) -> 发布就绪事件
protected void onRefresh () {
super .onRefresh();
try {
this .createWebServer();
} catch (Throwable ex) {
throw new ApplicationContextException ("Unable to start web server" , ex);
}
}
public WebServer getWebServer (ServletContextInitializer... initializers) {
Tomcat tomcat = this .createTomcat();
this .prepareContext(tomcat.getHost(), initializers);
return this .getTomcatWebServer(tomcat);
}
扩展点
三个明星扩展点 Spring Boot 的设计理念之一就是'约定优于配置',同时提供了非常灵活的扩展机制 。ApplicationContextInitializer、ApplicationListener、CommandLineRunner)就像是 Spring Boot 启动流水线上的三个关键检修站,让你可以在不同的阶段介入并执行自定义逻辑。
扩展点的执行顺序是固定的:
ApplicationContextInitializer (初始化上下文) → ApplicationListener (监听刷新/环境事件) → CommandLineRunner (最后执行业务)
扩展点 触发时机 (时间轴) 核心作用 典型应用场景 ApplicationContextInitializer 最早 (容器刷新前) 修改环境或上下文 动态添加配置属性、注册 BeanFactoryPostProcessor ApplicationListener 贯穿全程 (监听事件) 监听生命周期事件 环境准备后读取配置、容器刷新后执行逻辑 CommandLineRunner 最后 (应用就绪前) 执行具体的业务代码 数据初始化、缓存预热、健康检查
BeanPostProcessor 这是 Spring 中最强大也最复杂的扩展点之一。它允许你在 Bean 初始化的前后 执行自定义逻辑。
核心方法 :
postProcessBeforeInitialization: 在 Bean 的 @PostConstruct 或 InitializingBean 之前执行。
postProcessAfterInitialization: 在 Bean 初始化之后执行(AOP 代理通常在这里生成)。
应用场景 :
AOP 代理 :Spring AOP 就是利用这个机制实现的。
修改 Bean 属性 :比如自动给某些字段注入值。
代理包装 :返回一个 Bean 的代理对象。
BeanFactoryPostProcessor BeanFactoryPostProcessor 处理的就是 Bean 的'图纸'(即 BeanDefinition)。
核心能力 :在容器实例化任何 Bean 之前,读取并修改 BeanDefinition 的配置元数据。
应用场景 :
修改配置 :比如动态修改某个 Bean 的构造参数或属性值。
自定义占位符解析 :扩展
懒加载与 Profile
懒加载 当开启懒加载后,Bean 在容器启动时不会 被实例化。只有当该 Bean 第一次被注入或被请求时,Spring 才会去创建它。
全局配置
在单个 Bean 通过 @Lazy 注解
spring:
main:
lazy-initialization: true
维度 优势 劣势/注意 启动时间 ⬇️ 显著缩短。只加载必要组件,适合大型项目本地调试。 无 运行时性能 ⚠️ 首次请求变慢。第一次 HTTP 请求处理时间会变长,因为需要触发 Bean 初始化。 后续请求不受影响。 问题排查 ⚠️ 延迟暴露错误。Bean 创建过程中的错误(如配置错误)不会在启动时抛出,而是在第一次使用时才暴露。 不利于'早发现、早解决',生产环境需谨慎。 排除机制 支持排除。如果开启了全局懒加载,但希望某些核心 Bean 启动时加载,可以使用 @Lazy(false) 或实现 LazyInitializationExcludeFilter。 需要额外配置。
Spring Boot Profile (多环境隔离) 在软件开发中,开发(dev)、测试(test)和生产(prod)环境的配置(如数据库地址、Redis 密码)通常是不同的。Profile 就是用来实现这种环境隔离 的机制,让你无需修改代码即可切换环境。
配置文件命名规则 :
application.yml:主配置文件(通用配置)。
application-dev.yml:开发环境专属配置。
application-prod.yml:生产环境专属配置。
激活机制 :Spring Boot 会自动加载主配置文件 + 当前激活 Profile 的专属配置,注意:同时同时激活多个
命令行参数 :java -jar myapp.jar --spring.profiles.active=prod
JVM 参数 :-Dspring.profiles.active=dev
环境变量 :SPRING_PROFILES_ACTIVE=prod
配置文件 :在 application.yml 中写死(不推荐用于生产,适合本地开发)。
代码中使用 @Profile
你可以在配置类上使用注解,让某些 Bean 只在特定环境下生效
IOC(Inversion of Control,控制反转)
概念 Spring Boot 的 IOC(Inversion of Control,控制反转) 容器是整个框架的核心基石 。简单来说,它就是一个超级工厂,负责创建、配置和管理你项目中所有 Java 对象(在 Spring 里叫 Bean )。
在 Spring Boot 中,IOC 容器主要由两个核心接口 实现:
BeanFactory :基础容器,提供基本的 IOC 功能
**ApplicationContext**:BeanFactory 的子接口,功能更强大,也是 Spring Boot 默认使用的容器
依赖查找/注入 :把对象放进容器(存),从容器里拿出对象(取)。
生命周期管理 :控制对象什么时候创建、什么时候销毁、以及中间的加工过程
Bean 的生命周期管理 代码可看:ConfigurationClassPostProcessor、ConfigurationClassParser、ClassPathBeanDefinitionScanner、ClassPathScanningCandidateComponentProvider、DefaultListableBeanFactory。
扫描与注册 Spring Boot 会自动扫描主启动类 @SpringBootApplication 注解上的 @ComponentScan。
动作 :容器会扫描指定包下的所有类。
标记 :如果发现类上有 @Component、@Service、@Controller、@Repository 或 @Configuration 等注解,就会把这个类的信息提取出来。
结果 :这些信息会被解析成 BeanDefinition(Bean 的定义信息/说明书),注册到一个类似 Map 的结构中等待处理。
实例化与依赖注入 容器根据 BeanDefinition 开始干活:
实例化 :通过反射(newInstance 或 CGLIB)调用类的构造方法,创建出原始的对象(半成品)。
依赖注入(DI) :检查这个对象里有没有 @Autowired 或 @Resource 注解的属性。如果有,容器会去池子里找对应的 Bean,通过 setter 方法或字段注入进去。
初始化 最关键的'加工'环节。对象虽然创建好了,但还没完全准备好对外提供服务。
Aware 接口回调 :如果 Bean 实现了某些接口(如 ApplicationContextAware),容器会把对应的资源(如容器本身)注入进去。
BeanPostProcessor(后置处理器) :这是 Spring 最强大的扩展点。
postProcessBeforeInitialization:在初始化前 执行(比如执行 @PostConstruct 注解的方法)。
postProcessAfterInitialization:在初始化后 执行。AOP 代理通常在这里产生 (比如给你的 Service 加上事务管理)。
如何解决循环依赖? 在实际开发中,经常出现 A Service 依赖 B Service,而 B Service 又依赖 A Service 的情况(循环依赖)。
Spring 使用了三级缓存 机制来解决这个问题(仅限于单例 Bean 的 setter 注入):
一级缓存 :存放完全初始化好的 Bean(singletonObjects)。
二级缓存 :存放早期暴露的 Bean(earlySingletonObjects),此时属性还没填充。
三级缓存 :存放 Bean 工厂对象(singletonFactories)。
三级缓存对应 DefaultSingletonBeanRegistry 类中的三个 Map。
private final Map<String, Object> singletonObjects = new ConcurrentHashMap <>(256 );
private final Map<String, Object> earlySingletonObjects = new ConcurrentHashMap <>(16 );
private final Map<String, ObjectFactory<?>> singletonFactories = new ConcurrentHashMap <>(16 );
创建 A,发现需要 B。
将 A 的工厂 放入三级缓存,然后去创建 B。
创建 B 时,发现需要 A。B 去缓存中找,发现三级缓存里有 A 的工厂,于是通过工厂拿到 A 的早期引用(半成品)。
B 创建完成,放入一级缓存。
回头继续完善 A 的创建,此时 B 已经有了,A 也就顺利创建完成。
JDK 版本比较 Spring 团队在官方文档和博客中明确推荐 Liberica JDK 用于 Spring Native 项目。
Liberica JDK (NIK) 。它是 Spring 官方推荐的'最佳拍档',能帮你省去大量排查构建错误的时间,是目前体验最好的组合。
Liberica 是 Spring 生态的优选方案,在 AOT 和 Native 场景下有加成;Temurin 是'全能选手',免费且稳定,适合绝大多数场景;标准 OpenJDK 则更多是作为底层技术的基石存在。
特性 Liberica JDK Eclipse Temurin 标准 OpenJDK (上游) Spring Native / AOT (原生镜像) ⭐⭐⭐⭐⭐ (最强) 官方推荐,深度集成⭐⭐⭐ (中等) 支持良好,但需手动配置⭐⭐ (一般) 通常不直接支持,需转用 GraalVMJVM 模式下的 AOT (启动加速) ⭐⭐⭐⭐⭐ (优秀) 容器优化好,启动快⭐⭐⭐⭐ (良好) 标准兼容,稳定⭐⭐⭐ (标准) 取决于具体发行商优化GraalVM 兼容性 原生支持 (Liberica NIK)需额外安装 GraalVM不包含 ,需切换工具链主要优势 Spring 团队官方推荐,构建 Native 镜像最省心 免费、社区中立、广泛兼容、企业级稳定 纯粹的参考实现,无额外封装 适用场景 Spring Native 开发、Serverless、追求极致启动速度 传统微服务、企业级应用、云原生部署 学习研究、对发行版无特殊要求
微信扫一扫,关注极客日志 微信公众号「极客日志」,在微信中扫描左侧二维码关注。展示文案:极客日志 zeeklog
相关免费在线工具 Keycode 信息 查找任何按下的键的javascript键代码、代码、位置和修饰符。 在线工具,Keycode 信息在线工具,online
Escape 与 Native 编解码 JavaScript 字符串转义/反转义;Java 风格 \uXXXX(Native2Ascii)编码与解码。 在线工具,Escape 与 Native 编解码在线工具,online
JavaScript / HTML 格式化 使用 Prettier 在浏览器内格式化 JavaScript 或 HTML 片段。 在线工具,JavaScript / HTML 格式化在线工具,online
JavaScript 压缩与混淆 Terser 压缩、变量名混淆,或 javascript-obfuscator 高强度混淆(体积会增大)。 在线工具,JavaScript 压缩与混淆在线工具,online
Base64 字符串编码/解码 将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
Base64 文件转换器 将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online