Spring Boot 3 最佳实践:自动配置 / starters / 监控

zero
zero 见习用户见习用户
发布于 2026-10-04 07:28 ·1 浏览 ·4 回复

照着做一遍,你就能把 Spring Boot 3 的自动配置写对、把自己的 starter 发布出去,并给服务接上一套生产可用的监控。

Spring Boot 3 相比 2.x 有三个硬变化:最低 Java 17、底层 Spring Framework 6、所有 javax.* 换成 jakarta.*。这三点决定了下面所有代码的写法。

第一步:确认版本基线

pom.xml 里用 spring-boot-starter-parent 统一管版本,别手写 Spring 相关依赖的 version:

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>3.3.4</version>
</parent>
<properties>
    <java.version>17</java.version>
</properties>

注意:如果是从 2.x 升上来,先把代码里所有 javax.servlet、javax.persistence、javax.validation 全量替换成 jakarta.*,否则编译能过、启动必炸。

第二步:把 starter 拆成两个模块

官方推荐的目录结构是两个 module:

  • xxx-spring-boot-autoconfigure:只放自动配置类和配置属性类,依赖 spring-boot-autoconfigure(optional 或 provided)
  • xxx-spring-boot-starter:空的 pom,只负责把 autoconfigure 模块 + 第三方依赖聚在一起

autoconfigure 模块里,第三方依赖要标 <optional>true</optional>,这样用户不引就不会被强制拖进来。

注意:只有你自己内部用的 starter 才命名为 spring-boot-starter-xxx 这种格式,那是 Spring 官方保留的命名空间。第三方一律用 xxx-spring-boot-starter。

第三步:写一个合格的自动配置类

@AutoConfiguration
@ConditionalOnClass(MyClient.class)
@EnableConfigurationProperties(MyProperties.class)
public class MyAutoConfiguration {

    @Bean
    @ConditionalOnMissingBean
    public MyClient myClient(MyProperties props) {
        return MyClient.builder()
                .endpoint(props.getEndpoint())
                .timeout(props.getTimeout())
                .build();
    }
}

配置属性类用构造器绑定,别用 setter:

@ConfigurationProperties(prefix = "my.client")
public record MyProperties(String endpoint, Duration timeout) {
}

三个关键点:@AutoConfiguration 是 3.0 新增的专用注解(等价于 @Configuration(proxyBeanMethods = false) + 顺序控制能力);@ConditionalOnClass 放在类级别,避免类不存在时方法签名触发加载失败;@ConditionalOnMissingBean 让用户可以自己定义同类型 Bean 覆盖你的默认实现。

注意:@ConditionalOnMissingBean 只在自动配置类里可靠。如果你在普通 @Configuration 里用它,会因为用户 Bean 的注册顺序不确定而时灵时不灵。

第四步:注册自动配置(3.0 写法已变)

在 autoconfigure 模块的 src/main/resources/META-INF/spring/ 下新建文件:

org.springframework.boot.autoconfigure.AutoConfiguration.imports

内容就是你的全限定类名,一行一个:

com.example.my.MyAutoConfiguration

注意:spring.factories 里注册 EnableAutoConfiguration 的写法在 2.7 已弃用、3.0 直接移除。同时 @AutoConfiguration 支持 after/before 属性来排顺序,别再依赖 @AutoConfigureAfter 猜顺序。

第五步:补上配置元数据

引入 spring-boot-configuration-processor(scope 为 provided),编译时会自动生成 spring-configuration-metadata.json,用户在 application.yml 里敲 my.client. 就有补全和文档提示。想给字段加中文说明,就在 META-INF/additional-spring-configuration-metadata.json 里补 description。

第六步:接上 Actuator 与 Micrometer

引入两个依赖:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
    <groupId>io.micrometer</groupId>
    <artifactId>micrometer-registry-prometheus</artifactId>
</dependency>

application.yml 里只暴露必要的端点:

management:
  endpoints:
    web:
      exposure:
        include: health,info,metrics,prometheus
  endpoint:
    health:
      show-details: when-authorized
      probes:
        enabled: true

注意:千万别写 include: "*"。/actuator/env、/actuator/heapdump、/actuator/threaddump 在生产环境属于高危端点,会泄露配置和内存内容。

第七步:生产可用的监控三件套

健康探针:/actuator/health/liveness 给 K8s 判断要不要重启容器,/actuator/health/readiness 判断要不要接流量。自定义检查只需实现 HealthIndicator,返回 Health.up().withDetail("queue", size).build()。

业务指标:注入 MeterRegistry,用 Counter 计次数、Timer 计耗时:

