【Spring Boot Starter 设计思考:分离模式是否适用于所有场景】

Spring Boot Starter 设计思考:分离模式是否适用于所有场景?

在 Spring Boot 生态中,starter + autoconfigure 的分离设计已成为广泛采用的标准模式。但当我们深入分析其在实际项目中的应用时,或许可以思考一下:这种设计是否真的是恰当的?

一、当前模式的分析

1. 官方及绝大多数流行框架的模式

当前大多数 Starter 采用这样的结构,其中 Starter 本身是一个空的聚合包,不包含任何实际代码:

xml 复制代码
<!-- mybatis-spring-boot-starter (空壳聚合包) -->
<dependencies>
    <dependency>
        <groupId>org.mybatis.spring.boot</groupId>
        <artifactId>mybatis-spring-boot-autoconfigure</artifactId>
    </dependency>
    <dependency>
        <groupId>org.mybatis</groupId>
        <artifactId>mybatis</artifactId>
    </dependency>
</dependencies>

同时在 autoconfigure 模块中需要重新声明依赖:

xml 复制代码
<!-- mybatis-spring-boot-autoconfigure -->
<dependencies>
    <dependency>
        <groupId>org.mybatis</groupId>
        <artifactId>mybatis</artifactId>
        <optional>true</optional>
    </dependency>
</dependencies>

这种设计带来的现象:

  • Starter 模块作为纯粹的依赖聚合空壳,不包含任何业务逻辑
  • 实际的自动配置逻辑位于独立的 autoconfigure 模块
  • 相同的依赖需要在两个位置声明,造成配置冗余

2. 自动配置类独立存在的思考

以 Redis 自动配置为例:

java 复制代码
// org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration

考虑这样的使用场景:

xml 复制代码
<!-- 用户只引入了基础库,未使用完整 starter -->
<dependency>
    <groupId>org.springframework.data</groupId>
    <artifactId>spring-data-redis</artifactId>
</dependency>

可能出现的情况:

  • 自动配置类检测到基础类存在,开始配置流程
  • 但由于缺少某些必要组件,配置可能无法完全生效
  • 这种"部分配置"的状态可能带来不确定性

二、自动配置类的本质思考

1. 自动配置类与 Starter 的天然联系

从功能角度看,自动配置类与 Starter 之间存在着紧密的关联:

  • 自动配置类的目的是让 Starter 能够开箱即用
  • Starter 的价值很大程度上通过自动配置类来体现
  • 两者在逻辑上是一个完整的功能单元

2. 关于 Condition 注解的思考

在当前的分离模式下,条件注解看起来是必要的:

java 复制代码
@ConditionalOnClass({RedisTemplate.class, JedisConnection.class})

但如果我们换一个角度思考:当自动配置类位于 Starter 内部时,这些条件检查是否还有必要?

如果自动配置类直接放在 Starter 包内:

java 复制代码
@Configuration
public class RedisAutoConfiguration {
    // 由于 Starter 已经包含了所有必要依赖,
    // @ConditionalOnClass 可能变得冗余
}

个人看法:

用户引入 Starter 的行为本身就表明了使用意图,Starter 的依赖管理已经确保了所需类的存在。在这种情况下,过多的 @ConditionalOnClass 检查反而可能增加了不必要的复杂性。

三、社区现状的分析

1. 广泛采用的模式

MyBatis、MyBatis-Plus、PageHelper 等流行项目都采用了与 Spring 官方相似的结构:

xml 复制代码
<!-- mybatis-spring-boot-starter -->
<dependencies>
    <dependency>
        <groupId>org.mybatis.spring.boot</groupId>
        <artifactId>mybatis-spring-boot-autoconfigure</artifactId>
    </dependency>
    <dependency>
        <groupId>org.mybatis</groupId>
        <artifactId>mybatis</artifactId>
    </dependency>
</dependencies>

这种一致性主要基于:

  • 学习成本考虑:开发者已经熟悉这种组织结构
  • 生态一致性:保持与其他组件的统一结构
  • 风险规避:遵循经过验证的实践方案

2. Spring 官方的特殊考量

Spring 团队采用集中式的 autoconfigure 设计,可能基于以下考虑:

  • 统一管理:协调大量第三方库的适配
  • 版本控制:统一管理配置类的兼容性
  • 框架生态:维护整个 Spring Boot 生态的一致性

但这种针对框架级别的设计考量,并不一定适用于所有的自研 Starter 场景。

四、自研 Starter 的替代方案思考

1. 内聚式设计

对于自研的 Starter,可以考虑采用更加内聚的设计:

