Spring boot 4 搞懂 Spring Boot 核心启动流程

Spring boot 4 搞懂 Spring Boot 核心启动流程

本文根据Spring boot 3/4的源代码,针对Spring Boot 核心启动流程进行了总结梳理。

Spring Boot 3/4 的启动核心在于自动化配置与事件驱动。它通过SpringApplication.run()入口,利用 spring.factoriesAutoConfigurationSPIAOT等加载扩展点。流程历经环境准备、容器刷新(含自动配置与条件化装配)、内嵌Web服务器启动,最后通过 Runner 回调完成就绪,全程由事件发布-监听机制串联。

准备【Run 方法执行前】

即执行SpringApplication的构造函数

  1. 入口推断:通过WebApplicationType.deduceFromClasspath()推断
    • 如果有 Reactive 相关包,推断为 响应式应用
    • 如果没有Servlet相关包,推断为普通应用
    • 其它情况,推断为 WEB应用
  2. 加载工厂配置:利用 SpringFactoriesLoader 机制,读取 META-INF/spring.factories 文件
    • 加载所有的 ApplicationContextInitializer(上下文初始化器)
    • 加载所有的 ApplicationListener(事件监听器)
  3. 推断启动类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) 时,真正的启动流程开始了。这个过程主要包含以下几个核心步骤

  1. 启动监听与环境构建
    • 启动监听器:首先会启动一个 Startup 来记录启动耗时
    • 准备环境:创建 Environment 对象(如 StandardServletEnvironment)。
      • 它会加载 application.propertiesapplication.yml 配置文件。
      • 它会收集 JVM 系统属性、操作系统环境变量等。
      • 这个环境对象后续会被注入到容器中,供 @Value 等注解读取。
  2. 创建并准备应用上下文
    • 创建容器:根据第一阶段推断出的应用类型,通过反射创建具体的 ApplicationContext 实例【WEB 是AnnotationConfigServletWebServerApplicationContext】。
    • 前置处理:将之前准备好的 Environment 关联到容器中,执行 ApplicationContextInitializer 的初始化逻辑。
    • 打印 Banner:如果你配置了自定义的 Banner,它会在日志中打印出来
  3. 刷新容器(核心中的核心):这是 Spring 框架最核心的逻辑,也是 Bean 生命开始的地方。
    • BeanFactory 预准备:配置类加载器、后置处理器等。
    • 注册 BeanPostProcessor:注册拦截 Bean 创建过程的后置处理器。
    • 初始化 MessageSource:为国际化做准备。
    • 初始化 ApplicationEventMulticaster:初始化事件广播器。
    • 调用 onRefresh()在这里,Spring Boot 会启动内嵌的 Web 服务器(Tomcat/Jetty/Undertow)。这是 Web 应用能接收请求的关键。
    • 注册监听器并发布事件:将应用监听器注册到容器中,并广播之前的事件
  4. 自动配置与 Bean 加载
    • 扫描与注册:根据 @ComponentScan 扫描路径,将带有 @Component@Service@Controller 等注解的类解析为 BeanDefinition,注册到 BeanFactory 中。
    • 自动配置生效:这是 Spring Boot 的灵魂。
      • 通过 @EnableAutoConfiguration 导入 AutoConfigurationImportSelector
      • 它再次利用 spring.factories 加载大量的 xxxAutoConfiguration 类。
      • 这些配置类利用 @ConditionalOnXXX 注解,根据类路径下的依赖(比如是否有 Redis 的 jar 包)来决定是否要创建对应的 Bean(比如 RedisTemplate)。这就是“开箱即用”的原理
  5. 启动完成与收尾
    • 执行 Runner:调用所有实现了 CommandLineRunnerApplicationRunner 接口的 Bean 的 run 方法。这通常用于在应用启动后执行一些特定的逻辑(如预热缓存、打印启动完成日志等)。
    • 发布就绪事件:发布 ApplicationReadyEvent 事件,标志着应用已经准备好接收外部请求了。

核心启动流程汇总

阶段关键动作核心组件/概念
1. 推断与加载推断应用类型,加载初始化器和监听器SpringFactoriesLoader, WebApplicationType
2. 环境准备加载配置文件、环境变量,创建 EnvironmentConfigurableEnvironment, PropertySources
3. 容器创建根据类型创建 ApplicationContextApplicationContext
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 为根
  • 通过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@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 启动流程的 准备环境之后、刷新容器之前

触发点

  1. 调用 SpringApplication.run()
  2. 解析注解:Spring 容器开始解析主启动类上的注解
  3. 解析到 @SpringBootApplication 注解时,由于它组合了 @EnableAutoConfiguration,进而触发了自动配置机制

