Repository navigation
Guide Add Auto Configuration
本页回答:一个自动配置类该放在哪个模块、怎么注册、怎么写条件与顺序、怎样保证默认实现可被业务替换,以及自动配置“不生效”时按什么顺序排查。 所有模式都来自仓库里的真实类,括号中给出类名或路径。
example/Example是占位符。与源码不一致之处会以“文档与实现存在出入:…”标出,请以句末给出的权威来源为准。
| 场景 | 放置位置 | 依据 |
|---|---|---|
| 某个 Feature 自己的装配(属性、默认 Bean、过滤器、拦截器) | 该 Feature 模块(coco-features/coco-<x>) |
AGENTS.md:starter 不承载行为;通用模块不依赖具体 Feature |
| 跨 Feature 的公共装配:国际化、日志、Feature 计划、异步、Jackson、启动横幅 | coco-spring/coco-spring-boot-autoconfigure |
该模块现有 8 个自动配置(见 架构总览) |
需要在 Spring Boot 很早期介入:SpringApplicationRunListener、EnvironmentPostProcessor、AutoConfigurationImportFilter
|
coco-spring-boot-autoconfigure 的 META-INF/spring.factories
|
现有三个扩展点 |
| 只组合依赖、不含行为 |
coco-spring-boot-starter(仅 pom,无主代码) |
AGENTS.md |
调整 autoconfigure 中既有自动配置时要特别小心:迁移期保留了旧的包名与 FQCN(io.github.coco.config、io.github.coco.feature.runtime.*、io.github.coco.feature.registry、io.github.coco.common.*),module-layout.md 规定公开 FQCN、配置前缀、feature id、插件 goal 不得因重命名而改变。包名保留不等于模块仍在旧 jar 里。
每个模块在 src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports 里每行写一个自动配置 FQCN。例(coco-captcha):
io.github.coco.captcha.CocoCaptchaRedisAutoConfiguration
io.github.coco.captcha.CocoCaptchaRedisMissingDependencyAutoConfiguration
io.github.coco.captcha.CocoCaptchaAutoConfiguration
coco-web 比较特殊:CocoWebAutoConfiguration 用 @Import 汇入 9 个内部配置(CocoWebI18nAutoConfiguration、CocoWebResponseAutoConfiguration、CocoWebExceptionAutoConfiguration、CocoWebRequestBodyAutoConfiguration、CocoWebContextAutoConfiguration、CocoWebTraceAutoConfiguration、CocoWebSignatureAutoConfiguration、CocoWebEncryptionAutoConfiguration、CocoWebReplayAutoConfiguration),这些内部配置不在 .imports 里单独登记。
仅用于早期扩展点。coco-spring-boot-autoconfigure 当前注册:
| 键 | 实现 |
|---|---|
org.springframework.boot.SpringApplicationRunListener |
io.github.coco.spring.boot.banner.CocoSpringApplicationRunListener |
org.springframework.boot.EnvironmentPostProcessor |
io.github.coco.spring.boot.logging.CocoLoggingEnvironmentPostProcessor |
org.springframework.boot.autoconfigure.AutoConfigurationImportFilter |
io.github.coco.feature.runtime.autoconfigure.CocoFeatureAutoConfigurationImportFilter |
同一个自动配置不能在多个 jar 里各登记一次。兼容门面(coco-config、coco-feature-runtime 等)严禁复制 .imports 或 spring.factories。CocoSpringCompositionClasspathTest(coco-spring/coco-spring-boot-starter)在 starter 与兼容门面同在 classpath 时,断言迁入 autoconfigure 的 5 个自动配置(CocoCommonAutoConfiguration、CocoCommonLoggingAutoConfiguration、CocoFeatureRegistryAutoConfiguration、CocoConfigAutoConfiguration、CocoFeatureRuntimeAutoConfiguration)以及上述 3 个 spring.factories 扩展点都只注册一次。该测试没有覆盖 CocoAutoConfiguration、CocoAsyncAutoConfiguration、CocoJacksonAutoConfiguration 三个;是否有意不覆盖,设计意图未见文档说明,行为以 CocoSpringCompositionClasspathTest.java 为准。
依据 CocoLockAutoConfiguration、CocoIdempotencyAutoConfiguration、CocoSecurityAutoConfiguration 总结(示意代码,不能直接复制编译,需按真实类型调整):
@AutoConfiguration
@EnableConfigurationProperties(CocoExampleProperties.class)
@ConditionalOnProperty(prefix = "coco.example", name = "enabled", havingValue = "true")
public class CocoExampleAutoConfiguration {
// 1) 每个模块一个消息包注册器 Bean
@Bean
@ConditionalOnMissingBean(name = "cocoExampleMessageBundleRegistrar")
public CocoMessageBundleRegistrar cocoExampleMessageBundleRegistrar() {
return registry -> registry.add("coco-example-messages");
}
// 2) 默认实现,业务方可通过自定义 Bean 替换
@Bean
@ConditionalOnMissingBean
public CocoExampleKeyResolver cocoExampleKeyResolver(CocoExampleProperties properties) {
return new DefaultCocoExampleKeyResolver(properties);
}
// 3) 默认存储:in-memory(属性缺省或显式 in-memory 时生效)
@Bean(destroyMethod = "close")
@ConditionalOnMissingBean(CocoExampleStore.class)
@ConditionalOnProperty(prefix = "coco.example", name = "store-type",
havingValue = "in-memory", matchIfMissing = true)
public CocoExampleStore cocoExampleStore(CocoExampleProperties properties) {
return new InMemoryCocoExampleStore(properties);
}
}若该 Feature 要由 Feature 计划门控,再加 @ConditionalOnCocoFeature(CocoFeature.EXAMPLE)(需要模块依赖 coco-spring-boot-autoconfigure,是否使用的取舍见 Feature 机制)。
| 目的 | 写法 | 真实例子 |
|---|---|---|
| 按 Feature 计划门控 | @ConditionalOnCocoFeature(CocoFeature.X) |
CocoSecurityAutoConfiguration、CocoIdempotencyAutoConfiguration 等 9 个 Feature |
| 模块级开关(默认关闭) | @ConditionalOnProperty(prefix = "coco.x", name = "enabled", havingValue = "true") |
CocoLockAutoConfiguration、CocoIdempotencyAutoConfiguration
|
| 模块级开关(缺省开启) | 同上 + matchIfMissing = true
|
CocoSchedulingAutoConfiguration、CocoMessagingAutoConfiguration
|
| 仅 Servlet Web 应用 |
@ConditionalOnWebApplication(type = SERVLET),必要时叠加 @ConditionalOnClass(Filter.class)
|
CocoIdempotencyAutoConfiguration;CocoSecurityAutoConfiguration 中的 Bean 方法 |
| 可选依赖存在才装配 | @ConditionalOnClass(...) |
CocoTenantMybatisPlusAutoConfiguration(TenantLineInnerInterceptor、CocoMybatisPlusInterceptorCustomizer) |
| 按 Bean 是否存在装配 | @ConditionalOnBean |
CocoOpenApiAutoConfiguration 的 cocoSpringDocOpenApiCustomizer
|
| 在某个自动配置之后 | @AutoConfiguration(after = X.class) |
CocoAuditAutoConfiguration(after CocoCommonLoggingAutoConfiguration) |
| 在某个自动配置之前 |
@AutoConfiguration(before = X.class) 或 @AutoConfigureBefore
|
CocoWebAutoConfiguration(before CocoCommonAutoConfiguration);CocoIdempotencyAutoConfiguration(@AutoConfigureBefore(CocoWebContextAutoConfiguration.class)) |
| 引用本模块依赖中没有的类 |
afterName = {"全限定类名"} 字符串形式 |
CocoOpenApiAutoConfiguration(after CocoWebAutoConfiguration、CocoSecurityAutoConfiguration;pom 中并没有依赖 coco-web / coco-security) |
| 夹在第三方自动配置之间 |
after = ... + before = ...
|
CocoMybatisPlusAutoConfiguration(after MybatisPlusAutoConfiguration,before MybatisPlusInnerInterceptorAutoConfiguration) |
| 多个自动配置有先后 | after = {A.class, B.class} |
CocoTenantMybatisPlusAutoConfiguration(after CocoTenantAutoConfiguration、CocoMybatisPlusAutoConfiguration) |
- 默认实现一律
@ConditionalOnMissingBean(按类型);有固定名称语义的用@ConditionalOnMissingBean(name = "..."),如cocoExampleClock。 - 消息包注册器 Bean 的名字形如
coco<Module>MessageBundleRegistrar,并用@ConditionalOnMissingBean(name = ...)。CocoCommonAutoConfiguration通过ObjectProvider<CocoMessageBundleRegistrar>收集全部注册器,再追加基础包coco-messages构造cocoMessageSource。 - 具有“策略”性质的组件(解析器、存储、写出器、验证器)都应是接口 +
Default*/InMemory*/Redis*实现;业务方提供同类型 Bean 即可替换。 - 面向业务的 SPI 保持小而稳(
AGENTS.md):没有真实替换点就不要新增接口。 - 安全相关默认值要保守,并在启用危险能力时给出日志:
CocoSecurityAutoConfiguration在coco.security.web.header.enabled=true时打印 WARN,说明该解析器信任上游 HTTP 头,不得直接暴露公网。
-
@ConfigurationProperties("coco.<x>")(或prefix = "coco.<x>"),配合@EnableConfigurationProperties; - 属性键 kebab-case;默认值合理、危险能力默认关闭;
- 属性类放哪个模块有两种现状:Feature 的属性类放在 Feature 模块里;
CocoCommonProperties(coco-i18n)与CocoLoggingProperties(coco-logging)在 foundation 模块,却在 autoconfigure 的CocoCommonAutoConfiguration/CocoCommonLoggingAutoConfiguration里用@EnableConfigurationProperties启用。新代码请与目标模块的现有做法一致; - IDE 提示:有的模块提供
META-INF/additional-spring-configuration-metadata.json,其余依赖spring-boot-configuration-processor(optional)。
适用于 rate-limit、idempotency、lock、cache、captcha 与 web replay。以 coco-lock 为准:
| 类 | 作用 | 关键条件 |
|---|---|---|
CocoLockRedisAutoConfiguration |
提供 RedisCocoLockStore
|
@AutoConfiguration(before = CocoLockAutoConfiguration.class);coco.lock.enabled=true;coco.lock.store-type=redis;@ConditionalOnClass(StringRedisTemplate.class);@ConditionalOnMissingBean(CocoLockStore.class)
|
CocoLockRedisMissingDependencyAutoConfiguration |
Redis 类缺失时 fail-fast:Bean 方法直接抛 BeanCreationException,信息为 coco.lock.store-type=redis requires ... StringRedisTemplate on the runtime classpath
|
@ConditionalOnMissingClass("org.springframework.data.redis.core.StringRedisTemplate");enabled=true;store-type=redis
|
CocoLockAutoConfiguration |
主配置,in-memory 默认存储 |
store-type=in-memory 且 matchIfMissing = true
|
要点:
- Redis 配置通过
before排在主配置之前,主配置的默认存储再用@ConditionalOnMissingBean(CocoLockStore.class)让位; -
StringRedisTemplate的选择:优先用coco.lock.redis.template-bean-name指定的 Bean;否则要求只有一个候选或恰好一个@Primary,否则抛BeanCreationException; -
coco-cache的条件是store-type=two-level(不是redis),默认LOCAL; - 三个类都要写进
.imports,顺序按 Redis、MissingDependency、主配置(见coco-captcha); - 对应测试见
CocoLockAutoConfigurationTest、CocoCaptchaAutoConfigurationTest、CocoCacheAutoConfigurationTest。
需要给 MyBatis-Plus 拦截器链追加内部拦截器的 Feature(tenant、data-permission),不直接创建 MybatisPlusInterceptor,而是提供一个 CocoMybatisPlusInterceptorCustomizer Bean(@FunctionalInterface,位于 coco-mybatis-plus):
@Bean
@ConditionalOnMissingBean(name = "cocoTenantMybatisPlusInterceptorCustomizer")
public CocoMybatisPlusInterceptorCustomizer cocoTenantMybatisPlusInterceptorCustomizer(...) {
return interceptor -> {
interceptor.addInnerInterceptor(interceptorIgnoreGuard);
interceptor.addInnerInterceptor(new TenantLineInnerInterceptor(tenantLineHandler));
};
}该 Bean 所在的自动配置要:after 排在 CocoMybatisPlusAutoConfiguration 之后;用 @ConditionalOnClass 保护对 coco-mybatis-plus(optional 依赖)和 MyBatis-Plus 类的引用;仍然受 @ConditionalOnCocoFeature(TENANT / DATA_PERMISSION) 与 SQL 开关属性(coco.tenant.sql.enabled 缺省开启,coco.data-permission.sql.enabled 缺省关闭)控制。参考 CocoTenantMybatisPlusAutoConfiguration、CocoDataPermissionMybatisPlusAutoConfiguration。
- Servlet 过滤器:用
FilterRegistrationBean显式设置名称与顺序,例如CocoSecurityAutoConfiguration注册cocoSecurityWebFilter,order = Ordered.HIGHEST_PRECEDENCE + 5,Bean 名cocoSecurityWebFilterRegistration(@ConditionalOnMissingBean(name = ...))。 - MVC 拦截器:提供
WebMvcConfigurerBean 并在addInterceptors中设置顺序,例如CocoIdempotencyAutoConfiguration的MVC_INTERCEPTOR_ORDER = Ordered.HIGHEST_PRECEDENCE + 10。 - 顺序常量请集中定义并写注释,避免与
coco-web的 Trace、上下文、签名、重放等过滤器产生隐含依赖。
coco-spring-boot-starter 是业务应用唯一的常规 starter 依赖,它只组合依赖,没有主代码(src/main 下没有任何文件)。因此“给 starter 加东西”实际上只有两类改动:
| 目的 | 做法 | 注意 |
|---|---|---|
| 让新 Feature 模块随 starter 进入业务应用 | 在 coco-spring/coco-spring-boot-starter/pom.xml 追加 <dependency>
|
根 pom 没有登记的模块要显式写 <version>${project.version}</version>(现状:scheduling、lock、storage、messaging、cache、notification、captcha 就是这样);是否应进入 starter 的取舍见 新增一个 Feature 模块
|
| 保证某个模块不会进入 starter | 不加依赖,并补契约测试 | 仿照 CocoRateLimitStarterDependencyContractTest(断言 starter 直接依赖不含 coco-rate-limit、coco-idempotency);规范制品与兼容门面的关系用 CocoMybatisPlusStarterDependencyContractTest 钉住(starter 依赖 coco-mybatis-plus,而不是 coco-feature-mybatis-plus) |
不要做的事:
- 不要在 starter 里新增
@Configuration、Bean 或META-INF/spring/*.imports(AGENTS.md:starter 应组合能力、不承载行为);装配代码放进 autoconfigure 或对应 Feature 模块; - 不要通过兼容门面(
coco-config、coco-feature-runtime、coco-feature-*)间接获得能力;starter 对这两个旧坐标只以 test 作用域引用,用来证明共存时自动配置只注册一次。
验证 starter 改动:
-
CocoStarterSmokeIT(coco-spring/coco-spring-boot-starter/src/test/java/io/github/coco/spring/boot/)以 starter 默认值启动一个 Servlet 应用,断言cocoStartupBannerBean 存在,且能取到CocoMessageService与CocoWebRequestContextResolver; -
CocoSpringCompositionClasspathTest(见 §2.3)与上述两个依赖契约测试; - 命令(来自
AGENTS.md的聚焦验证写法):mvn -pl :coco-spring-boot-starter -am verify(verify才会运行 Failsafe 的 IT)。
使用 ApplicationContextRunner(Servlet 场景 WebApplicationContextRunner):
private final ApplicationContextRunner runner = new ApplicationContextRunner()
.withConfiguration(AutoConfigurations.of(CocoExampleAutoConfiguration.class));- 断言默认 Bean 存在、
enabled=false时不装配、自定义 Bean 覆盖默认实现; - 用
FilteredClassLoader模拟 classpath 缺类(Redis、MyBatis-Plus 等),断言 fail-fast 或降级行为; - 涉及消息包时,在 runner 中加入
CocoCommonAutoConfiguration,参考CocoMybatisPlusAutoConfigurationTest、CocoDataPermissionAutoConfigurationTest; - 修改了
.imports/spring.factories时,同时检查CocoSpringCompositionClasspathTest是否仍通过。
更多约定见 指南:测试约定。
-
Feature 是否被禁用:查看启动日志
Coco features resolved from ...中的enabled/disabled/disabledByDependency;注意features.json(构建期产出)优先于运行期属性。详见 Feature 机制。 -
AutoConfigurationImportFilter是否过滤:mybatis-plus未启用时会过滤 MyBatis-Plus 的 5 个自动配置,以及在无数据源配置、无嵌入式驱动时过滤 Spring Boot 的 3 个数据源自动配置。 -
模块级属性:
coco.<x>.enabled的默认值(默认关闭的模块必须显式开启);Redis 相关还要看store-type。 -
条件注解:
@ConditionalOnWebApplication、@ConditionalOnClass、@ConditionalOnBean是否满足;可以打开 Spring Boot 的条件评估报告(通用做法:启动参数--debug或属性debug=true,本仓库未专门配置)。 -
是否注册:模块的
.imports是否包含该 FQCN;META-INF是否被打进 jar;模块是否被coco:prune-package裁剪掉(查看BOOT-INF/lib)。 -
顺序:
before/after是否导致默认 Bean 先于自定义 Bean 注册,或@ConditionalOnMissingBean判定提前。 -
是否有重复注册:兼容门面或其他 jar 是否复制了
.imports。
仓库 · CHANGELOG · CONTRIBUTING · GOVERNANCE · SECURITY · 许可证 Apache-2.0
本 Wiki 镜像自仓库的 docs/wiki 目录,请通过 PR 修改,不要在网页上直接编辑。
开始
架构
模块参考
约定与指南
速查与排障
仓库