ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Spring Boot自动配置原理与自定义Starter实战:从源码到条件注解

Spring Boot自动配置原理与自定义Starter实战:从源码到条件注解 1. 为什么 Spring Boot 的“自动”值得被彻底搞懂先交代一下背景。我最早接触 Spring Boot 是 1.5 时代那时候有个很经典的困惑就加了一个spring-boot-starter-web依赖然后写了一个SpringBootApplication注解的类项目就能跑起来Tomcat 自己就启动了Spring MVC 的东西也能用了。当时我的想法就是——这东西真玄学。后来去做项目交接二开别人的老系统遇到一个诡异问题一个第三方封装的 Redis 客户端 Starter 怎么调都不生效RedisTemplate永远注入不进去。排查来排查去最后定位到是自定义 Starter 的spring.factories写错了路径类根本没有被加载。从那一刻我才意识到如果不懂自动配置的原理你连“它为什么不生效”都说不清楚。这篇博文我不讲废话直接拆三层从SpringBootApplication出发源码层面看自动配置到底怎么被触发的。把条件注解逐个讲透因为自动配置的命根子是Conditional那一套。手写一个能用的自定义 Starter从零走到本地发布、引用、验证。看完这篇你至少能做到三件事第一Spring Boot 启动时为什么加载这些配置、不加载那些配置你能看着启动日志说出原因第二第三方 Starter 失效时你有一个系统的排查链路而不是瞎试第三你具备写一个生产级 Starter 的能力公司内部公共组件封装这种活能直接拿得出手。2. 源码拆解自动配置从启动到生效的完整链路2.1 从SpringBootApplication注解说起我们先从最常见的写法入手。绝大多数项目的启动类长这样SpringBootApplication public class Application { public static void main(String[] args) { SpringApplication.run(Application.class, args); } }SpringBootApplication是一个组合注解按住 Ctrl 点进去你会看到它是这三个注解的合体SpringBootConfiguration EnableAutoConfiguration ComponentScan这说明两件事SpringBootConfiguration只是被Configuration元注解过的标记让启动类本身能充当配置类。ComponentScan默认扫描启动类所在包及其子包这是为什么你的Service、Controller能自动被扫进来的原因。EnableAutoConfiguration才是“自动配置”的开关。注意一个容易忽略的细节SpringBootApplication默认的ComponentScan不包含excludeFilters之外的过滤规则但EnableAutoConfiguration引入的AutoConfigurationImportSelector是独立于组件扫描的一套机制。也就是说自动配置类的发现和普通 Bean 的扫描是两条完全不同的路径两者互不干扰又最终在同一个ConfigurationClassPostProcessor阶段汇合。理解这一点你后面排查“为什么这个 Bean 没加载”时就知道先查哪条链路。2.2AutoConfigurationImportSelector的加载顺序EnableAutoConfiguration源码里有这样一行Import(AutoConfigurationImportSelector.class)Spring 处理Import时会把AutoConfigurationImportSelector当作一个ImportSelector来执行。ImportSelector接口的核心方法是selectImports(AnnotationMetadata importingClassMetadata)返回值是 String 数组——也就是一组类的全限定名。AutoConfigurationImportSelector的执行流程可以用下面这条主线概括从spring.factoriesSpring Boot 2.7 之前或AutoConfiguration.importsSpring Boot 2.7 开始引入中读取所有EnableAutoConfiguration键对应的配置类列表。通过Conditional系列注解对所有候选配置类做过滤。对过滤后的配置类进行去重、排序AutoConfigureOrder、AutoConfigureBefore、AutoConfigureAfter。返回最终的配置类名数组交给 Spring 容器注册。这里要特别指出排序这步很多人会忽略。自动配置类之间的顺序是有讲究的比如DataSourceAutoConfiguration往往要在MybatisAutoConfiguration之前加载因为 MyBatis 的配置依赖一个已经初始化好的DataSource。AutoConfigureBefore和AutoConfigureAfter就是干这个的。2.3 从spring.factories到AutoConfiguration.imports的演变Spring Boot 2.7 之前自动配置类都在META-INF/spring.factories这个文件里键是org.springframework.boot.autoconfigure.EnableAutoConfiguration。举一个真实的例子org.springframework.boot.autoconfigure.EnableAutoConfiguration\ com.example.mylib.MyAutoConfiguration,\ com.example.mylib.AnotherAutoConfigurationSpring Boot 2.7 开始官方建议把自动配置类挪到META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports一个类名一行不加EnableAutoConfiguration键前缀。Spring Boot 3.0 则直接移除了对spring.factories中自动配置的支持只认imports文件。这个变动的意义在于spring.factories这个文件同时被 Spring Cloud、其他第三方库用来做各种 SPI 扩展键很多容易冲突也容易加载到一些用不到的类imports文件把自动配置的职责单独拆出来更清晰、更省内存。我自己在升级 Spring Boot 2.7 的时候踩过坑公司的公共组件老版本用的是spring.factories升级之后怎么都不生效后来查了文档才知道要新增一个imports文件。如果你现在要写新的 Starter我建议直接一步到位用AutoConfiguration.imports不要走已经快被淘汰的老路。2.4ConfigurationClassPostProcessor如何接管这些配置类AutoConfigurationImportSelector返回类名后Spring 会把这些类当作普通的Configuration配置类做处理。这个阶段的关键角色是ConfigurationClassPostProcessor它是 Spring 容器启动早期注册的一个BeanDefinitionRegistryPostProcessor。它会在invokeBeanFactoryPostProcessors阶段被触发做的工作大致是解析所有配置类的Bean方法。解析配置类上的Import触发嵌套的配置类加载。处理配置类上的Conditional条件判断。将最终生成的BeanDefinition注册进容器。也就是说自动配置类并不是被“一次性全部实例化”的而是先注册 Bean 定义等到真正的 Bean 实例化阶段finishBeanFactoryInitialization才根据条件产生对象。这就能解释一个现象为什么自动配置类很多但你的应用启动后并没有创建那么多 Bean——因为大量ConditionalOnMissingBean、ConditionalOnClass在早早期就把不满足条件的定义直接拦掉了。3. 条件注解才是自动配置的灵魂从ConditionalOnClass到ConditionalOnMissingBean3.1 条件注解怎么做到“不满足就跳过”自动配置之所以“自动”全靠条件注解把关。如果没有条件注解你引入任何一个 Starter它就会把所有可能的配置全部加载到时候DataSource、RedisTemplate、RestTemplate全都给你创建一遍项目根本跑不动。条件注解的核心原理并不玄乎Spring 在执行配置类的解析阶段会通过ConditionEvaluator评估每个条件注解如果条件不成立就跳过这个配置类或跳过这个Bean方法。看一个典型例子RabbitAutoConfiguration内部的条件是这样的Configuration(proxyBeanMethods false) ConditionalOnClass({ RabbitTemplate.class, Channel.class }) EnableConfigurationProperties(RabbitProperties.class) Import({ RabbitAnnotationDrivenConfiguration.class, RabbitStreamConfiguration.class }) public class RabbitAutoConfiguration { // ... }RabbitTemplate.class和Channel.class必须存在于 classpath 中这个自动配置类才生效。也就是说你的项目里只要引入了spring-boot-starter-amqp这两个类就存在配置生效如果没引入类不存在条件不满足自动配置跳过。3.2 一个条件判断内部发生了什么ConditionalOnClass背后是OnClassCondition它实现的是Condition接口。Spring 在评估时会对配置类做“元信息处理”OnClassCondition拿到的是ConditionContext和AnnotatedTypeMetadata。判断方式核心就是类加载器尝试加载目标类ClassLoader classLoader context.getClassLoader(); Class? targetType ClassUtils.resolveClassName(className, classLoader);如果加载成功说明 classpath 里有这个类如果抛出ClassNotFoundException说明没这个类条件失败。这个过程中有个容易被忽略的点ConditionalOnClass的name属性和value属性混用时判断逻辑稍有差异。value是基于类引用在编译期就确认了name是字符串在运行期用ClassUtils.isPresent来判断。有些库为了避开类加载的坑会刻意用name方式。你自己写条件注解时建议优先用value因为编译期就能发现类路径问题但如果要判断类是否存在于一个本身可能不存在的模块里比如某可选依赖的类用name更稳妥——因为直接写SomeClass.class会导致编译期强依赖。3.3ConditionalOnMissingBean防止重复创建 Bean 的关键ConditionalOnMissingBean是另一个高频条件注解它的作用是当容器里已有某个类型的 Bean 时不再创建被标注的这个 Bean。常见的使用场景是框架给你一个默认实现但允许你自己覆盖。比如RedisAutoConfiguration内部Bean ConditionalOnMissingBean(name redisTemplate) public RedisTemplateObject, Object redisTemplate(RedisConnectionFactory redisConnectionFactory) { RedisTemplateObject, Object template new RedisTemplate(); template.setConnectionFactory(redisConnectionFactory); return template; }这个设计意图很清晰如果你自己在代码里定义了一个名为redisTemplate的 Bean框架的默认模板就不生效以你的为准。ConditionalOnMissingBean有个众所周知的坑它只能在 BeanDefinition 解析阶段判断如果一个 Bean 是通过Autowired在运行时才塞进来的这个条件看不到它。也就是说它判断的是“容器里是否已经注册了这个类型的 BeanDefinition”不是“运行期能否通过类型拿到一个实例”。这导致的一个典型问题是你自己定义了一个Bean返回接口类型A而自动配置里判断的是实现类B.class两者类型不一致条件就误判了。3.4 其他常用的条件注解和一些组合技巧Spring Boot 的条件注解远不止上面两个我列一个常用的表格条件注解判断依据常见使用场景ConditionalOnClassclasspath 是否存在指定类引入对应依赖才加载配置ConditionalOnMissingClassclasspath 是否不存在指定类排除某些环境下的自动配置ConditionalOnBean容器是否存在指定 Bean依赖前置 Bean 的存在ConditionalOnMissingBean容器是否不存在指定 Bean提供默认实现但允许覆盖ConditionalOnProperty配置项是否有指定值通过配置项开关功能ConditionalOnExpressionSpEL 表达式求值结果复杂条件组合ConditionalOnWebApplication是否 Web 应用Web 相关配置ConditionalOnNotWebApplication是否非 Web 应用非 Web 场景配置组合使用时有一个技巧ConditionalOnClass和ConditionalOnMissingBean常常一起出现前者保证“依赖在闸门才开”后者保证“你没有自定义默认的才顶上”。这两个条件同时成立配置才会真正生效。我还踩过一个ConditionalOnProperty的坑。那时候我们给内部组件加开关写了这样的条件ConditionalOnProperty(name my.component.enabled, havingValue true)配置中心下发时写得是my.component.enabledtrue但一直不生效。后来发现ConditionalOnProperty默认的matchIfMissing是false也就是说配置项如果压根没出现或没被加载到条件就不成立。而我们那个组件凑巧没把配置中心的 dataId 读进来所以永远不满足。这提醒我一件事条件注解的判断依赖的配置必须是确定存在的否则宁可显式给默认值。4. 手写一个生产级 Starter从需求设计到发布使用的全过程4.1 先想清楚 Starter 要解决什么问题写 Starter 之前先自问一句这个组件有多少 Bean 是“每次都要配置且配置方式极度重复”的如果没几个那就不值得封装如果每个项目引入都要写一堆Bean封装成 Starter 才有意义。我这边以“封装一个操作日志记录客户端”为例子展开。假设公司内部要统一收集业务操作日志日志需要发送到 MQ然后由另一个服务消费入库。每个业务方接入时都要做这几件事声明一个RabbitTemplate并设置好 exchange、routingKey。定义OperationLogSender把业务方传入的日志对象序列化后发到 MQ。定义OperationLogProperties让调用方配置 exchange、routingKey、appName 等字段。这三件事如果靠业务方自己写代码重复度极高而且一旦 MQ 配置调整所有业务方都要改。封装成 Starter 后接入方只需加依赖、配几行字段完事。4.2 工程结构设计与配置类设计Starter 建议单独拆成 Maven 模块结构大概是operation-log-starter ├── pom.xml └── src/main/java/com/example/operationlog ├── OperationLogAutoConfiguration.java ├── OperationLogProperties.java ├── OperationLogSender.java └── META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.importsOperationLogProperties对应配置前缀ConfigurationProperties(prefix operation.log) public class OperationLogProperties { private String exchange; private String routingKey; private String appName; private boolean enabled true; // getter / setter 省略 }这里有个细节ConfigurationProperties的类本身不会被自动扫描到容器里需要通过EnableConfigurationProperties(OperationLogProperties.class)显式激活。我见过有人写了ConfigurationProperties以为就能用了实际上还得有启用机制否则配置项永远绑定不上。OperationLogSender是核心业务类只保留一个发送方法public class OperationLogSender { private final RabbitTemplate rabbitTemplate; private final OperationLogProperties properties; public OperationLogSender(RabbitTemplate rabbitTemplate, OperationLogProperties properties) { this.rabbitTemplate rabbitTemplate; this.properties properties; } public void send(OperationLogEvent event) { if (!properties.isEnabled()) { return; } rabbitTemplate.convertAndSend(properties.getExchange(), properties.getRoutingKey(), event); } }这个类里不写死任何配置项全部通过OperationLogProperties拿为的是业务方可以在application.yml里动态覆盖。4.3AutoConfiguration.imports文件的正确写法在resources/META-INF/spring/目录下新建org.springframework.boot.autoconfigure.AutoConfiguration.imports内容是com.example.operationlog.OperationLogAutoConfiguration注意三点文件名必须完全匹配org.springframework.boot.autoconfigure.AutoConfiguration.imports路径是META-INF/spring/不是META-INF/。文件编码建议 UTF-8类名一个一行不允许有多余的空格和逗号。这个文件只放自动配置类的全限定名。如果你的包里有普通的Configuration配置类不要放进来——那些类应该由业务方手动Import或者交给ComponentScan处理。如果你还在维护旧版本 Spring Boot才需要在META-INF/spring.factories里补充org.springframework.boot.autoconfigure.EnableAutoConfiguration\ com.example.operationlog.OperationLogAutoConfigurationspring.factories的格式要求键唯一、多行用反斜杠续行写错一个字符整个文件都读不了而且不会有任何报错排查非常痛苦。4.4 自动配置类把 Bean 按需注册进去OperationLogAutoConfiguration的设计主题是“依赖的前提条件成立才注册用户自定义了就不重复注册”。AutoConfiguration ConditionalOnClass(RabbitTemplate.class) EnableConfigurationProperties(OperationLogProperties.class) public class OperationLogAutoConfiguration { Bean ConditionalOnMissingBean public OperationLogSender operationLogSender(RabbitTemplate rabbitTemplate, OperationLogProperties properties) { return new OperationLogSender(rabbitTemplate, properties); } }这里我用了AutoConfiguration注解。它是 Spring Boot 2.7 之后引入的新注解相当于Configuration(proxyBeanMethods false)AutoConfigureBefore/AutoConfigureAfter这些能力的组合标记。用AutoConfiguration比直接用Configuration更贴合自动配置的语境而且官方推荐自动配置类一律用这个注解。条件设计的思路是ConditionalOnClass(RabbitTemplate.class)业务方项目里必须引入RabbitTemplate才会激活这个配置。如果没有 MQ 依赖配置类直接跳过连OperationLogProperties绑定都不会发生。ConditionalOnMissingBean如果业务方已经手动定义了一个OperationLogSenderBean那这个自动配置的 Bean 就不注册了。给业务方留了“自定义扩展”的口子。另外不要在这个配置类里写ComponentScan也不要让自动配置类所在的包和业务方的包重叠。自动配置类的包路径一旦被ComponentScan扫到会引发重复注册或覆盖问题。4.5 配置项绑定和 Spring 的自动提示Starter 里定义好ConfigurationProperties后业务方在配置文件里写operation.log开头的属性时IDE 如果没有提示体验会差很多。Spring Boot 的配置元数据文件spring-configuration-metadata.json就是干这个的。位置在src/main/resources/META-INF/spring-configuration-metadata.json内容片段{ properties: [ { name: operation.log.enabled, type: java.lang.Boolean, description: 是否启用操作日志发送., defaultValue: true } ] }手工写这个 JSON 有点繁琐但好在有替代方案在pom.xml里引入spring-boot-configuration-processor编译时会自动生成元数据。添加依赖dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-configuration-processor/artifactId optionaltrue/optional /dependencyoptionaltrue/optional是为了防止这个处理器依赖传递到业务方项目里。我第一次没加 optional结果业务方应用类路径里多了一个 annotation processor编译期老是弹奇怪警告。4.6 本地验证 Starter 是否生效Starter 写好之后不要直接丢到业务方先在本地快速验证。我习惯建一个独立的测试工程引入刚打包的 Starter 依赖。在application.yml里配置operation: log: exchange: biz.log.exchange routing-key: biz.log.route app-name: demo-service enabled: true启动项目如果自动配置生效日志里会出现Completed initialization of OperationLogSender或者你直接注入OperationLogSender到 Controller 里调用一次 send 方法看有没有异常。关键是要验证三类情况情况预期结果没有引入 RabbitMQ 相关依赖自动配置类不加载Bean 不存在引入依赖且未自定义 Bean自动创建 OperationLogSender引入依赖且用户自定义了 Bean用户 Bean 生效默认 Bean 不创建这三种情况都测过才能放心地把 Starter 发布到公司内部仓库。我建议每个 Starter 的测试工程保留下来后面每次改代码都要跑一遍防回归。5. 自动配置失效时的系统化排查链路5.1 一种最典型的“不生效”现象我前面提到过那个 Redis Starter 不生效的问题这里把完整排查链路还原出来。现象是引用新写的 Starter 后启动没报错但注入的 Bean 是空的调用时报 NPE。排查思路要按顺序来不要一上来就翻源码。第一步先在启动日志里看自动配置报告的条件评估结果。Spring Boot 启动时加上--debug参数或者直接在application.yml里加debug: true启动后日志里会出现CONDITIONS EVALUATION REPORT里面有所有自动配置类的名字以及Matched/Did not match和原因。我当时一眼就看到那行Did not match: - ConditionalOnClass did not find required class org.springframework.data.redis.core.RedisTemplate这行说明 classpath 里根本没有RedisTemplate。再一查原来引用的依赖坐标写错了版本引入的是一个不包含 Redis 的模块。这就是“有没错但肯定没生效”的典型。5.2 条件评估报告怎么读CONDITIONS EVALUATION REPORT分两个部分Positive matches和Negative matches。Positive matches列出匹配成功、将要生效的自动配置类。Negative matches列出未匹配的且每条都会带上未匹配的原因。排查时先从Negative matches里找有没有你预期中的配置类再看原因。原因一般就那几类ConditionalOnClass did not find required class缺依赖。ConditionalOnProperty did not find property xxx配置项没写或没读到。ConditionalOnMissingBean found bean xxx已经有用户自定义 Bean默认的不加载。5.3 电脑上调试时怎么下断点跟源码条件报告只能告诉你“不满足”但想知道“为什么这个条件这么写”就得下断点跟源码。我常用的断点位置有这几个类方法作用AutoConfigurationImportSelectorgetAutoConfigurationEntry确认候选配置类列表AutoConfigurationImportSelectorfilter查看过滤流程OnClassConditiongetOutcomes查看类是否存在的结果ConfigurationClassParserprocessConfigurationClass查看配置类是否被处理在 IDEA 里对这些方法下断点启动后在调试面板看调用栈能直观看到条件过滤的发生时刻和结果。5.4 配置类已经被加载但 Bean 没注册怎么办有一种更隐蔽的情况自动配置类在Positive matches里显示匹配了但预期的 Bean 还是不存在。这种情况多半是自动配置类里的Bean方法没有被执行。我遇到过一次自动配置类里Bean方法返回一个接口类型但实现类在另一个模块里那个模块没被引入方法内部直接抛了NoClassDefFoundError。Spring 对Bean方法处理异常时不会把异常直接抛出去而是注册一个失败的 BeanDefinition。日志里只会有很隐蔽的 warning很容易漏掉。所以排查时在Bean方法第一行下断点如果你停不进去说明方法压根没被调用如果停进去了就逐步跟看是哪一步挂了。5.5 类加载顺序导致的“间歇性失效”有一种情况用条件报告也很难定位就是自动配置类之间依赖顺序不对。比如你的 Starter 需要一个前置服务类先注册但如果你的自动配置类跑得太早前置的 BeanDefinition 还没解析完ConditionalOnBean判断时找不到就会误判为“不存在”导致你的 Bean 不注册。这种问题的特点是多跑几次启动有时生效有时不生效。解法是给自动配置类标顺序AutoConfiguration(after SomeBaseAutoConfiguration.class) public class MyAutoConfiguration { }或者用AutoConfigureAfter注解显式声明依赖关系。当你写 Starter 时建议对“被依赖的配置类”和“依赖别人的配置类”都做显式声明不要只靠默认排序。5.6 排查链路的总结我把自己的排查经验整理成五步启动参数加--debug或debug: true生成条件评估报告。看Negative matches寻找预期配置类不匹配的原因。看Positive matches确认配置类确实被加载。对配置类的Bean方法下断点确认 Bean 是否走到了实例化阶段。检查自动配置类之间的顺序看是否存在依赖前置配置类的逻辑。这套方法我用了很久基本能兜住绝大多数 Starter 不生效的问题。如果你试过发现条件报告和断点都正常那大概率就是类路径冲突——不同模块里有同名类加载顺序不可控这种情况下直接查依赖树。6. 写自动配置时的一些进阶考量6.1proxyBeanMethods false到底省在哪里现在看各种自动配置类源码时你会发现它们几乎都写了proxyBeanMethods false或者直接用AutoConfiguration隐含了这个设置。proxyBeanMethods false的意思是Configuration类不会被创建成 CGLIB 代理类调用Bean方法时不会检查“容器里是否已有该 Bean”而是每次直接走方法返回新对象。为什么自动配置类要关掉代理原因有两点自动配置类通常不需要配置类内部直接调用自己的Bean方法所以不需要代理带来的 Bean 复用语义。去掉代理能减少启动时的开销自动配置类数量庞大每个都加代理会拖慢启动。你在业务代码里写普通Configuration时还是建议保留默认proxyBeanMethods true因为你的配置类内部可能相互引用Bean方法。只有当你想写一个“纯声明式、无内部方法调用”的配置类时才考虑关掉。6.2 自动配置类和普通Component扫描的区别很多人一开始会混淆为什么我写一个Configuration类放进启动类的子包会被ComponentScan扫到而自动配置类要求必须写在imports或spring.factories里原因是时机不同ComponentScan扫描到的配置类处理得比较早但扫描范围由SpringBootApplication的包位置决定第三方 Starter 不可能控制业务方的包结构。自动配置类通过 SPI 机制独立加载不受扫描包路径限制。这是第三方库能“自动生效”的根本原因。这个区别也解释了一个掉坑案例我把一个自动配置类写成了普通Configuration并且类放在了com.example.common.config包下业务方启动类扫描到它功能确实能用但后来业务方改了扫描路径配置类就不加载了。如果当初走的是imports机制就不会有这种问题。6.3 自定义 Starter 的参数校验和默认值写 Starter 时务必给ConfigurationProperties里的关键字段配默认值。我见过太多 Starter 因为没配默认值业务方一忘配就启动报错。推荐的做法是private boolean enabled true; private String exchange default.exchange; private String routingKey default.route;同时在OperationLogSender构造时做个防御性校验if (properties.getAppName() null || properties.getAppName().isBlank()) { throw new IllegalArgumentException(operation.log.app-name is required); }早失败比晚失败好越早暴露配置缺失越少浪费排查时间。6.4 自动装配失效时应该提供明确的错误信息有些情况下你的 Starter 依赖的必备条件不满足但你又不想直接报错因为业务方可能确实不需要这个功能。比如上面说的既有ConditionalOnClass又有ConditionalOnProperty如果配置了enabled: false就应该静默跳过。但如果条件是无法满足的硬性条件且这个 Starter 是业务方明确引入的最好提供一个显式错误提示。Spring Boot 提供了FailureAnalyzer机制可以自定义启动错误分析器。你可以让OperationLogAutoConfiguration在条件不满足时抛出OperationLogAutoConfigurationException再由自定义FailureAnalyzer把异常翻译成友好的提示信息。这在内部组件里很实用能让业务方一眼看出“还缺什么配置”。6.5 一个 Starter 里包含多个自动配置类的组织方式复杂组件往往不止一个自动配置类。比如你封装的是一个缓存客户端可能需要CacheAutoConfiguration、MetricsAutoConfiguration、SerializerAutoConfiguration三个类分别处理主逻辑、监控指标、序列化方案。组织上建议每个自动配置类职责单一一个类只负责一组 Bean。通过AutoConfigureAfter、AutoConfigureBefore明确类之间的先后。imports文件中按执行顺序排列虽然真正的排序是由注解控制的但文件顺序尽量也保持一致方便后来者阅读。我用过的一种方式是把公共逻辑抽到一个AbstractXxxConfiguration基类三个自动配置类继承它各自通过不同的Conditional控制开关。这个模式适合一整套组件的场景比如“基础连接 高级功能 监控上报”这种分层组合。7. 从理解到能改造自动配置能带给你的额外能力自动配置原理搞透之后你获得的不只是“会写 Starter”这个技能而是一整套“系统行为可解释”的能力。Spring Boot 里许多看似黑盒的东西本质上都在自动配置的框架下运转。举几个我实际感受到收益的场景第一排查性能问题。项目启动慢可以看自动配置类哪些在无谓地加载。比如你引入了spring-boot-starter-data-jpa却一直用的是 MyBatis那HibernateJpaAutoConfiguration会在启动时做不少初始化动作白白消耗时间。这时候可以通过排除配置类优化启动速度SpringBootApplication(exclude { HibernateJpaAutoConfiguration.class })这个排除机制的底层就是AutoConfigurationImportSelector的exclude集合校验理解源码后你知道它是怎么生效的也更清楚哪些“排除后会导致功能失效”。第二写测试更精准。单元测试、集成测试里经常会对某一个自动配置类做定向加载用ContextConfiguration(classes ...)去模拟容器局部初始化。对自动配置类之间的依赖关系有清晰认知你才明白测试里该引入哪些前置配置。第三阅读第三方库源码不再发怵。Spring Cloud 的LoadBalancerAutoConfiguration、MyBatis 的MybatisAutoConfiguration、Redis 的RedisAutoConfiguration它们的源码结构其实都很相似一个自动配置入口类 一个或几个Bean方法 若干条件注解。掌握了整体的套路再复杂的库在你眼里也只是“多了几个组件 Bean 而已”。8. 最后分享几条实践的体会写自动配置、维护内部 Starter做到这里其实已经越过了“会用”的层面。我再根据自己的实操补充几条体会。第一条件注解不是越多越好。条件写得太细判断逻辑复杂反而容易出现误判和隐性问题。一个 Starter 的自动配置类我通常最多用三个条件ConditionalOnClass保证依赖存在、ConditionalOnProperty功能开关、ConditionalOnMissingBean允许覆盖。超过三个条件要重新审视设计。第二一定要保留自动配置的测试工程。Starter 改动后如果只是重新打包给业务方测试成本太高。我的做法是独立测试工程里覆盖三类场景无依赖环境、默认环境、用户自定义环境。每次改动跑一遍五分钟内得出结论比部署到业务方再排查快得多。第三spring-boot-configuration-processor一定要加并且标记为 optional。这个细节我前面提过再强调一次是因为很多人会在集成时漏掉。没有配置元数据业务方看不到配置提示使用体验差一大截。第四任何 Starter 的文档必须写上“配置项说明”和“不生效排查指引”。内部组件最容易出问题的地方不是写不出来而是业务方不知道怎么配置、出问题了不知道怎么查。我见过最好的一个团队内部 Starter 文档里面直接附了一张流程图条件报告在哪看、配置项如何对应、常见错误码的含义业务方照着图就能解决九成问题。第五遇到自己看不懂的自动配置逻辑用--debug启动看一下条件报告比网上搜答案快。条件报告是 Spring Boot 给开发者最实在的调试工具很多问题不用断点就能定位。这些经验不一定适配所有团队但对个人理解和排查能力的提升是实打实的。把这套东西吃透后再看 Spring Boot 生态里任何自动配置相关的组件你都能很快找到它的入口、条件和 Bean 注册逻辑不用再靠猜。
返回列表