核心加载器:AutoConfigurationImportSelector

加载逻辑的核心执行者。它的调用链路如下:

  • @EnableAutoConfiguration 注解上标注了 @Import(AutoConfigurationImportSelector.class)
  • 当 Spring 解析 @Import 注解时,会调用 AutoConfigurationImportSelector 的 内部 类AutoConfigurationGroupprocess(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.factoriesMETA-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 应用层面的自动化配置

类加载器: 双亲委派机制

解决的问题

  1. 类加载混乱
  2. 核心类库不安全
  3. 资源浪费

双亲委派

双亲委派机制的核心解决方案:规定类加载器加载类时,优先委派给父类加载器,只有父类加载器无法加载时,才由当前类加载器自行加载。其核心价值体现在三点:

  • 保证类的唯一性:同一类仅由一个类加载器加载(通常是最顶层的启动类加载器),避免类冲突;
  • 保护核心类库:JDK 核心类(如java.lang.*)由顶层类加载器加载,自定义类加载器无法加载同名类,防止核心类被篡改;
  • 提升加载效率:避免重复加载,父类加载器已加载的类,子类加载器直接复用,减少内存占用。

层级顺序:启动类加载器BootstrapClassLoader → 扩展类加载器ExtClassLoader → 应用程序类加载器AppClassLoader → 自定义类加载器(自上而下,父加载器在上)

在标准的双亲委派机制下,类加载器的层级是单向的:子加载器可以访问父加载器加载的类,但父加载器无法访问子加载器加载的类

优势

  1. 安全性:保护 JDK 核心类库不被恶意代码替换,防止 “伪核心类” 引发的安全风险(如自定义java.lang.System类篡改系统方法);
  2. 唯一性:确保同一类仅被加载一次,避免类冲突(如不同模块的同名类不会重复加载);
  3. 高效性:缓存机制 + 委派复用,减少重复加载,提升 JVM 运行效率;
  4. 规范性:明确类加载器的职责边界,形成统一的类加载秩序,便于 JVM 管理类的生命周期。

局限性

  1. 灵活性不足:严格的委派机制限制了类加载的灵活性,无法满足 “父类加载器需要加载子类加载器范围的类” 的场景(如 Tomcat 的 Web 应用类加载);
  2. 无法加载异构类:若需要加载不在默认加载范围的类(如加密的.class 文件、网络传输的类),默认类加载器无法实现,需自定义类加载器打破委派;
  3. 热部署支持有限:默认类加载器加载类后,无法卸载已加载的类(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 为例):

  1. 设置:应用程序启动时,main线程的上下文类加载器被设为 AppClassLoader
  2. 调用DriverManager(由 BootstrapClassLoader 加载)需要加载驱动。
  3. 获取DriverManager 不再用自己的加载器去加载,而是通过 Thread.currentThread().getContextClassLoader() 拿到了 AppClassLoader
  4. 加载DriverManager 使用拿到的 AppClassLoader 去加载 com.mysql...Driver
  5. 结果:核心库(父)成功加载了应用库(子)中的类,打破了双亲委派。
加密类加载

默认类加载器仅能加载本地文件系统中的.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)就完成了上述大部分工作:

  1. 源码分析:在构建(Maven/Gradle)时,AOT 插件会分析你的字节码。
  2. 静态化处理:它会生成大量的 Java 源代码,这些代码直接包含了 Bean 的注册、代理的创建、配置的绑定等逻辑。
    1. Bean 注册静态化: 不再通过 @ComponentScan 扫描包,而是生成一个 BeanFactoryInitializationAotProcessor,在启动时直接注册所有已知的 Bean 定义
    2. 代理提前生成:AOP 代理(如事务)不再使用运行时动态代理(CGLIB/JDK Proxy),而是尽可能在编译期生成代理类
    3. 资源/配置预绑定:将 @ConfigurationProperties 的绑定逻辑生成为直接的 setter 调用,避免运行时使用反射进行属性填充
    4. 移除不可达代码:GraalVM 会进行严格的“可达性分析”,所有没有被引用到的代码(死代码)都会被直接从最终的二进制文件中移除,这也是体积变小的原因
  3. 原生编译:利用 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 的设计理念之一就是“约定优于配置”,同时提供了非常灵活的扩展机制ApplicationContextInitializerApplicationListenerCommandLineRunner)就像是 Spring Boot 启动流水线上的三个关键检修站,让你可以在不同的阶段介入并执行自定义逻辑

扩展点的执行顺序是固定的:
ApplicationContextInitializer (初始化上下文) →→ ApplicationListener (监听刷新/环境事件) →→ CommandLineRunner (最后执行业务)