复制代码
company-spring-boot-starter/
├── src/main/java
│   └── com/company/autoconfigure/
│       ├── CompanyAutoConfiguration.java
│       └── CompanyProperties.java
├── src/main/resources
│   └── META-INF
│       ├── spring.factories
│       └── spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
└── pom.xml

2. 简化的依赖管理

只需要在 Starter 中声明一次依赖:

xml 复制代码
<dependencies>
    <dependency>
        <groupId>com.company</groupId>
        <artifactId>company-core</artifactId>
    </dependency>
    <!-- 其他运行时依赖 -->
</dependencies>

这样可以避免:

  • 独立的 autoconfigure 模块创建和维护
  • 重复的依赖声明
  • optional 依赖的复杂管理

3. 合理的条件注解使用

在内聚式设计中,可以更加有选择地使用条件注解:

java 复制代码
@Configuration
@AutoConfigureAfter(DataSourceAutoConfiguration.class)
public class CompanyAutoConfiguration {
    
    @Bean
    @ConditionalOnMissingBean  // 保留:允许用户自定义
    public CompanyService companyService() {
        return new DefaultCompanyService();
    }
    
    @Bean
    @ConditionalOnProperty(prefix = "company", name = "feature.enabled")
    public FeatureService featureService() {
        return new FeatureService();
    }
}

个人建议:

  • 保留 @ConditionalOnMissingBean 尊重用户自定义
  • 保留 @ConditionalOnProperty 支持功能开关
  • 保留 @AutoConfigureAfter 确保配置顺序
  • 酌情减少 @ConditionalOnClass 的使用

五、对传统分离模式的重新审视

分离模式的实际价值有限

从实际应用角度来看,传统分离模式的优势场景相当有限:

  • Spring 官方场景:仅当需要统一管理几十个不同组件的配置和版本时,集中式的 autoconfigure 包才有其管理上的便利性
  • 第三方组件场景:对于大多数独立的第三方组件来说,这种分离带来的更多是复杂性而非价值

内聚式设计的普适优势

相比之下,内聚式设计在大多数场景下都表现出更好的特性:

  • 架构简洁:减少不必要的模块分层,降低系统复杂度
  • 维护便利:所有相关代码集中在同一模块,便于理解和修改
  • 依赖明确:避免重复的依赖声明,减少配置错误可能性
  • 意图清晰:Starter 作为一个完整的功能单元,职责单一明确

六、总结与建议

经过对 Spring Boot Starter 设计模式的深入分析,我认为传统的分离模式在大多数应用场景下可能并非最优选择。

核心观点:

  • 自动配置类本质上与其对应的 Starter 是高度内聚的,强行分离反而引入不必要的复杂性
  • 对于绝大多数项目(特别是自研 Starter),将自动配置类直接包含在 Starter 包内是更加简单直接的设计
  • 只有在类似 Spring 官方需要统一管理大量第三方组件配置的特殊场景下,集中式的 autoconfigure 设计才有其合理性

实践建议:

在开发自研 Starter 时,建议优先考虑内聚式设计。这种设计不仅减少了模块间的依赖关系,简化了构建配置,还使得代码结构更加直观易懂。只有当确实需要支持高度灵活的可选依赖组合时,才考虑采用传统的分离模式。

技术决策应当基于实际需求和具体场景,而非盲目遵循某种"标准做法"。在理解现有设计背后的考量的同时,保持批判性思维,选择最适合自己项目的技术方案,这才是技术人应有的态度。

相关推荐
Victor3563 小时前
Redis(97)Redis的日志文件如何管理?
后端
Victor3563 小时前
Redis(96)Redis的备份和恢复策略如何配置?
后端
Jack电子实验室3 小时前
深入理解C语言函数指针:从基础到实战应用
java·c语言·算法
用户21411832636023 小时前
AI 驱动教学革命:3 分钟生成专业级动画课件,还能导出视频 GIF!
后端
小马哥编程3 小时前
【软考架构】案例分析-系统设计与建模:数据流图DFD与数据字典
java·数据库·架构·统一建模语言
sibylyue3 小时前
Spring编程式事务和声明式事务
java·数据库·mysql
程序员爱钓鱼3 小时前
Python编程实战 | 函数与模块化编程 - 第三方库的安装与管理(pip使用)
后端·python·ipython
程序员爱钓鱼3 小时前
Python编程实战 | 面向对象与进阶语法-类与对象的概念
后端·python·ipython
伊布拉西莫4 小时前
spring-ai advisors 使用与源码分析
java·人工智能·spring