Spring boot 4 搞懂 Spring Boot 核心启动流程
本文根据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
staticWebApplicationTypededuceFromClasspath(){if(ClassUtils.isPresent(WEBFLUX_INDICATOR_CLASS,null)&&!ClassUtils.isPresent(WEBMVC_INDICATOR_CLASS,null)&&!ClassUtils.isPresent(JERSEY_INDICATOR_CLASS,null)){returnWebApplicationType.REACTIVE;}for(String className :SERVLET_INDICATOR_CLASSES){if(!ClassUtils.isPresent(className,null)){returnWebApplicationType.NONE;}}returnWebApplicationType.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事件,标志着应用已经准备好接收外部请求了。
- 执行 Runner:调用所有实现了
核心启动流程汇总
| 阶段 | 关键动作 | 核心组件/概念 |
|---|---|---|
| 1. 推断与加载 | 推断应用类型,加载初始化器和监听器 | SpringFactoriesLoader, WebApplicationType |
| 2. 环境准备 | 加载配置文件、环境变量,创建 Environment | ConfigurableEnvironment, PropertySources |
| 3. 容器创建 | 根据类型创建 ApplicationContext | ApplicationContext |
| 4. 刷新容器 | 核心:准备上下文、注册 Bean、启动 Web 服务器 | refreshContext(), onRefresh() |
| 5. 自动配置 | 扫描组件、加载 spring.factories 中的自动配置类 | @EnableAutoConfiguration, @Conditional |
| 6. 启动完成 | 执行 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 加载的类是互相隔离的,所以缓存必须以 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.ServiceLoader | org.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@NullableClass<?>deduceMainApplicationClass(){returnStackWalker.getInstance(StackWalker.Option.RETAIN_CLASS_REFERENCE).walk(this::findMainClass).orElse(null);}privateOptional<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);protectedAutoConfigurationEntrygetAutoConfigurationEntry(AnnotationMetadata annotationMetadata){if(!isEnabled(annotationMetadata)){returnEMPTY_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);returnnewAutoConfigurationEntry(configurations, exclusions);}protectedList<String>getCandidateConfigurations(AnnotationMetadata metadata,@NullableAnnotationAttributes 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.factories | META-INF/spring/org.springframework.boot<br>.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)
使用上下文类加载器的流程(以 JDBC 为例):
- 设置:应用程序启动时,main线程的上下文类加载器被设为
AppClassLoader。 - 调用:
DriverManager(由BootstrapClassLoader加载)需要加载驱动。 - 获取:
DriverManager不再用自己的加载器去加载,而是通过Thread.currentThread().getContextClassLoader()拿到了AppClassLoader。 - 加载:
DriverManager使用拿到的AppClassLoader去加载com.mysql...Driver。 - 结果:核心库(父)成功加载了应用库(子)中的类,打破了双亲委派。
加密类加载
默认类加载器仅能加载本地文件系统中的.class 文件,若需要加载加密的.class 文件(防止反编译),需自定义类加载器,并重写loadClass方法打破双亲委派
- 自定义类加载器需继承
ClassLoader类,并重写loadClass方法(打破委派)或findClass方法(遵循委派,仅扩展加载路径) - 打破双亲委派的核心是:改变 “先委派父类加载器” 的逻辑,优先加载自定义范围的类,失败后再委派;
defineClass方法是关键:将解密后的字节数组转换为 JVM 可识别的 Class 对象,该方法由父类ClassLoader提供,不可重写
importjava.io.ByteArrayOutputStream;importjava.io.FileInputStream;importjava.io.IOException;// 自定义类加载器:加载加密的.class文件(示例中加密逻辑简化为“异或解密”)publicclassEncryptClassLoaderextendsClassLoader{// 加密/解密密钥(实际开发中需复杂密钥)privatestaticfinalbyteKEY=0x99;// 加密类文件的存储路径privatefinalString encryptClassPath;// 构造器:指定加密类路径,父加载器为应用程序类加载器publicEncryptClassLoader(String encryptClassPath){super(ClassLoader.getSystemClassLoader());this.encryptClassPath = encryptClassPath;}// 核心方法:加载类(打破双亲委派,优先加载加密类)@OverrideprotectedClass<?>loadClass(String name,boolean resolve)throwsClassNotFoundException{synchronized(getClassLoadingLock(name)){// 1. 检查缓存是否已加载Class<?> c =findLoadedClass(name);if(c !=null){return c;}// 2. 打破双亲委派:优先加载自定义路径下的加密类(仅处理com.example包下的类)if(name.startsWith("io.renren")){try{// 加载加密的.class文件并解密byte[] classData =loadEncryptClassData(name);// 定义类(将字节数组转换为Class对象) c =defineClass(name, classData,0, classData.length);if(resolve){resolveClass(c);}return c;}catch(IOException e){// 自定义路径加载失败,再委派给父类加载器returnsuper.loadClass(name, resolve);}}// 3. 非自定义包的类,遵循双亲委派returnsuper.loadClass(name, resolve);}}// 加载加密的.class文件并解密privatebyte[]loadEncryptClassData(String className)throwsIOException{// 转换类名到文件路径(com.example.User → com/example/User.class)String filePath = encryptClassPath +"/"+ className.replace(".","/")+".class";try(FileInputStream fis =newFileInputStream(filePath);ByteArrayOutputStream bos =newByteArrayOutputStream()){int b;while((b = fis.read())!=-1){// 解密:异或运算(加密时也用同样逻辑) bos.write(b ^KEY);}return bos.toByteArray();}}}publicclassCustomClassLoaderTest{publicstaticvoidmain(String[] args)throwsException{// 加密类文件存储路径(本地目录)String encryptClassPath ="D:/encryptClasses";// 创建自定义类加载器EncryptClassLoader encryptClassLoader =newEncryptClassLoader(encryptClassPath);// 加载加密的io.renren.User类Class<?> userClass = encryptClassLoader.loadClass("io.renren.User");// 反射创建实例并调用方法Object user = userClass.newInstance(); userClass.getMethod("sayHello").invoke(user);// 输出:Hello from encrypted class!// 验证:应用程序类加载器无法加载加密类(需解密)ClassLoader systemClassLoader =ClassLoader.getSystemClassLoader(); systemClassLoader.loadClass("io.renren.User");// 抛ClassNotFoundException}}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:通过反射创建对象。
这个过程非常消耗 CPU 和内存,且启动时间长
AOT方式
Spring Boot AOT 则是在编译阶段(Build Time)就完成了上述大部分工作:
- 源码分析:在构建(Maven/Gradle)时,AOT 插件会分析你的字节码。
- 静态化处理:它会生成大量的 Java 源代码,这些代码直接包含了 Bean 的注册、代理的创建、配置的绑定等逻辑。
- Bean 注册静态化: 不再通过
@ComponentScan扫描包,而是生成一个BeanFactoryInitializationAotProcessor,在启动时直接注册所有已知的 Bean 定义 - 代理提前生成:AOP 代理(如事务)不再使用运行时动态代理(CGLIB/JDK Proxy),而是尽可能在编译期生成代理类
- 资源/配置预绑定:将
@ConfigurationProperties的绑定逻辑生成为直接的 setter 调用,避免运行时使用反射进行属性填充 - 移除不可达代码:GraalVM 会进行严格的“可达性分析”,所有没有被引用到的代码(死代码)都会被直接从最终的二进制文件中移除,这也是体积变小的原因
- Bean 注册静态化: 不再通过
- 原生编译:利用 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) -> 发布就绪事件
protectedvoidonRefresh(){super.onRefresh();try{this.createWebServer();}catch(Throwable ex){thrownewApplicationContextException("Unable to start web server", ex);}}publicWebServergetWebServer(ServletContextInitializer... initializers){Tomcat tomcat =this.createTomcat();this.prepareContext(tomcat.getHost(), initializers);returnthis.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
privatefinalMap<String,Object> singletonObjects =newConcurrentHashMap(256);privatefinalMap<String,Object> earlySingletonObjects =newConcurrentHashMap(16);privatefinalMap<String,ObjectFactory<?>> singletonFactories =newConcurrentHashMap(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 (原生镜像) | ⭐⭐⭐⭐⭐ (最强) 官方推荐,深度集成 | ⭐⭐⭐ (中等) 支持良好,但需手动配置 | ⭐⭐ (一般) 通常不直接支持,需转用 GraalVM |
| JVM 模式下的 AOT (启动加速) | ⭐⭐⭐⭐⭐ (优秀) 容器优化好,启动快 | ⭐⭐⭐⭐ (良好) 标准兼容,稳定 | ⭐⭐⭐ (标准) 取决于具体发行商优化 |
| GraalVM 兼容性 | 原生支持 (Liberica NIK) | 需额外安装 GraalVM | 不包含,需切换工具链 |
| 主要优势 | Spring 团队官方推荐,构建 Native 镜像最省心 | 免费、社区中立、广泛兼容、企业级稳定 | 纯粹的参考实现,无额外封装 |
| 适用场景 | Spring Native 开发、Serverless、追求极致启动速度 | 传统微服务、企业级应用、云原生部署 | 学习研究、对发行版无特殊要求 |