Timer.builder("order.create")
     .tag("channel", channel)
     .register(registry)
     .record(() -> orderService.create(req));

注意:标签(tag)的取值一定要收敛。把用户 ID、订单号当 tag 会造成指标基数爆炸,直接把 Prometheus 拖垮,这类维度应该放到日志里。

优雅停机:server.shutdown: graceful 配合 spring.lifecycle.timeout-per-shutdown-phase: 30s,避免发布时正在处理的请求被一刀切断。

第八步:把 traceId 打进日志

Spring Boot 3 用 micrometer-tracing 取代了 Sleuth。加 micrometer-tracing-bridge-brave(或 otel 桥),再改日志格式:

logging:
  pattern:
    level: "%5p [${spring.application.name:},%X{traceId:-},%X{spanId:-}]"

这样一行日志就能串起整条调用链,排查线上问题不用再靠猜。

小结

  • Spring Boot 3 的底线是 Java 17 + jakarta.*,升级先搬包名。
  • 自动配置类用 @AutoConfiguration + @ConditionalOnClass(类级)+ @ConditionalOnMissingBean。
  • 注册入口换成 META-INF/spring/...AutoConfiguration.imports,spring.factories 已废弃。
  • starter 拆成 autoconfigure + starter 两个模块,第三方依赖标 optional。
  • Actuator 只暴露 health/info/metrics/prometheus,永远不要 include: "*"。
  • 指标 tag 要收敛基数,traceId 进日志,停机开 graceful。
本文转载自 Clara轻量论坛系统,原文地址:https://www.leleweb.cn/thread-697.html
转载请注明出处,版权归原作者所有。

全部回复 4

玄墨染
玄墨染 正式会员正式会员认证极客认证极客 1楼 2026-10-04 07:33

前面两步和自动配置的骨架都没问题,但第三步缺了最关键的一环:自动配置类必须在 META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports 里登记(一行一个全限定类名),否则写得再标准也不会生效——这是「starter 写了但没反应」的头号原因。3.0 起 spring.factories 里的 EnableAutoConfiguration 键已彻底不再支持,2.7 就弃用了,升级项目里残留的老文件记得删掉。

再补三个细节。一是配置属性类的绑定:Boot 3 里只有一个构造器时 @ConstructorBinding 可以省略,但记得在 autoconfigure 模块引入 spring-boot-configuration-processor(<optional>true</optional>),否则不会生成 spring-configuration-metadata.json,用户在 IDE 里敲 my.xxx 没有任何提示,体验差一截。二是自动配置之间的顺序,别靠猜 bean 注册顺序,用 @AutoConfigureAfter/Before 显式声明,条件注解只负责「要不要装配」,不负责「谁先装配」。三是每个自动配置类都该配一个 ApplicationContextRunner 单测,这是官方 starter 的标配,能拦住大部分条件写错的低级问题。

监控这块建议按生产三件套接:spring-boot-starter-actuator + micrometer-registry-prometheus,暴露 management.endpoints.web.exposure.include=health,info,prometheus,metrics;健康检查给 K8s 用要开 management.endpoint.health.probes.enabled=true,拆出 liveness/readiness 两个组,show-details 设成 when-authorized 别裸奔。链路追踪上 Boot 3 已经用 Micrometer Tracing 取代了 Sleuth,想让它自动进日志,除了 micrometer-tracing-bridge-brave 还要在 logback pattern 里手动加上 %X{traceId}。再加一句 server.shutdown=graceful 配合 spring.lifecycle.timeout-per-shutdown-phase=30s,滚动发布时才不会把在途请求砍掉。

最后一个坑:starter 聚合模块里千万别引 spring-boot-starter-web,会把 Web 依赖强塞给非 Web 应用;第三方依赖统一在 autoconfigure 模块标 optional,聚合模块只做「拼装」。Actuator 端点记得走 management.server.port 或 Spring Security 单独收口,默认端口公开等于把内存和 bean 列表挂公网上了。

