
我之前在阿里做基础架构的时候核心工作之一就是封装公司级的公共Starter统一各个业务线的技术栈不用每个项目都重复写Redis操作、日志埋点、权限校验这些通用功能。很多开发者天天用Starter但真问Starter的原理80%的人答不上来更别说自己写一个符合大厂规范的Starter了。今天我就把Starter的原理讲透带大家手写一个可复用的Redis操作Starter再分享大厂封装Starter的规范和踩坑经验。一、Starter到底是什么很多人以为Starter是什么特殊的技术其实它就是一个普通的Maven/Gradle模块核心作用是依赖聚合把某个功能需要的所有依赖都整合进来用户不用自己一个个引入避免依赖冲突。自动配置根据当前项目的环境自动配置需要的Bean不用用户写一堆XML或者注解配置。约定大于配置提供默认的配置参数用户只有在需要修改默认值的时候才需要自己配置。举个例子你要做Web开发只要引入spring-boot-starter-web就自动引入了Tomcat、Spring MVC、Jackson这些依赖而且自动配置好了端口8080、静态资源路径这些默认参数直接就能写接口这就是Starter的价值。1.1 自动配置的核心条件注解Starter的自动配置底层全靠Conditional系列的条件注解常用的有这些注解作用ConditionalOnClass类路径下存在指定类的时候才生效ConditionalOnMissingBean容器中不存在指定Bean的时候才创建ConditionalOnProperty配置文件中存在指定属性的时候才生效ConditionalOnWebApplication当前是Web应用的时候才生效ConditionalOnExpression满足SpEL表达式的时候才生效这些注解的底层原理很简单Spring在加载Bean定义的时候会判断条件注解的条件是否满足满足才会把Bean注册到容器中。2.x/3.x差异Spring Boot 2.x的自动配置类是配置在META-INF/spring.factories文件里key是org.springframework.boot.autoconfigure.EnableAutoConfiguration3.x之后废弃了spring.factories这种方式改成配置在META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports文件里每行写一个自动配置类的全类名。二、手写自定义Redis Starter实战我们来写一个可复用的Redis操作Starter功能包括自动配置Redis连接池封装常用的Redis操作工具类支持jedis和lettuce两种客户端自动切换符合大厂规范。2.1 第一步创建Starter项目首先创建一个Maven项目项目命名为redis-spring-boot-starter注意官方Starter的命名是spring-boot-starter-xxx第三方自定义Starter的命名必须是xxx-spring-boot-starter不能和官方命名规则冲突这是大厂的强制规范。pom.xml核心依赖dependencies!-- 自动配置核心依赖 --dependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-autoconfigure/artifactIdversion2.7.15/version/dependency!-- 配置属性元数据IDE会自动提示配置项 --dependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-configuration-processor/artifactIdversion2.7.15/versionoptionaltrue/optional/dependency!-- Redis客户端依赖设为optional用户项目里有就用没有就不生效 --dependencygroupIdredis.clients/groupIdartifactIdjedis/artifactIdversion3.9.0/versionoptionaltrue/optional/dependencydependencygroupIdio.lettuce/groupIdartifactIdlettuce-core/artifactIdversion6.2.6.RELEASE/versionoptionaltrue/optional/dependency/dependencies2.2 第二步编写配置属性类配置属性类用来绑定配置文件里的参数给用户提供自定义配置的入口ConfigurationProperties(prefixredis.operator)publicclassRedisOperatorProperties{/** * Redis主机地址 */privateStringhost127.0.0.1;/** * Redis端口 */privateintport6379;/** * Redis密码 */privateStringpassword;/** * 数据库索引 */privateintdatabase0;/** * 连接超时时间 */privateinttimeout2000;/** * 客户端类型jedis/lettuce默认自动探测 */privateStringclientType;// 省略getter/setter}加了prefix redis.operator之后用户在配置文件里只要写redis.operator.hostxxx就能覆盖默认配置IDE还会自动提示配置项这就是配置元数据的作用。2.3 第三步编写自动配置类这是Starter的核心根据条件自动配置BeanConfigurationEnableConfigurationProperties(RedisOperatorProperties.class)ConditionalOnClass({Jedis.class,RedisClient.class})// 类路径下有jedis或者lettuce才生效publicclassRedisOperatorAutoConfiguration{BeanConditionalOnMissingBeanConditionalOnProperty(prefixredis.operator,nameclientType,havingValuejedis,matchIfMissingtrue)publicRedisOperatorjedisRedisOperator(RedisOperatorPropertiesproperties){JedisPooljedisPoolnewJedisPool(newJedisPoolConfig(),properties.getHost(),properties.getPort(),properties.getTimeout(),properties.getPassword(),properties.getDatabase());returnnewJedisRedisOperator(jedisPool);}BeanConditionalOnMissingBeanConditionalOnProperty(prefixredis.operator,nameclientType,havingValuelettuce)publicRedisOperatorlettuceRedisOperator(RedisOperatorPropertiesproperties){RedisClientredisClientRedisClient.create(String.format(redis://%s:%s/%s,properties.getHost(),properties.getPort(),properties.getDatabase()));returnnewLettuceRedisOperator(redisClient);}}这里的逻辑很清晰如果用户配置了clientType为jedis或者没配置默认用jedis客户端如果配置了clientType为lettuce就用lettuce客户端容器里没有RedisOperator这个Bean的时候才创建用户可以自定义Bean覆盖默认实现。2.4 第四步注册自动配置类Spring Boot 2.x在src/main/resources/META-INF下创建spring.factories文件org.springframework.boot.autoconfigure.EnableAutoConfiguration\ com.example.redis.autoconfigure.RedisOperatorAutoConfigurationSpring Boot 3.x在src/main/resources/META-INF/spring下创建org.springframework.boot.autoconfigure.AutoConfiguration.imports文件com.example.redis.autoconfigure.RedisOperatorAutoConfiguration2.5 第五步封装Redis操作接口和实现定义统一的Redis操作接口不同客户端实现这个接口publicinterfaceRedisOperator{voidset(Stringkey,Stringvalue);Stringget(Stringkey);voidsetEx(Stringkey,Stringvalue,longexpireSeconds);Booleandel(Stringkey);}然后分别写Jedis和Lettuce的实现类这里就不贴具体代码了很简单。2.6 测试Starter把Starter打包install到本地Maven仓库然后在测试项目里引入依赖dependencygroupIdcom.example/groupIdartifactIdredis-spring-boot-starter/artifactIdversion1.0.0/version/dependency配置文件里加Redis配置redis:operator:host:192.168.1.100port:6379password:123456然后直接注入RedisOperator就能用了RestControllerpublicclassTestController{AutowiredprivateRedisOperatorredisOperator;GetMapping(/test)publicStringtest(){redisOperator.setEx(name,威哥,3600);returnredisOperator.get(name);}}完全不需要自己写Redis连接配置开箱即用。三、大厂公共Starter封装规范我之前在阿里封装公共Starter的时候有一套严格的规范分享给大家命名规范所有自定义Starter必须以xxx-spring-boot-starter命名禁止使用spring-boot-starter-xxx开头避免和官方Starter冲突。依赖规范所有第三方依赖必须设为optionaltrue避免把依赖强制传递到用户项目里造成依赖冲突。配置规范所有配置项必须加统一的前缀比如公司的Starter统一用aliyun.xxx作为前缀避免和其他配置冲突。兼容规范必须兼容Spring Boot 2.4到3.x的所有主流版本用条件注解判断不同版本的类不能出现版本不兼容的问题。文档规范每个Starter必须有详细的使用文档包含配置项说明、使用示例、常见问题排查。3.1 版本兼容技巧如果你的Starter需要同时支持2.x和3.x有几个技巧用ConditionalOnClass判断不同版本的类比如2.x有javax.servlet.Filter3.x有jakarta.servlet.Filter分别配置对应的Bean。用Maven的profile打包不同版本的Starter2.x版本的Starter用javax的依赖3.x版本的用jakarta的依赖。不要使用高版本Spring Boot特有的API尽量用通用API。四、常见问题排查问题1Starter引入后不生效排查步骤启动的时候加debugtrue配置看自动配置报告找到你的自动配置类看条件有没有满足有没有被排除。检查自动配置类有没有正确注册到spring.factories或者AutoConfiguration.imports文件里路径有没有写错。检查配置属性的前缀有没有写错有没有配置EnableConfigurationProperties注解。问题2依赖冲突比如用户项目里已经有jedis的旧版本Starter里的jedis版本和用户的版本冲突解决方法把Starter里的jedis依赖设为optionaltrue让用户自己控制jedis的版本。在Starter的pom里用dependencyManagement统一管理依赖版本避免冲突。问题3自定义Bean覆盖不了Starter的默认Bean原因是Starter的自动配置类加载顺序比用户的配置类晚解决方法给用户的自定义Bean加Primary注解优先使用用户的Bean。在Bean注解上加ConditionalOnMissingBean只有容器里没有的时候才创建。五、面试考点提炼Spring Boot Starter的工作原理是什么手写一个Starter的步骤有哪些Conditional系列注解常用的有哪些作用是什么Spring Boot 2.x和3.x自动配置类的注册方式有什么差异自定义Starter怎么实现版本兼容总结Starter是Spring Boot的核心设计思想之一学会写自定义Starter不仅能理解Spring Boot的自动配置原理还能在公司里封装公共组件提升团队的开发效率。大家平时可以多看看官方Starter的源码学习官方的封装思路慢慢就能写出符合大厂规范的Starter了。