扩展点触发时机 (时间轴)核心作用典型应用场景
ApplicationContextInitializer最早 (容器刷新前)修改环境或上下文动态添加配置属性、注册 BeanFactoryPostProcessor
ApplicationListener贯穿全程 (监听事件)监听生命周期事件环境准备后读取配置、容器刷新后执行逻辑
CommandLineRunner最后 (应用就绪前)执行具体的业务代码数据初始化、缓存预热、健康检查

BeanPostProcessor

这是 Spring 中最强大也最复杂的扩展点之一。它允许你在 Bean 初始化的前后执行自定义逻辑。

  • 核心方法
    • postProcessBeforeInitialization: 在 Bean 的 @PostConstructInitializingBean 之前执行。
    • 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 的专属配置,注意:同时同时激活多个
    1. 命令行参数java -jar myapp.jar --spring.profiles.active=prod
    2. JVM 参数-Dspring.profiles.active=dev
    3. 环境变量SPRING_PROFILES_ACTIVE=prod
    4. 配置文件:在 application.yml 中写死(不推荐用于生产,适合本地开发)。
  • 代码中使用 @Profile
    • 你可以在配置类上使用注解,让某些 Bean 只在特定环境下生效

IOC(Inversion of Control,控制反转)

概念

Spring Boot 的 IOC(Inversion of Control,控制反转) 容器是整个框架的核心基石。简单来说,它就是一个超级工厂,负责创建、配置和管理你项目中所有 Java 对象(在 Spring 里叫 Bean

在 Spring Boot 中,IOC 容器主要由两个核心接口实现:

  • BeanFactory:基础容器,提供基本的 IOC 功能
  • ApplicationContextBeanFactory 的子接口,功能更强大,也是 Spring Boot 默认使用的容器

核心任务只有两步:

  1. 依赖查找/注入:把对象放进容器(存),从容器里拿出对象(取)。
  2. 生命周期管理:控制对象什么时候创建、什么时候销毁、以及中间的加工过程

Bean的生命周期管理

代码可看:ConfigurationClassPostProcessorConfigurationClassParserClassPathBeanDefinitionScannerClassPathScanningCandidateComponentProviderDefaultListableBeanFactory

扫描与注册

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 注入):

  1. 一级缓存:存放完全初始化好的 Bean(singletonObjects)。
  2. 二级缓存:存放早期暴露的 Bean(earlySingletonObjects),此时属性还没填充。
  3. 三级缓存:存放 Bean 工厂对象(singletonFactories)。

三级缓存对应 DefaultSingletonBeanRegistry类中的三个Map

privatefinalMap<String,Object> singletonObjects =newConcurrentHashMap(256);privatefinalMap<String,Object> earlySingletonObjects =newConcurrentHashMap(16);privatefinalMap<String,ObjectFactory<?>> singletonFactories =newConcurrentHashMap(16);

解决流程:

  1. 创建 A,发现需要 B。
  2. 将 A 的工厂放入三级缓存,然后去创建 B。
  3. 创建 B 时,发现需要 A。B 去缓存中找,发现三级缓存里有 A 的工厂,于是通过工厂拿到 A 的早期引用(半成品)。
  4. B 创建完成,放入一级缓存。
  5. 回头继续完善 A 的创建,此时 B 已经有了,A 也就顺利创建完成

JDK版本比较

Spring 团队在官方文档和博客中明确推荐 Liberica JDK 用于 Spring Native 项目
Liberica JDK (NIK)。它是 Spring 官方推荐的“最佳拍档”,能帮你省去大量排查构建错误的时间,是目前体验最好的组合

Liberica 是 Spring 生态的“优等生”,在 AOT 和 Native 场景下有加成;Temurin 是“全能选手”,免费且稳定,适合绝大多数场景;标准 OpenJDK 则更多是作为底层技术的基石存在。

特性Liberica JDKEclipse Temurin标准 OpenJDK (上游)
Spring Native / AOT (原生镜像)⭐⭐⭐⭐⭐ (最强)
官方推荐,深度集成
⭐⭐⭐ (中等)
支持良好,但需手动配置
⭐⭐ (一般)
通常不直接支持,需转用 GraalVM
JVM 模式下的 AOT (启动加速)⭐⭐⭐⭐⭐ (优秀)
容器优化好,启动快
⭐⭐⭐⭐ (良好)
标准兼容,稳定
⭐⭐⭐ (标准)
取决于具体发行商优化
GraalVM 兼容性原生支持 (Liberica NIK)需额外安装 GraalVM不包含,需切换工具链
主要优势Spring 团队官方推荐,构建 Native 镜像最省心免费、社区中立、广泛兼容、企业级稳定纯粹的参考实现,无额外封装
适用场景Spring Native 开发、Serverless、追求极致启动速度传统微服务、企业级应用、云原生部署学习研究、对发行版无特殊要求

