1. 背景
当前在开发一个 SDK2 的组件。为了复用代码,引入了公司内部的基础组件 SDK1 ,这个 SDK1 里包含了通用的分页切面 (PaginationAspect) 功能。
在开发 SDK2 的过程中,我写了一个简单的测试项目来验证功能。我的预期是:
只要我的 SDK2 依赖了 SDK1,使用 SDK2 的用户(包括我的测试项目)就应该自动拥有分页功能。
然而,分页切面根本没生效,完全忽略了分页注解。
2. 问题排查
2.1 依赖关系
Maven 依赖结构非常清晰,典型的传递依赖:
text
Test Project (测试项目)
└─> 依赖 SDK2
└─> 依赖 SDK1
2.2 现象
我在测试项目中启动 Spring Boot时发现:
- 代码能编译 :我能 import SDK1 里的类,说明 Maven 依赖传递是成功的,Jar 包确实在 Classpath 里。
- 功能失效:SDK1 里的 AOP 切面没有执行。
- 尝试修复:如果直接在pom.xml加入SDK1的依赖项分页可以生效。
但作为 SDK2 的开发者,不能要求每一个使用我组件的用户都去手动引入 SDK1 组件。这违背了 Spring Boot "开箱即用" 的原则。
2.3 根因定位:SPI 文件的资源覆盖
经过仔细对比 Spring Boot 的加载流程,发现了一个认知误区:
误区: 以为 Maven 把 SDK1 的 Jar 包拉进来了,Spring 就会自动加载里面的 Bean。
真相: Maven 确实把类搬运到了 Classpath,但 Spring Boot 不知道要去加载 SDK1 里的配置类
Spring Boot 的自动配置依赖于 SPI(Service Provider Interface) 机制,它通过读取所有 Jar 包下固定的 META-INF/spring/.../AutoConfiguration.imports 文件来发现配置类。
问题就出在这里:
SDK1 有自己的 AutoConfiguration.imports 文件。
SDK2 也有自己的 AutoConfiguration.imports 文件。
当 Maven 构建最终的应用时,它会把所有依赖的资源文件放到 Classpath 下。当遇到两个路径完全相同的文件时,默认策略是覆盖,而不是追加。
3. 解决方案
这里介绍一种跨包扫描法 。这个方案的核心思想是,既然 Spring 已经加载了 SDK2 的配置类 SecurityComponentConfig,那我就在这个配置类上加一个指令,让 Spring 的扫描器把 SDK1 的包也扫描一遍。
代码实现
在 SDK2 的核心配置类 SecurityComponentConfig 上添加 @ComponentScan 注解,并扩大其扫描范围:
java
// 手动扩大扫描范围,包含了 SDK1 的包路径 "com.cmgii.cbb.http.response"
@ComponentScan(basePackages = {
"SDK2", // SDK2 自己的包
"SDK1" // 【添加】SDK1 的包路径
})
@MapperScan("com.cmgii.cbb.chain.mapper")
@Configuration
public class SecurityComponentConfig {
// ...
}
4. 总结
Maven 依赖解决的是"类路径"问题,而 Spring Bean 加载解决的是"容器管理"问题,两者不能混为一谈。
| 对比维度 | Maven 依赖 | Spring Bean 加载 |
|---|---|---|
| 作用 | 确保 .class 文件在编译和运行时能被找到。 |
确保类的实例被 Spring 创建并管理。 |
| 生效方式 | pom.xml 声明。 |
@ComponentScan 扫描或 SPI 机制导入。 |
| 核心误区 | 以为引入了 Jar 包,Spring 就会自动接管。 | 忘了告诉 Spring 去哪里找 Bean,或者如何加载。 |
作为 SDK 的开发者,我们不仅要确保依赖关系的正确性,更要主动管理好组件的生命周期和配置加载。在追求"开箱即用"的用户体验时,像 @ComponentScan 这样的工具虽然直接,但也要权衡其带来的耦合性问题。