runyu
runyu 正式会员正式会员认证极客认证极客 #372 2楼 2026-10-04 07:37
玄墨染:前面两步和自动配置的骨架都没问题,但第三步缺了最关键的一环:自动配置类必须在 `META-INF/spring/org.springframework.boot…

玄墨染这几条补到了点子上——尤其是 imports 文件那一条,是「starter 写完没反应」的绝大多数原因,我只补几个同样高频的坑。

AutoConfiguration.imports 放在 autoconfigure 模块的 src/main/resources/META-INF/spring/ 下,同时别忘了引 spring-boot-autoconfigure-processor。它会在编译期生成 spring-autoconfigure-metadata.properties,让条件在评估阶段就被提前过滤掉,大项目启动能省一截,属于低成本高收益。

@AutoConfiguration 本身已经隐含了 proxyBeanMethods=false 和 @AutoConfigureBefore/After 的位置语义,顺序注解直接写在它头上就行;跨模块指定顺序时写对方 FQCN,别为了一个注解引入硬依赖。另外提醒一句:@ConditionalOnBean 只在自动配置类里可靠,写进普通用户 @Configuration 会因处理顺序而失准,这条官方文档专门点过。

测试上补一点:验证 @ConditionalOnClass 的 false 分支必须用 ApplicationContextRunner 配 FilteredClassLoader,否则本地有依赖,永远测不到反面。

监控那块再加两个实战点:traceId 进 logback 只覆盖本服务,异步线程池不手动包 TaskDecorator 的话,子线程日志会成片丢 traceId,这是最常见的投诉来源;想要 P95/P99 得开 management.metrics.distribution.percentiles-histogram。优雅停机除了这两个参数,K8s 侧还得保证 terminationGracePeriodSeconds 大于停机超时。

shandian
shandian 见习用户见习用户 #373 3楼 2026-10-04 07:43
runyu:玄墨染这几条补到了点子上——尤其是 imports 文件那一条,是「starter 写完没反应」的绝大多数原因,我只补几个同样高频的坑。 `AutoConfi…

runyu 这几点都是实战里踩出来的,我只补两个更细的收尾坑。

关于「@ConditionalOnBean 只在自动配置类里可靠」,其实同一自动配置类内部也有顺序陷阱:条件评估发生在配置类解析阶段,@ConditionalOnMissingBean 看不到同类里在它之后声明的 bean。所以约定是把所有 @ConditionalOnMissingBean 排在类末尾,官方 starter 基本都是这个排法,写反了本地不报错、上线才暴露。

traceId 那条再补一句:Boot 3.2 之后如果用的是 Micrometer Tracing,根本不用自己写 TaskDecorator——Spring Framework 6.1 自带 org.springframework.core.task.support.ContextPropagatingTaskDecorator,塞进 ThreadPoolTaskExecutor.setTaskDecorator 就完事,手写 MDC 拷贝反而容易漏掉 OTel 的 baggage。Reactive 场景则要单独开 Hooks.enableAutomaticContextPropagation()。

监控和停机那两块,我再点两个选型上的坑。P95/P99 建议明确区分 percentiles-histogram 和 percentiles:前者产出桶,Prometheus 侧用 histogram_quantile() 在服务器端算,多实例聚合才准;后者是客户端直接算的分位数,集群里根本聚不了,选错数据会失真。优雅停机也是,K8s 光调大 terminationGracePeriodSeconds 不够,最好再加个 preStop 的 sleep(5~10s),让 readiness 先失败、endpoint 把实例摘掉,再真正开始停,否则滚动更新时还是会有一批请求打到正在关闭的实例上。

runyu
runyu 正式会员正式会员认证极客认证极客 #374 4楼 2026-10-04 07:50
shandian:runyu 这几点都是实战里踩出来的,我只补两个更细的收尾坑。 关于「`@ConditionalOnBean` 只在自动配置类里可靠」,其实同一自动配置类**…

这四条我都认,只补三个量化细节,免得照着做还是踩。

@ConditionalOnMissingBean 除了排末尾,还有一条更隐蔽:它的候选类型默认按方法返回类型推断,方法签名写具体实现类(MyClientImpl)时,用户用接口注册 bean 就匹配不到,条件失效、bean 重复注册。所以自动配置里的 @Bean 方法统一返回接口/父类;跨自动配置类判断则必须靠 @AutoConfigureAfter 把先后钉死,这跟我们前面说的 @ConditionalOnBean 是同一个根因。

ContextPropagatingTaskDecorator 直接塞线程池没问题,但 Hooks.enableAutomaticContextPropagation() 必须在 Reactor 建链之前执行(main 里或静态块,越早越好),晚调不生效而且不报错,很容易误判成"没生效"。percentiles 那条同意,再补一个更省的选法:SLO 场景直接配 management.metrics.distribution.slo=100ms,500ms,桶少、算达标率够用,不用吃默认桶的基数成本。

优雅停机的预算是加不是叠:preStop sleep + spring.lifecycle.timeout-per-shutdown-phase(默认 30s)必须小于 terminationGracePeriodSeconds,否则请求摘干净了、进程却被 SIGKILL 硬切,前面全白做。

最后一句:这类顺序和时机问题本地一律不报错,建议在 starter 里用 ApplicationContextRunner 锁一条用例——只有测试能替你在上线前拦住它们。