Read more

黑马网课springboot3+vue3中大事件项目代码优化(前端篇)

黑马网课springboot3+vue3中大事件项目代码优化(前端篇)

大事件前端代码优化和功能添加详细对比分析 前言 本人在进行黑马网课springboot3加vue3的课程时,在后端中优化了一系列代码,和增加了新的功能,于是对前端代码也进行相应调整,使这些原版项目中没有实现的功能,在优化版项目中实现,以下是我我做出的优化和功能添加对比分析,后端篇优化可以去看我写的这个:黑马网课springboot3+vue3中大事件项目代码优化(后端篇) 详细代码差异对比 1. 用户密码修改功能 优化版项目中的UserResetPassword.vue文件实现了完整的密码修改功能: <script setup>import{ ref }from'vue'import{ userUpdatePasswordService }from'@/api/user.js'import{ ElMessage }from'element-plus'import{ useRouter }from'vue-router'const router

By Ne0inhk
DevUI 组织 2025 年度运营报告:扎根 AtomGit,开源前端再启新程

DevUI 组织 2025 年度运营报告:扎根 AtomGit,开源前端再启新程

在开源浪潮席卷全球的今天,DevUI 始终以「打造企业级前端开源解决方案」为初心,依托 AtomGit 平台的生态优势稳步前行。2025 年,我们聚焦组件迭代、生态共建与社区连接,在技术深耕与开源协作中收获颇丰。现将全年发展足迹与未来规划分享如下,致谢每一位同行者的支持! 一、年度核心成果:数据见证成长 * 项目矩阵持续扩容:新增 MateChat-React、react-devui 等 4 个衍生项目,形成覆盖 Vue、Angular、React 三大框架的全栈组件生态,累计开源仓库达 7 个 * 社区活力显著提升:全年接收 PR 提交 300+,Star 数稳步增长至 3.02k,累计下载量超 46.08k 次,核心项目 MateChat、vue-devui、ng-devui

By Ne0inhk
突破网页数据集获取难题:Web Unlocker API 助力 AI 训练与微调数据集全方位解决方案

突破网页数据集获取难题:Web Unlocker API 助力 AI 训练与微调数据集全方位解决方案

突破网页数据集获取难题:Web Unlocker API 助力 AI 训练与微调数据集全方位解决方案 背景 随着AI技术的飞速发展,诸如DeepSeek R1、千问QWQ32、文小言、元宝等AI大模型迅速崛起。在AI大模型训练和微调、AI知识库建设中,数据集的获取已成为不可或缺的基础。尤其是在面对各式各样的网页数据结构时,将其整理成可用的数据集是一项极具挑战的任务。开发者不仅需要付出大量的开发和人工成本,还需应对复杂的网页数据获取难题。在这种情况下,一款能够自动化解决网页数据获取问题的工具变得尤为重要。 本文将介绍网页解锁器Web Unlocker API、网页抓取Web-Scraper以及搜索引擎结果页SERP API等工具,特别适合中小企业解决商业化网页数据集问题,展示其如何解决AI数据集网页抓取的难题,提供高效、自动化的数据获取解决方案。 什么是Web Unlocker API工具? Web Unlocker API是基于Bright Data的代理基础设施开发的,具备三个关键组件:请求管理、浏览器指纹伪装和内容验证。通过这些功能,它能够自动化处理所有网页解锁操作

By Ne0inhk

下载海康视频插件后,浏览器显示此站点正在尝试打开webcontrol浏览器中间件

已下载并安装海康的【VideoWebPlugin.exe】视频插件后,页面显示弹窗【要打开webcontrol浏览器中间件吗?xxxx://xxxxxx 想打开此应用】且页面仍然没有监控视频 解决步骤: 1、选中弹窗中的【xxxx://xxxxxx】这个地址并复制; 2、打开谷歌浏览器在地址栏输入【chrome://flags】,若是edge浏览器会自动跳转到【edge://flags】,其他浏览器同理; 3、在搜索框中输入【Insecure origins treated as secure】; 4、在输入框粘贴刚复制的【xxxx://xxxxxx】地址,并将【已停用】改为【已启用】,后点击右下角【重新启动】按钮重启浏览器; 5、重启后刷新该页面会在地址栏下方显示弹窗,点击【允许】按钮即可正常显示监控视频; *若点击的是【屏蔽】

By Ne0inhk