尧图网络 高端网站定制 · 原创设计
免费咨询热线
400-888-6620
免费获取方案
SpringBoot自动配置原理与手写自定义Starter实战
SpringBoot 用久了你真的会忍不住好奇为什么引入一个spring-boot-starter-data-redis啥都没配RedisTemplate就躺在容器里等着你用为什么application.yml里写几行配置那些组件就跟变魔术一样自动注册好了我之前带团队做微服务改造的时候被新人问过无数次这些问题。其实说白了SpringBoot 的自动配置没有那么多玄学核心就是EnableAutoConfiguration加一堆ConditionalOnXxx的组合判断。这篇文章我准备把这块原理掰开揉碎讲清楚然后再带你从零手写一个自定义 Starter把这套机制彻底吃透。不管你是刚接触 SpringBoot 的初学者还是写了好几年业务代码想弄明白底层逻辑的老手这篇都值得你花二十分钟读完。1. 自动配置原理到底是谁在帮你“自动”装配想知道自定义 Starter 怎么写先得搞清楚 SpringBoot 启动时那套自动化装配流程是怎么转起来的。很多同学看源码总是盯着SpringApplication.run()不放其实那里面分支太多真正和自动配置强相关的就是其中一小段调用链。1.1 从启动注解一路追到自动配置类我们写的启动类上都有一个SpringBootApplication它其实是个组合注解内部包含了SpringBootConfiguration、EnableAutoConfiguration和ComponentScan。前两个好理解一个表示这是配置类一个启用自动配置第三个是包扫描。真正让自动配置活起来的是EnableAutoConfiguration。EnableAutoConfiguration内部通过Import(AutoConfigurationImportSelector.class)引入了一个选择器。这个AutoConfigurationImportSelector是整个自动配置机制的总调度员它的核心方法是getCandidateConfigurations()做的事情很单一从META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports文件里读取所有自动配置类的全限定名。注意这里有个版本细节SpringBoot 2.7 之前用的是META-INF/spring.factorieskey 是org.springframework.boot.autoconfigure.EnableAutoConfiguration。2.7 开始逐渐迁移到独立的AutoConfiguration.imports文件3.0 之后彻底移除了 spring.factories 里自动配置的支持。这个坑后面写自定义 Starter 时必须注意不然在 SpringBoot 3.x 上启动直接不生效。读取到这些候选配置类之后选择器会做两件重要的事先通过ConditionalOnXxx一堆条件注解进行过滤把不满足条件的配置类剔除掉然后经过AutoConfigureBefore、AutoConfigureAfter、AutoConfigureOrder调整排序最后再交给容器做真正的 Bean 注册。1.2 条件注解就是自动配置的“开关”自动配置看起来神奇本质就是大量条件注解的组合判断。SpringBoot 维护了一批配置类每个配置类上面挂满了条件条件满足就装配不满足就跳过全程对你透明。常见的条件注解有这么几个ConditionalOnClass当 classpath 下存在指定类时才生效。ConditionalOnMissingBean当容器中没有指定 Bean 时才创建。ConditionalOnProperty当配置文件中存在指定配置项时才生效。ConditionalOnWebApplication当应用是 Web 环境时才生效。ConditionalOnExpression当 SpEL 表达式计算结果为 true 时才生效。拿 Redis 的自动配置来举例子RedisAutoConfiguration上面挂着ConditionalOnClass(RedisOperations.class)也就是说只有你在 pom 里引入了 redis 的客户端依赖RedisOperations这个类出现在 classpath 里这个配置类才有资格被加载。接着类里面定义RedisTemplate的方法上又挂着ConditionalOnMissingBean(name redisTemplate)意思是如果你自己已经定义了一个RedisTemplate那自动配置的就别覆盖了以你手动定义的优先。这个设计思路非常值得学习自动配置提供的是“默认值”和“兜底方案”一旦发现开发者有自定义行为立刻让路。这样的好处是既保证了开箱即用又不剥夺开发者的控制权。1.3 自动配置与普通配置的优先级关系很多人以为自动配置优先级最高其实恰恰相反。自动配置的ConditionalOnMissingBean决定了它优先级最低避免覆盖用户显式定义的 Bean。SpringBoot 在设计上刻意把自动配置类放在所有用户自定义配置之后处理保证用户配置能够覆盖默认配置。如果去看AutoConfigurationSorter的源码会发现它除了处理AutoConfigureBefore/After之外还会把自动配置类的排序放在Configuration用户配置之后。这种“默认最低优先级”的哲学贯穿整个 SpringBoot 生命周期写自定义 Starter 的时候也要延续这个思路千万别写一个固有 Bean 把用户的配置顶掉。2. 手写自定义 Starter从需求设计到最终产物原理看完了手痒的人这时候就该自己动手写一个了。我拿一个生产环境里特别常见的需求来做例子封装一个通用的短信发送 Starter。需求很简单——引入依赖之后只要在配置文件里填好 accessKey 和 secretKey注入一个SmsSender就能发短信企业内部的多个项目都能直接用。2.1 自定义 Starter 的标准项目结构先规划一下代码仓库的结构。自定义 Starter 跟普通业务模块有一个本质区别它是一个被其他项目引入的依赖库不是一个独立运行的应用。所以结构上要干净不能带启动类更不能有spring-boot-maven-plugin的 repackage 配置否则打出来的 jar 不能作为普通依赖被引用。分享一下我习惯的结构sms-spring-boot-starter/ ├── pom.xml └── src/main/java/ └── com/cxk/sms/ ├── SmsAutoConfiguration.java ├── SmsProperties.java ├── SmsSender.java ├── SmsSenderImpl.java └── condition/ └── OnSmsEnabledCondition.javapom 里面除了继承父工程之外必须加上spring-boot-autoconfigure和spring-boot-configuration-processor这两个依赖。前者提供自动配置相关的注解和工具类后者用来生成配置元数据这样使用方在 application.yml 里写配置时能有提示。这里有个容易踩的坑很多人为了省事直接在 pom 里依赖spring-boot-starter这会把一堆用不到的传递依赖带进使用方项目里轻则增大体积重则版本冲突。正确做法是依赖精确的最小集合缺什么补什么。2.2 定义配置属性类SmsProperties配置属性类是 Starter 和使用方之间的契约决定了你暴露哪些配置项给使用者。这个类需要用ConfigurationProperties注解标记并指定一个前缀。ConfigurationProperties(prefix sms) public class SmsProperties { /** * 是否启用短信发送 */ private boolean enabled true; /** * 服务商类型目前支持 aliyun / tencent */ private String provider aliyun; /** * Access Key */ private String accessKey; /** * Secret Key */ private String secretKey; /** * 默认签名 */ private String signName; /** * 连接超时时间单位毫秒 */ private int connectTimeout 3000; // getters and setters 省略 }注意这里给每个字段都写了默认值。enabled默认 trueprovider默认 aliyun超时时间默认 3000 毫秒。好的 Starter 应该做到用户什么都不填也能跑起来用默认值填了就用用户的值覆盖。SpringBoot 2.2 之前还需要配合EnableConfigurationProperties或者Component才能让属性类生效后面版本只要在自动配置类上加EnableConfigurationProperties(SmsProperties.class)即可单独给属性类标Component反而会被其他项目扫描到制造混乱不推荐。2.3 实现核心服务SmsSender 及其实现核心服务接口要尽量简单。我这里定义了一个SmsSender包含两个方法单条发送和批量发送。接口的意义在于面向抽象编程方便使用方做扩展和替换实现。public interface SmsSender { boolean send(String phone, String content); boolean sendBatch(ListString phones, String content); }实现类SmsSenderImpl根据SmsProperties.provider选择不同的服务商 SDK。这里不做真正的 HTTP 调用用接口模拟出核心逻辑即可重点演示的是整个装配过程。public class SmsSenderImpl implements SmsSender { private final SmsProperties properties; public SmsSenderImpl(SmsProperties properties) { this.properties properties; } Override public boolean send(String phone, String content) { // 这里去做真实的服务商调用 System.out.println([ properties.getProvider() ] 发送短信到 phone : content); return true; } Override public boolean sendBatch(ListString phones, String content) { phones.forEach(phone - send(phone, content)); return true; } }生产环境的 Starter 里还需要在这里做参数校验、幂等控制、失败重试等但骨架思路一致。2.4 编写自动配置类把上面这些串联起来关键角色来了——自动配置类。这个类是整个 Starter 的编排者负责把SmsProperties、SmsSender这些组件注册进容器。Configuration(proxyBeanMethods false) ConditionalOnClass(SmsSender.class) EnableConfigurationProperties(SmsProperties.class) Conditional(SmsEnabledCondition.class) public class SmsAutoConfiguration { Bean ConditionalOnMissingBean(SmsSender.class) public SmsSender smsSender(SmsProperties properties) { return new SmsSenderImpl(properties); } }这段代码里有几个细节值得展开说一下。第一Configuration(proxyBeanMethods false)。SpringBoot 官方在自动配置类上强烈建议使用 Lite 模式也就是 proxyBeanMethods false因为自动配置类之间不会互相调用Bean方法不需要 CGLIB 代理省去了代理开销启动更快。第二Conditional(SmsEnabledCondition.class)。这个是自定义的条件用来判断sms.enabled是否为 true。虽然ConditionalOnProperty也能实现同样的效果但写自定义条件可以演示更复杂的判断逻辑。第三ConditionalOnMissingBean(SmsSender.class)。照搬官方套路用户自己定义了SmsSender咱们就不重复创建了保证用户自定义优先。2.5 注册文件AutoConfiguration.imports配置类写好了还得把它告诉 SpringBoot。这里就需要刚才提到的那份注册文件了。在src/main/resources/META-INF/下新建spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports内容是com.cxk.sms.SmsAutoConfiguration文件就一行写清楚自动配置类的完整类名。SpringBoot 启动的时候会扫描所有依赖 jar 中的这个文件把里面列出的配置类全部加载进来。如果你的项目还支持 SpringBoot 2.7 以下的老版本可以额外保留一份META-INF/spring.factoriesorg.springframework.boot.autoconfigure.EnableAutoConfigurationcom.cxk.sms.SmsAutoConfiguration两个文件都放版本兼容性最好。2024 年了还在用 SpringBoot 2.6 之前版本的项目不多但为了稳妥放两份文件成本很低。2.6 引入配置处理器生成元数据提示为了给使用方提供配置提示Starter 里建议加上spring-boot-configuration-processor。加了这个依赖之后编译时会在META-INF下生成spring-configuration-metadata.json文件里面记录了所有配置项的默认值、描述、类型等信息。使用方在 IDEA 里写配置时就会出现智能提示。别小看这个体验优化内部框架用的人多了有没有提示的差距非常明显。3. 实操验证本地构建一个测试工程代码写完了最重要的是验证它能不能跑通。我建议无论如何都要单独建一个测试工程来验证 Starter别直接在业务项目里联调问题定位太麻烦。3.1 本地安装与依赖引入先把 Starter 安装到本地仓库mvn clean install然后在一个全新的 SpringBoot 项目中引入依赖dependency groupIdcom.cxk/groupId artifactIdsms-spring-boot-starter/artifactId version1.0.0/version /dependency此时不写任何配置直接在启动类所在目录建一个测试接口注入SmsSenderRestController public class TestController { Autowired private SmsSender smsSender; GetMapping(/send) public String send() { smsSender.send(13800138000, hello starter); return ok; } }因为SmsProperties里默认值的存在即使配置文件啥都不写SmsSender也能注入成功跑起来控制台会打印一条模拟发送日志。3.2 配置项生效验证接下来在application.yml里覆盖配置sms: provider: tencent access-key: LTAI5tXXXX secret-key: wJalrXUtnXXXX sign-name: 测试签名重新启动调用接口控制台输出应该显示 provider 变成了 tencent。如果SmsProperties的绑定正常说明整个自动配置链路工作正常。3.3 条件注解行为的验证方法写自动配置最怕的是条件判断不生效但表面上又不报错。我常用的验证手段是在启动类里添加一个ApplicationRunner打印一下容器里有哪些相关 BeanComponent public class BeanPrinterRunner implements ApplicationRunner { Autowired private ApplicationContext context; Override public void run(ApplicationArguments args) { String[] beanNames context.getBeanNamesForType(SmsSender.class); System.out.println(SmsSender 实例 Arrays.toString(beanNames)); } }如果赛出来为空说明配置类压根没被加载如果有一个实例说明加载正常如果有两个那说明你自己的Bean定义和自动配置的重复了需要检查ConditionalOnMissingBean是否书写正确。4. 常见问题与排查技巧实录光会写 Starter 还不够遇到问题时能快速定位才是真本事。这几年我处理过不少 Starter 相关的疑难杂症挑几个典型场景分享一下排查思路。4.1 springboot 版本太高导致 Starter 失效前面提到过SpringBoot 3.x 移除了spring.factories中自动配置的支持。如果你的 Starter 还只提供spring.factories文件比如在 SpringBoot 3.2 项目里引入是不会有任何效果的。排查方法很简单看 SpringBoot 启动日志里有没有SmsAutoConfiguration相关的记录。debugtrue开启调试日志后框架会打印自动配置评估报告里面明确说明哪些自动配置类被匹配、哪些被排除、原因是什么。这个评估报告是排查自动配置问题的头号法宝。如果报告里压根没出现你的配置类那就是注册文件路径或者格式不对如果出现但显示 Negative matches那就是条件不满足。处理方案写一份AutoConfiguration.imports文件里面的类名以AutoConfiguration结尾的话也可以换用org.springframework.boot.autoconfigure.AutoConfiguration注解标记配置类。SpringBoot 3.x 推荐自动配置类上使用该注解与Configuration作用类似但语义更明确。4.2 配置项绑定了但拿到的值永远是 null这个问题常有发生原因大多数是ConfigurationProperties的类没有在容器中注册。SpringBoot 的配置绑定不是魔法它需要一个入口把配置文件里的值注入属性类的实例。如果属性类没有通过EnableConfigurationProperties注册或者自动配置类没有被加载那属性类就是个普通 Java 对象里面的值必然是 null。在 SpringBoot 2.2 时代最简单可靠的方式就是自动配置类上加EnableConfigurationProperties(SmsProperties.class)其余交给框架处理。别在属性类上直接加Component那样会导致组件扫描时机和自动配置存在不确定性。4.3 Bean 重复定义导致启动失败ConditionalOnMissingBean的语义是“容器里没有才创建”但很多人忽略了它判断的时机。如果用户在自己的Configuration里定义了SmsSender但由于配置类加载顺序的问题自动配置先执行了自动创建了 Bean然后用户定义的那个再注册时就会因为 bean 名称冲突而报错。避免方式一是自己定义 Bean 的方法名避免和smsSender这个方法创建的 bean 名称重复二是尽量用ConditionalOnMissingBean兜底三是正确配置AutoConfigureOrder或者AutoConfigureBefore/After确保用户配置优先加载。4.4 不生效时的一整套排查思路把过去的排查经验整理成一个固定套路开debugtrue确认自动配置评估报告里是否有你的配置类。看评估报告里配置类是 Positive 还是 Negative matches负匹配里会写明哪个条件不达标。检查META-INF/spring下的 imports 文件路径是否拼写正确SpringBoot 对路径大小写敏感。检查 pom 中是否真的引入了spring-boot-autoconfigure。检查使用方项目的包结构是否多出来一个同名配置类组件扫描有时会把 Starter 里本不该被扫描的类误扫描进来。如果还是不行在自动配置类里写一个ApplicationRunner或者构造器日志确认配置类构造阶段有没有被执行。这套思路我在内部复盘文档里写了不下十次只要按顺序走一遍绝大多数自动配置问题都能定位。4.5 本地构建时容易忽略的坑重复打包Starter 工程在用mvn install之前最好检查 pom 里有没有误加spring-boot-maven-plugin。我之前有同事参考业务项目模板把 repackage 配置带进了 Starter导致打出来的 jar 里包含BOOT-INF/目录结构。业务项目引用这个 jar 时类加载直接找不到类排查了快一个小时才发现是包结构错了。标准 jar 的结构是类文件直接放在com/目录下资源文件直接放在根目录资源路径里。可执行 jar 的结构是类被挪到BOOT-INF/classes/下依赖在BOOT-INF/lib/下两者使用场景完全不同。5. 手写 Starter 的进阶技巧与设计规范一个能用的 Starter 不算难写一个大家愿意用的 Starter 才是真正考验设计能力。这部分分享一些我总结的规范和技巧。5.1 命名规范为什么必须是 xxx-spring-boot-starterSpringBoot 官方对 Starter 的命名有明确约定自研 Starter 的 artifactId 应该写成xxx-spring-boot-starter比如sms-spring-boot-starter而 SpringBoot 官方的命名是spring-boot-starter-xxx比如spring-boot-starter-data-redis。这里有个隐藏的约定部分 IDE 和 Spring 官方工具对 Standard 命名有特殊处理。如果你用了spring-boot-starter-xxx的格式会被误认为是官方 Starter某些 IDE 工具可能会做特殊处理。用xxx-spring-boot-starter格式可以有效区分。5.2 条件判断的范围要克制自动配置的条件判断不宜过多。有人写 Starter 恨不得每个 Bean 上都挂三个条件结果使用方调试时头都大了。我的原则是classpath 条件控制在关键 API 类上不要把内部依赖类作为判断条件。配置条件使用ConditionalOnProperty的matchIfMissing属性来设定默认行为。ConditionalOnMissingBean是最后兜底的选择不要滥用否则调试时根本分不清哪些 Bean 是哪来的。5.3 自动配置类的内聚与拆分一个大型 Starter 里可能有多个自动配置类。比如短信 Starter 里既有发送核心配置又有定时器统计配置。不要全部塞进一个配置类里拆分成SmsAutoConfiguration、SmsMonitorAutoConfiguration两个更清晰。拆分时需要注意顺序如果SmsMonitorAutoConfiguration依赖SmsSender就用AutoConfigureAfter(SmsAutoConfiguration.class)保证顺序。这个注解不仅表达顺序也帮助维护者理清依赖关系。5.4 Starter 里一定要提供清晰的文档注释说句实在话代码写得好不好看注释就知道。Starter 是给别的项目的开发者用的面对的不是你本人所以注释要写清楚“这个配置项是做什么的”“如果不填会发生什么”。spring-boot-configuration-processor会把这些注释生成到元数据里用户悬停在配置项上就能看到中文说明这种体验上的用心程度直接影响内部框架的采用率。5.5 版本策略与兼容性矩阵如果 Starter 要支持不同版本的 SpringBoot提前列一个兼容性矩阵嘴上的口头承诺不算写在 README 里让使用者能看到。我通常是这么处理的SpringBoot 版本是否支持备注1.5.x不支持不再维护2.0.x - 2.6.x支持使用 spring.factories 注册2.7.x支持双注册文件兼容3.0.x 及以上支持使用 imports 文件注册版本兼容性看起来是小事实际使用中是非常痛苦的一件事。如果一个 Starter 内部用了新版本 API而使用方的项目还在 SpringBoot 2.4启动时直接报类找不到的错体验很差。解决思路是尽量使用兼容性好的 API并通过 maven 的 profile 机制针对不同版本做差异化编译。6. 结尾还有一些掏心窝子的经验手写 Starter 这件事看起来技术含量不高做起来却非常考验对 SpringBoot 生态的理解。自动配置只是冰山一角真正难的是如何优雅地暴露配置项、如何保证优雅降级、如何让别人愿意用你封装的东西。第一先学会看官方 Starter 源码。写自定义 Starter 最有效的学习材料就是spring-boot-autoconfigure源码里自带的那几十个自动配置类。我当初就是从DataSourceAutoConfiguration和MybatisAutoConfiguration开始啃的每一行都研究他们条件注解为什么这么写慢慢就有了语感。第二从项目里总结共性需求而不是凭空发明功能。比如我那个短信 Starter是在公司第 8 次接入不同短信服务商后忍无可忍做的抽象。好的 Starter 一定是生长出来的不是设计出来的。第三别迷信“零配置”。Starter 的目标是“少配置”不是“零配置”。像 accessKey 这种每个公司环境都不同的安全配置强制要求用户填写才是负责任的做法。启动时做个校验缺关键配置直接抛异常把问题暴露在启动阶段而不是运行时这是我踩过无数次坑之后最深的体会。现在建议你放下手里的业务代码挑一个你们组里最常见的重复轮子按这篇文章的思路写一个内部 Starter。写完后你再看 SpringBoot 的自动配置评估报告一定会比之前清晰得多。
RELATED

相关推荐

基于Spring Boot+Vue的花店管理系统毕业设计全攻略

基于Spring Boot+Vue的花店管理系统毕业设计全攻略

做毕业设计最怕的不是不会写代码,而是不知道自己到底要做一个什么样的系统,做完之后能不能讲清楚。选“基于Spring Boot Vue的花店管理系统”这个题目的人,通常已经明确了两件事:第一,想用前后端分离架构展示完整的开…

📅 2026/10/9 4:22:22
GPT辅助科学计算编程:两个实例拆解提示词设计与验证

GPT辅助科学计算编程:两个实例拆解提示词设计与验证

从去年开始我做材料计算方向的Python脚本基本都在GPT辅助下完成,这个系列也写到了第四篇。前面的内容讲了不少提示词框架和基础技巧,今天这篇我打算完全换一种讲法:直接拿两个计算力学和材料计算里的典型任务,庖丁解牛一样把提示词…

📅 2026/10/9 4:22:22
P3010 Dividing the Gold 题解:0/1背包转换与方案数DP详解

P3010 Dividing the Gold 题解:0/1背包转换与方案数DP详解

出门前还在想“今晚把这题刷完就睡”,结果一道[USACO11JAN] Dividing the Gold S让我折腾到凌晨。这题在洛谷是P3010,USACO 2011年1月的Silver组题目,表面看就是个“把金子分成两堆让重量差最小”,可实际上它同时考了0/1背包的经典…

📅 2026/10/9 4:22:22
MORE NEWS

更多资讯

📰

领域特定评估实战:用 Argilla、Distilabel 与 LightEval 构建考试问答评估流水线(smol-course)

教程人工智能大模型NLP微调 【免费下载链接】smol-course A course on aligning smol models. 项目地址: https://gitcode.com/gh_mirrors/smo/smol-course 点击查看 免费下载 主流基准(如 MMLU、TruthfulQA)大多衡量推理、数学、代码等通用…

📰

Apache Storm 集群安全加固实战:从 OS 层防护到 Kerberos 认证与 ACL 授权

后端大数据 【免费下载链接】storm Apache Storm 项目地址: https://gitcode.com/gh_mirrors/storm22/storm 点击查看 免费下载 Apache Storm 默认以"信任内网"的方式运行,所有认证(Authentication)与授权(…

📰

CMake FindOpenCL 模块全解析:从 find_package 到 OpenCL::OpenCL 导入目标

构建工具开发工具CLI 【免费下载链接】CMake Mirror of CMake upstream repository 项目地址: https://gitcode.com/gh_mirrors/cm/CMake 点击查看 免费下载 本指南围绕 CMake 官方模块 FindOpenCL(Modules/FindOpenCL.cmake)展开&#xff0…

📰

YOLO船舶检测实战:数据集解析与训练避坑指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

📰

题解:洛谷 P14361 [CSP-S 2025] 社团招新

本文分享的必刷题目是从蓝桥云课、洛谷、AcWing等知名刷题平台精心挑选而来,并结合各平台提供的算法标签和难度等级进行了系统分类。题目涵盖了从基础到进阶的多种算法和数据结构,旨在为不同阶段的编程学习者提供一条清晰、平稳的学习提升路径。 欢迎大家订阅我的专栏:算法…

📰

U-Boot Kbuild深度解析:从零构建RV1106移植的四大核心步骤

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

TODAY

今日更新

THIS WEEK

本周精选

THIS MONTH

本月热门

读完文章,想聊聊您的网站?

告诉我们您的行业与需求,资深顾问一对一梳理方案与报价,全程免费。

📞 💬