当前位置: 首页 > news >正文

如何自定义一个Spring Boot Starter

如何自定义一个 Spring Boot Starter?从零封装一个自己的自动配置

本文基于 Spring Boot 2.7+ / 3.x,核心原理通用


一、从一个真实场景说起

最近咱们团队在做内部工具链建设,好几个项目都要集成统一的日志追踪功能——每个请求进来,自动在 MDC 里塞一个traceId,然后打印到日志里。

我一开始的做法很原始:每个项目 copy 一份TraceFilter+TraceInterceptor,再 copy 一份配置类。 copy 到第三个项目的时候,我忍不住了:

“这玩意儿能不能像spring-boot-starter-web一样,加个依赖就自动生效?”

能。这就是Spring Boot Starter要干的事。

说白了,Starter 就是 Spring Boot 的**"即插即用"插件机制**。今天咱们就从一个具体需求出发,手把手封装一个自己的 Starter。


二、Starter 到底是什么?先搞清楚原理

2.1 一句话解释

Starter 本质上是一个普通的 Maven/Gradle 模块,核心就做两件事:

  1. 引入你需要的依赖(比如你的工具类、第三方 SDK)
  2. 自动配置好这些组件(通过@Configuration+ 条件注解)

用户只需要在pom.xml里加一行:

<dependency><groupId>com.example</groupId><artifactId>my-trace-spring-boot-starter</artifactId><version>1.0.0</version></dependency>

然后啥也不用干,功能就自动生效了。这就是“约定优于配置”的体现。

2.2 自动配置的底层机制

Spring Boot 启动时,会扫描META-INF/spring.factories(Boot 2.7 之前)或META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports(Boot 2.7+)文件,里面列的类会被自动加载为配置类。

