Skip to content

Spring Boot 3.x 内部执行链与生产原理

本章默认读者已理解 Spring IoC。它专门回答 Boot 3 应用从 main 到可接收请求之间发生了什么,以及 Spring Framework 6、Jakarta、自动配置、AOT、Observability 如何接入这条主链。

一、版本边界

体系存量主线现代主线
JavaJDK 8Java 17 或更高受支持版本
Spring Framework5.36.x
Spring Boot2.7.x3.x 精确版本
Servletjavax.servletjakarta.servlet
ORMHibernate 5 常见Hibernate 6 常见
SecuritySecurity 5Security 6
TracingSleuth 常见Micrometer Tracing 主线

“Boot 3.x”不能代替精确版本。RestClient、虚拟线程配置、Observability 细节和依赖版本会随 3.x 小版本演进;项目必须锁定 BOM,并查对应版本官方系统要求。

二、从 main 到服务就绪的完整链路

mermaid
flowchart TD
    A["main调用SpringApplication.run"] --> B["推断应用类型并加载启动扩展"]
    B --> C["准备Environment和配置属性源"]
    C --> D["创建ApplicationContext"]
    D --> E["加载主配置与自动配置候选"]
    E --> F["refresh注册后处理器并创建Bean"]
    F --> G["创建内嵌Servlet容器"]
    G --> H["执行Runner并发布Ready事件"]
    H --> I["Readiness允许接收流量"]

关键认识:

  1. SpringApplication 是启动编排器,不替代 IoC 容器。
  2. 自动配置注册的是 BeanDefinition,真正实例化仍发生在 refresh() 主链。
  3. 端口监听不等于业务就绪。数据库迁移、缓存预热、Runner 或外部依赖检查仍可能未完成。
  4. Kubernetes Readiness 应表达“当前是否能接流量”,Liveness 只表达“进程是否需要重启”,两者混用会制造重启风暴。

三、Boot 3 自动配置内部链

Boot 3 通过:

text
META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports

列出候选自动配置。随后条件系统按 classpath、属性、应用类型、BeanDefinition 等证据决定是否匹配。

mermaid
flowchart TD
    A["EnableAutoConfiguration触发导入"] --> B["读取AutoConfiguration.imports"]
    B --> C["去重、排除并排序候选"]
    C --> D["解析Conditional条件"]
    D --> E["按评估结果分类处理候选"]
    E --> F["匹配项注册定义<br/>未匹配项记录原因"]
    F --> G["refresh阶段创建已注册的Bean"]

图中把两种分类结果合并进一个节点是为了窄屏阅读:每个候选类要么注册 BeanDefinition,要么只在 ConditionEvaluationReport 中留下未匹配原因;只有已注册定义会进入后续 Bean 创建。

条件为什么必须在合适阶段判断

@ConditionalOnClass 可以根据字节码元数据判断 classpath;@ConditionalOnMissingBean 依赖当前已经注册的 BeanDefinition。若自定义自动配置顺序错误,可能在用户 Bean 尚未可见时错误兜底。应使用 @AutoConfiguration(before/after=...) 或相应排序注解表达依赖,而不是依赖文件偶然顺序。

一个 Boot 3 Starter 最小实现

java
@AutoConfiguration
@ConditionalOnClass(CollectClient.class)
@EnableConfigurationProperties(CollectProperties.class)
public class CollectAutoConfiguration {

    @Bean
    @ConditionalOnMissingBean
    CollectClient collectClient(CollectProperties properties) {
        return new HttpCollectClient(properties.endpoint(), properties.timeout());
    }
}
java
@ConfigurationProperties("collect.client")
public record CollectProperties(URI endpoint, Duration timeout) {
    public CollectProperties {
        if (timeout == null) timeout = Duration.ofSeconds(2);
    }
}

注册文件:

text
com.example.collect.autoconfigure.CollectAutoConfiguration

用户自定义 CollectClient 后,@ConditionalOnMissingBean 让框架默认实现退让。若没有退让条件,项目会出现 Bean 冲突,或者用户以为覆盖成功但实际注入了框架实现。

四、Jakarta Web 请求链

mermaid
flowchart TD
    A["客户端连接Tomcat 10.1"] --> B["jakarta.servlet.Filter链"]
    B --> C["DispatcherServlet"]
    C --> D["HandlerMapping查找Controller"]
    D --> E["参数解析、类型转换和校验"]
    E --> F["AOP代理进入Service事务"]
    F --> G["Repository访问数据库"]
    G --> H["HttpMessageConverter序列化"]
    H --> I["Filter提交响应"]

javax.servlet.Filterjakarta.servlet.Filter 在 JVM 看来是不同类型。旧 Jar 即使源码逻辑完全正确,只要常量池仍引用 javax,也不能注册进 Jakarta 容器。迁移必须升级或重新编译第三方 Filter、Validator、JPA Entity 和公司 Starter。

五、Hibernate 6 迁移为什么可能改变 SQL 行为