┌─────────────────┐ │ 应用启动 │ │ @SpringBootApp │ └────────┬────────┘ │ ▼ ┌─────────────────────────────┐ │ 读取 META-INF/spring/*.imports │ │ 找到所有 AutoConfiguration │ └────────┬────────────────────┘ │ ▼ ┌─────────────────────────────┐ │ 条件判断(@ConditionalOnXxx) │ │ 满足条件才生效 │ └────────┬────────────────────┘ │ ▼ ┌─────────────────────────────┐ │ 读取 application.yml 配置 │ │ @EnableConfigurationProperties│ └────────┬────────────────────┘ │ ▼ ┌─────────────────────────────┐ │ 注册 Bean 到 Spring 容器 │ │ 功能生效! │ └─────────────────────────────┘

你看,整个流程就是:发现配置 → 条件筛选 → 读取配置 → 注册 Bean。咱们自定义 Starter,就是在这些环节里"填空"。


三、实战:封装一个 TraceId Starter

咱们要做一个my-trace-spring-boot-starter,功能很简单:

  • 自动给每个 HTTP 请求生成traceId
  • traceId放到 MDC 里,日志就能打印出来
  • 支持通过application.yml配置是否开启、以及 traceId 的请求头名称

3.1 项目结构

my-trace-spring-boot-starter/ ├── pom.xml └── src/ └── main/ ├── java/ │ └── com/example/trace/ │ ├── MyTraceAutoConfiguration.java # 自动配置类(核心) │ ├── TraceProperties.java # 配置属性类 │ ├── TraceFilter.java # 实际功能:过滤器 │ └── TraceContext.java # TraceId 上下文工具 └── resources/ └── META-INF/ └── spring/ └── org.springframework.boot.autoconfigure.AutoConfiguration.imports

3.2 第一步:创建 Maven 模块,引入关键依赖

pom.xml里最重要的是spring-boot-autoconfigurespring-boot-configuration-processor

<?xml version="1.0" encoding="UTF-8"?><project><modelVersion>4.0.0</modelVersion><!-- 注意:Starter 本身不需要 parent 是 spring-boot-starter-parent --><!-- 但通常我们会定义 spring-boot.version 统一管理 --><groupId>com.example</groupId><artifactId>my-trace-spring-boot-starter</artifactId><version>1.0.0-SNAPSHOT</version><packaging>jar</packaging><properties><java.version>17</java.version><spring-boot.version>3.2.0</spring-boot.version></properties><dependencies><!-- 核心:自动配置支持 --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-autoconfigure</artifactId><version>${spring-boot.version}</version></dependency><!-- 可选:配置属性提示(写 yml 时有自动补全) --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-configuration-processor</artifactId><version>${spring-boot.version}</version><optional>true</optional></dependency><!-- 功能依赖:我们需要 Servlet 的 Filter --><dependency><groupId>jakarta.servlet</groupId><artifactId>jakarta.servlet-api</artifactId><version>6.0.0</version><scope>provided</scope><!-- 由使用方提供 --></dependency><!-- 日志:MDC 需要 slf4j --><dependency><groupId>org.slf4j</groupId><artifactId>slf4j-api</artifactId><version>2.0.9</version><scope>provided</scope></dependency></dependencies></project>

关键点:

  • spring-boot-autoconfigure是 Starter 的"灵魂",没有它就没有自动配置能力
  • spring-boot-configuration-processor是"贴心小助手",编译时生成配置元数据,写application.yml时有 IDE 提示
  • Servlet 和日志依赖标记为provided,因为使用方项目里肯定有

3.3 第二步:定义配置属性类

用户可能想自定义 traceId 的请求头名称,或者临时关闭这个功能。咱们用@ConfigurationProperties来接收这些配置:

packagecom.example.trace;importorg.springframework.boot.context.properties.ConfigurationProperties;@ConfigurationProperties(prefix="my.trace")publicclassTraceProperties{// 是否开启,默认 trueprivatebooleanenabled=true;// traceId 在 HTTP Header 中的名称privateStringheaderName="X-Trace-Id";// traceId 在日志 MDC 中的 keyprivateStringmdcKey="traceId";// getter / setter 省略...publicbooleanisEnabled(){returnenabled;}publicvoidsetEnabled(booleanenabled){this.enabled=enabled;}publicStringgetHeaderName(){returnheaderName;}publicvoidsetHeaderName(StringheaderName){this.headerName=headerName;}publicStringgetMdcKey(){returnmdcKey;}publicvoidsetMdcKey(StringmdcKey){this.mdcKey=mdcKey;}}

这样用户在application.yml里就能这样配:

my:trace:enabled:trueheader-name:X-Request-Idmdc-key:requestId

3.4 第三步:写实际功能代码

TraceContext —— 生成和管理 traceId
packagecom.example.trace;importorg.slf4j.MDC;importjava.util.UUID;publicclassTraceContext{// 生成一个简化的 traceId:去掉 UUID 里的横杠,取前 16 位publicstaticStringgenerateTraceId(){returnUUID.randomUUID().toString().replace("-","").substring(0,16);}// 放入 MDCpublicstaticvoidput(Stringkey,StringtraceId){MDC.put(key,traceId);}// 清理 MDCpublicstaticvoidclear(Stringkey){MDC.remove(key);}// 获取当前 traceIdpublicstaticStringget(Stringkey){returnMDC.get(key);}}
TraceFilter —— 拦截请求,处理 traceId
packagecom.example.trace;importjakarta.servlet.*;importjakarta.servlet.http.HttpServletRequest;importjava.io.IOException;publicclassTraceFilterimplementsFilter{privatefinalTracePropertiesproperties;publicTraceFilter(TracePropertiesproperties){this.properties=properties;}@OverridepublicvoiddoFilter(ServletRequestrequest,ServletResponseresponse,FilterChainchain)throwsIOException,ServletException{HttpServletRequesthttpRequest=(HttpServletRequest)request;// 1. 优先从请求头里取(上游服务传过来的)StringtraceId=httpRequest.getHeader(properties.getHeaderName());// 2. 没有就生成一个if(traceId==null||traceId.isEmpty()){traceId=TraceContext.generateTraceId();}try{// 3. 放入 MDC,日志就能打印了TraceContext.put(properties.getMdcKey(),traceId);// 4. 继续处理请求chain.doFilter(request,response);}finally{// 5. 请求结束,清理 MDC,防止线程复用时污染TraceContext.clear(properties.getMdcKey());}}}

注意finally里的清理操作!这是踩过坑的教训。Web 容器用线程池复用线程,如果不清理 MDC,下一个请求可能拿到上一个请求的 traceId,那就乱套了。

3.5 第四步:编写自动配置类(核心中的核心)

packagecom.example.trace;importorg.springframework.boot.autoconfigure.AutoConfiguration;importorg.springframework.boot.autoconfigure.condition.ConditionalOnProperty;importorg.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;importorg.springframework.boot.context.properties.EnableConfigurationProperties;importorg.springframework.boot.web.servlet.FilterRegistrationBean;importorg.springframework.context.annotation.Bean;@AutoConfiguration@ConditionalOnWebApplication(type=ConditionalOnWebApplication.Type.SERVLET)@EnableConfigurationProperties(TraceProperties.class)@ConditionalOnProperty(prefix="my.trace",name="enabled",havingValue="true",matchIfMissing=true)publicclassMyTraceAutoConfiguration{@BeanpublicTraceFiltertraceFilter(TracePropertiesproperties){returnnewTraceFilter(properties);}@BeanpublicFilterRegistrationBean<TraceFilter>traceFilterRegistration(TraceFiltertraceFilter){FilterRegistrationBean<TraceFilter>registration=newFilterRegistrationBean<>();registration.setFilter(traceFilter);registration.addUrlPatterns("/*");registration.setOrder(Ordered.HIGHEST_PRECEDENCE);// 最早执行registration.setName("traceFilter");returnregistration;}}

来,咱们逐个拆解这些注解:

注解作用
@AutoConfiguration标记这是一个自动配置类(Spring Boot 2.7+ 推荐,替代@Configuration
@ConditionalOnWebApplication只在 Web 应用(Servlet 环境)下生效,非 Web 项目不加载
@EnableConfigurationPropertiesTraceProperties生效,能读取application.yml
@ConditionalOnProperty根据配置决定是否生效。matchIfMissing = true表示没配默认开启

为什么要用FilterRegistrationBean而不是直接@Bean返回 Filter?

因为直接@Bean返回 Filter,Spring Boot 会自动注册,但你控制不了顺序和 URL 匹配。用FilterRegistrationBean可以更精细地控制:

  • setOrder()控制过滤器顺序
  • addUrlPatterns()控制拦截路径
  • setName()给过滤器起个名字

3.6 第五步:注册自动配置

src/main/resources/META-INF/spring/下创建文件:

文件名(一字不能差):

org.springframework.boot.autoconfigure.AutoConfiguration.imports

内容:

com.example.trace.MyTraceAutoConfiguration

就这么简单,一行类全限定名。Spring Boot 启动时会读取这个文件,把里面的类都当成自动配置类去处理。

历史包袱:Spring Boot 2.7 之前用的是META-INF/spring.factories,格式是org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ xxx。2.7 之后推荐用新的imports文件,更轻量。如果你要兼容老版本,两个都写上也没问题。


四、验证:在项目中使用

4.1 安装到本地 Maven 仓库

cdmy-trace-spring-boot-starter mvn cleaninstall

4.2 在业务项目里引入

<dependency><groupId>com.example</groupId><artifactId>my-trace-spring-boot-starter</artifactId><version>1.0.0-SNAPSHOT</version></dependency>

4.3 配置日志格式,打印 traceId

logback-spring.xml里:

<appendername="CONSOLE"class="ch.qos.logback.core.ConsoleAppender"><encoder><!-- %X{traceId} 就是 MDC 里的值 --><pattern>%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %X{traceId} %logger{36} - %msg%n</pattern></encoder></appender>

4.4 启动项目,看效果

发一个请求:

curlhttp://localhost:8080/hello

日志输出:

2024-01-15 10:23:45 [http-nio-8080-exec-1] INFO a3f7b2c8d9e1f5a2 com.example.demo.HelloController - 收到请求

看到a3f7b2c8d9e1f5a2这个 traceId 了吗?自动生成的!如果你带请求头:

curl-H"X-Trace-Id: my-custom-id"http://localhost:8080/hello

日志里就会打印my-custom-id,实现了全链路追踪的基础能力。


五、进阶:让你的 Starter 更专业

5.1 添加配置提示元数据

src/main/resources/META-INF/下创建additional-spring-configuration-metadata.json

{"properties":[{"name":"my.trace.enabled","type":"java.lang.Boolean","description":"是否开启 TraceId 自动追踪","defaultValue":true},{"name":"my.trace.header-name","type":"java.lang.String","description":"HTTP 请求头中 traceId 的字段名","defaultValue":"X-Trace-Id"}]}

这样用户在 IDEA 里写application.yml时,会有自动补全和提示:

my:trace:enabled:true# ← 有提示:是否开启 TraceId 自动追踪

5.2 条件注解大全(按需取用)

Spring Boot 提供了一堆条件注解,让你的 Starter 更"智能":

// 类路径里有某个类时才生效@ConditionalOnClass(name="com.fasterxml.jackson.databind.ObjectMapper")// 类路径里**没有**某个类时才生效@ConditionalOnMissingClass("org.apache.commons.logging.Log")// 容器里有某个 Bean 时才生效@ConditionalOnBean(name="dataSource")// 容器里**没有**某个 Bean 时才生效(避免重复注册)@ConditionalOnMissingBean(TraceFilter.class)// 配置了某个属性时才生效@ConditionalOnProperty(prefix="my.trace",name="enabled",havingValue="true")// 只在特定 Spring Profile 下生效@ConditionalOnProfile("dev")

最佳实践:注册功能 Bean 时,用@ConditionalOnMissingBean,让用户可以自定义覆盖:

@Bean@ConditionalOnMissingBean// 用户自己定义了 TraceFilter 就用用户的publicTraceFiltertraceFilter(TracePropertiesproperties){returnnewTraceFilter(properties);}

5.3 多环境适配(Spring Boot 2.x vs 3.x)

Spring Boot 3.x 用的是jakarta.servlet包名,而 2.x 用的是javax.servlet。如果你的 Starter 要兼容两个版本,可以用条件注解:

// Jakarta 版本(Boot 3.x)@AutoConfiguration@ConditionalOnClass(jakarta.servlet.Filter.class)publicclassJakartaTraceAutoConfiguration{...}// Javax 版本(Boot 2.x)@AutoConfiguration@ConditionalOnClass(javax.servlet.Filter.class)publicclassJavaxTraceAutoConfiguration{...}

或者更简单的做法:分两个分支维护,主版本用 Jakarta。


六、常见坑点总结

原因解决方案
Starter 引入后没生效AutoConfiguration.imports文件路径/名称写错严格检查路径:META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
配置类没读取到 yml没加@EnableConfigurationProperties加上
Bean 重复注册业务项目里也定义了同名 Bean@ConditionalOnMissingBean
非 Web 项目报错Filter 依赖 Servlet 环境@ConditionalOnWebApplication
线程池里 traceId 错乱MDC 没清理finally里调用MDC.clear()
配置提示不生效spring-boot-configuration-processor没加或没重新编译加依赖 +mvn compile

七、一句话总结

自定义 Starter 就三步:写功能 → 加条件 → 注册配置。核心思路是把"重复的配置和初始化"封装起来,让使用方"一行依赖,开箱即用"。

说白了,Starter 就是 Spring Boot 版的"懒人包"。你封装得越完善,团队其他兄弟用起来越爽。


你在项目里封装过 Starter 吗?遇到过什么奇葩的坑?欢迎在评论区交流!

http://www.jsqmd.com/news/750940/

相关文章:

  • C++27模块调试黑盒破解:GDB 14+ LTO-aware调试流、模块符号映射表逆向工具链首次公开
  • 解锁Windows RT远程桌面:RDP Wrapper Library终极解决方案
  • 告别裸机GUI:在IMX6ULL的Linux系统上为你的产品快速集成LVGL界面库
  • 从微内核到无限扩展:下一代操作系统架构深度解析与实现路径
  • 如何通过3个实战步骤掌握Photon光影包:从安装到高级定制
  • Auto_Simulated_Universe快速指南:5分钟搞定崩坏星穹铁道模拟宇宙自动化
  • DSGE模型宝库:40+宏观经济模型一站式解决方案
  • 如何快速掌握ComfyUI-Impact-Pack:10个核心技巧解锁AI图像增强的终极能力
  • 为什么你的网络调试总是不顺利?Fiddler中文版5大实用技巧帮你解决
  • 植物大战僵尸终极修改器:PVZ Toolkit完整指南
  • GD32F103跑108MHz后串口乱码?手把手教你修改STM32标准库RCC配置
  • 如何实现Claude Code多设备配置同步:开发环境一致性的终极指南
  • 告别显存焦虑:用Qwen-VL-Chat-Int4在Ubuntu上低成本玩转AI识图(附完整依赖清单)
  • 远程桌面复制粘贴失灵?别急着重装,先试试重启这个隐藏的Windows进程
  • 不只是画图:用Design Entry CIS高效管理元器件位号的实战技巧(附批量修改与排序方法)
  • 海南大学考研辅导班推荐:排名深度评测与选哪家分析 - michalwang
  • CVPR 2022 SCI框架实战:5分钟为YOLO目标检测模型集成低光增强模块
  • 如何在5分钟内完成手机号码精准定位:免费工具终极指南
  • ComfyUI-WanVideoWrapper:突破1025帧长视频生成的3大显存优化技术实战指南
  • 从Target预测孕妇到你的推荐系统:用4R框架设计更‘懂人心’的算法策略
  • Tasmota设备与MQTT通信实战:从主题订阅到双向控制,一个案例讲透数据流
  • 终极指南:如何从多序列比对中快速提取SNP位点
  • 北京舞蹈学院考研辅导班推荐:排名深度评测与选哪家分析 - michalwang
  • 基于Vedic数学的轻量级说话头生成技术解析
  • Obsidian Excel插件终极指南:在笔记中无缝创建和嵌入专业电子表格
  • 终极指南:如何用Firmware Extractor一键提取20+种Android固件格式
  • DSGE模型集合终极指南:40+宏观经济模型一键运行实战教程
  • Translumo:3分钟掌握高效屏幕实时翻译,游戏视频无障碍体验完整指南
  • 从Rudin到卓里奇:给数学系高年级生的5本硬核分析教材深度横评(附学习路线)
  • 不止于合规:用ISO 28000:2022框架,打造你的供应链安全‘韧性护城河’