Boot 3 常见持久化主线进入 Hibernate 6。除了 import 改为 jakarta.persistence,还要回归:

  • 方言自动识别与自定义 Dialect。
  • HQL/JPQL 语义校验。
  • 自定义类型映射和序列类型。
  • 分页、批量写入、懒加载和生成 SQL。
  • 数据库驱动、时间类型、JSON 类型与枚举映射。

编译通过只能证明 API 能调用,不能证明生成 SQL、锁语义和执行计划不变。商业迁移必须保存关键 SQL 和 EXPLAIN 基线,回归高频查询与批处理。

六、Observability 怎样贯穿一次调用

Micrometer Observation 是统一观测门面,一次操作可以同时驱动 Timer、Span 和上下文传播。它不是把日志、指标、Trace 存在应用内;应用负责埋点和导出,后端系统负责存储与检索。

mermaid
flowchart TD
    A["HTTP入口创建Observation"] --> B["提取traceparent并建立Span"]
    B --> C["业务日志关联traceId"]
    C --> D["HTTP或消息客户端注入上下文"]
    D --> E["下游继续同一Trace"]
    E --> F["结束Observation并记录Timer"]
    F --> G["Exporter发送到Collector"]

指标标签必须低基数。URI 模板、方法、状态码适合标签;订单号、用户 ID、原始异常文本不适合,否则每个值都形成新时间序列,监控后端内存和费用会快速膨胀。

七、AOT 与 Native Image 的真实原理

JVM 模式允许运行时扫描、反射和动态代理;Native Image 做闭世界分析,需要在构建期尽量知道可达类型。Spring AOT 会分析 BeanDefinition 并生成初始化代码,Runtime Hints 则告诉原生构建器哪些反射、资源、序列化和代理必须保留。

自定义反射示例:

java
@ImportRuntimeHints(OrderRuntimeHints.class)
class OrderNativeConfiguration {}

final class OrderRuntimeHints implements RuntimeHintsRegistrar {
    @Override
    public void registerHints(RuntimeHints hints, ClassLoader classLoader) {
        hints.reflection().registerType(OrderMessage.class,
                MemberCategory.INVOKE_DECLARED_CONSTRUCTORS,
                MemberCategory.INVOKE_PUBLIC_METHODS);
        hints.resources().registerPattern("templates/order-*.json");
    }
}

缺少 Hint 时,JVM 模式可能正常,原生运行时才出现类、构造器或资源找不到。Native 的收益通常是启动快、空闲内存较低;代价是构建慢、动态能力受限、诊断方式不同。长期运行且 JIT 已充分预热的高吞吐服务必须实测,不应只因“云原生”就切换。

八、Boot 3 生产配置示例

yaml
server:
  shutdown: graceful
spring:
  lifecycle:
    timeout-per-shutdown-phase: 30s
management:
  endpoints:
    web:
      exposure:
        include: health,info,metrics,prometheus
  endpoint:
    health:
      probes:
        enabled: true
      show-details: never

优雅停机链路:先从服务发现或负载均衡摘除实例,等待传播和存量连接排空,再停止接收新请求,最后等待正在执行的请求在上限内结束。只设置 graceful 而不给 Kubernetes 足够的 terminationGracePeriodSeconds,进程仍可能被强杀。

Actuator 暴露必须遵循最小权限。envconfigprops、线程转储等端点可能泄露配置、路径或业务信息,不能直接暴露公网。

九、Java 21 虚拟线程不是 Java 17 能力

某些较新的 Boot 3.x 版本可配置虚拟线程,但前提是运行 Java 21,并且精确 Boot 版本支持。虚拟线程降低阻塞式任务对应平台线程的成本,不会增加数据库连接、下游配额或 CPU。

mermaid
flowchart TD
    A["大量阻塞请求"] --> B["每个请求使用虚拟线程"]
    B --> C["阻塞时卸载Carrier线程"]
    C --> D["Carrier执行其他可运行任务"]
    D --> E["IO就绪后恢复虚拟线程"]
    E --> F["仍需连接池和并发上限"]

若把 Tomcat 并发放大到十万,而数据库池仍只有 50,结果只是大量请求等待连接并最终超时。容量治理仍需 Deadline、Bulkhead、限流和连接池监控。

十、Boot 3 故障取证 Runbook

现象第一份证据重点判断
JVM 直接无法启动完整控制台和 java -versionclass 版本、失效 JVM 参数
javax 类缺失第一处 Caused by、依赖树哪个旧 Jar 仍引用旧规范
NoSuchMethodError调用方与被调用类来源 JarBOM 被覆盖、运行期多版本
自动配置未生效--debug 条件报告classpath、属性、Bean、应用类型条件
接口 404Mapping 日志、Context PathController 扫描、Security、Servlet 映射
数据库行为变化SQL、绑定参数、EXPLAINHibernate 6 映射和方言
Trace 断裂请求头与两端 Span传播格式、异步上下文、采样
停机丢请求Pod 事件、LB Endpoint、请求日志摘流传播与终止宽限期

依赖冲突定位:

bash
mvn dependency:tree -Dverbose
mvn help:effective-pom
java -verbose:class -jar app.jar

最后一条日志量很大,只在隔离环境或使用精确过滤时开启。

十一、关联学习