当前位置: 首页 > news >正文

SpringBoot多模块依赖冲突排查与架构优化实战(避坑指南)

目录

一、项目初始结构(问题场景)

二、核心问题排查与解决方案(按优先级)

问题1:tomcat-embed-core版本冲突(父模块10.1.54,子模块10.1.42)

1.1 问题根源

1.2 解决方案(父模块优化)

问题2:spring-security-web版本升级不生效(需升级至6.5.10)

2.1 问题根源

2.2 解决方案(调整父模块bom导入顺序)

问题3:非公共依赖(security)污染无关模块(C/D)

3.1 问题根源

3.2 解决方案(模块化拆分,按需依赖)

三、最终优化后的标准架构(企业级最佳实践)

四、关键注意点(避坑核心)

五、总结


前言:在SpringBoot多模块项目开发中,依赖冲突、版本不一致、模块依赖设计不合理是最常见的“拦路虎”。本文结合实际项目场景,记录从依赖版本冲突(tomcat、spring-security)到模块架构优化的完整排查、解决过程,总结企业级多模块最佳实践,帮助更多开发者避坑,提升项目可维护性。

本文核心解决场景:父模块+通用模块A+业务模块BCD的多模块结构中,依赖版本冲突、非公共依赖污染无关模块、模块依赖设计不合理等问题,全程贴合实际开发场景,提供可直接复用的解决方案。

一、项目初始结构(问题场景)

先明确项目初始结构,也是问题的起源,方便大家对号入座:

父项目(parent) ├── A模块(通用模块):包含service、dao、工具类,引入mybatis-plus、redis、spring-boot-starter-security等 ├── B模块(业务模块):依赖A模块,需要security功能 ├── C模块(业务模块):依赖A模块,不需要security功能 └── D模块(业务模块):依赖A模块,不需要security功能

初始问题汇总:

  • 1. 父模块在dependencies中直接引入tomcat-embed-core、spring-boot-starter-web(带排除),导致子模块出现tomcat版本冲突(父模块10.1.54,A模块传递10.1.42);

  • 2. A模块引入spring-boot-starter-security,需升级spring-security-web至6.5.10,但父模块锁定后不生效,A/B模块版本不一致;

  • 3. A模块引入的security依赖,传递给不需要的C/D模块,造成依赖冗余;

  • 4. 父模块未遵循“只管理版本,不引入依赖”的原则,导致依赖传递混乱。

二、核心问题排查与解决方案(按优先级)

以下解决方案按“先解决紧急冲突,再优化架构”的顺序,每一步都贴合实际操作,可直接复制配置使用。

问题1:tomcat-embed-core版本冲突(父模块10.1.54,子模块10.1.42)

1.1 问题根源

父模块在dependencies中直接引入tomcat-embed-core:10.1.54,同时父模块还引入了spring-boot-starter-web(排除了tomcat);A模块引入spring-boot-starter-web,自带tomcat-embed-core:10.1.42(SpringBoot 3.3.13默认版本);BCD模块依赖A模块,导致传递引入10.1.42,与父模块的10.1.54冲突。

关键原因:父模块直接在dependencies中引入依赖,优先级低于SpringBoot自带的版本管理;且未通过dependencyManagement统一锁定版本。

1.2 解决方案(父模块优化)

核心原则:父模块只通过dependencyManagement管理版本,不直接在dependencies中引入任何依赖。

<properties> <spring-boot.version>3.3.13</spring-boot.version> <tomcat.version>10.1.54</tomcat.version> <!-- 统一tomcat版本 --> </properties> <dependencyManagement> <dependencies> <!-- Spring Boot 官方依赖管理 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-dependencies</artifactId> <version>${spring-boot.version}</version> <type>pom</type> <scope>import</scope> </dependency> <!-- 强制锁定tomcat版本,覆盖SpringBoot默认版本 --> <dependency> <groupId>org.apache.tomcat.embed</groupId> <artifactId>tomcat-embed-core</artifactId> <version>${tomcat.version}</version> </dependency> </dependencies> </dependencyManagement> <!-- 父模块dependencies标签为空,不引入任何依赖 --> <dependencies> </dependencies>

后续操作:删除父模块中所有直接引入的依赖(tomcat-embed-core、spring-boot-starter-web),A模块正常引入spring-boot-starter-web即可,无需排除tomcat,会自动继承父模块锁定的10.1.54版本。

问题2:spring-security-web版本升级不生效(需升级至6.5.10)

2.1 问题根源

A模块引入spring-boot-starter-security,父模块通过dependencyManagement锁定spring-security.version=6.5.10,但版本不生效,A/B模块仍显示6.3.10(SpringBoot 3.3.13默认版本)。

关键原因:spring-security-bom的导入顺序在spring-boot-dependencies之后,Maven规则“先导入的bom优先级更高”,导致SpringBoot自带的版本覆盖了我们锁定的版本。

2.2 解决方案(调整父模块bom导入顺序)
<properties> <spring-boot.version>3.3.13</spring-boot.version> <tomcat.version>10.1.54</tomcat.version> <spring-security.version>6.5.10</spring-security.version> <!-- 统一security版本 --> </properties> <dependencyManagement> <dependencies> <!-- 重点:spring-security-bom放前面,优先级高于SpringBoot自带版本 --> <dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-bom</artifactId> <version>${spring-security.version}</version> <type>pom</type> <scope>import</scope> </dependency> <!-- Spring Boot 官方依赖放后面 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-dependencies</artifactId> <version>${spring-boot.version}</version> <type>pom</type> <scope>import</scope> </dependency> <!-- tomcat版本锁定 --> <dependency> <groupId>org.apache.tomcat.embed</groupId> <artifactId>tomcat-embed-core</artifactId> <version>${tomcat.version}</version> </dependency> </dependencies> </dependencyManagement>

验证方法:在A/B模块目录执行命令mvn dependency:tree -Dincludes=org.springframework.security:spring-security-web,输出结果中版本为6.5.10即生效。

注意:无需在A模块做任何排除操作,保持spring-boot-starter-security的正常引入即可。

问题3:非公共依赖(security)污染无关模块(C/D)

3.1 问题根源

A模块作为通用模块,引入了只有B模块需要的spring-boot-starter-security,导致依赖A的C/D模块也被动引入了security,造成冗余和不必要的依赖污染。

关键原因:模块依赖设计不合理,将“非公共依赖”放入了通用模块A中。

3.2 解决方案(模块化拆分,按需依赖)

核心原则:通用模块A只放“所有子模块(BCD)都需要”的依赖,非公共依赖由需要的模块自己引入。

  1. 第一步:清理A模块,移除spring-boot-starter-security依赖(A模块只保留mybatis-plus、redis、dao、service等公共依赖);

  2. 第二步:B模块自己引入spring-boot-starter-security(因为只有B需要);

  3. 第三步:C/D模块只依赖A模块,不引入任何多余依赖。

具体配置:

<!-- A模块pom.xml(清理后) --> <dependencies> <!-- 公共依赖:BCD都需要 --> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> </dependency> <!-- 其他公共service、dao相关依赖 --> </dependencies> <!-- B模块pom.xml(自己引入security) --> <dependencies> <!-- 依赖通用模块A --> <dependency> <groupId>xxx</groupId> <artifactId>A</artifactId> </dependency> <!-- 自己需要的security,不传递给C/D --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency> </dependencies> <!-- C/D模块pom.xml(只依赖A) --> <dependencies> <dependency> <groupId>xxx</groupId> <artifactId>A</artifactId> </dependency> </dependencies>

效果:B模块有security功能,C/D模块无security依赖,彻底解决依赖污染,无需任何排除操作。

三、最终优化后的标准架构(企业级最佳实践)

优化后结构,彻底解决所有问题,兼顾复用性和可维护性:

父项目(parent) ├── 核心职责:只通过dependencyManagement管理所有依赖版本,dependencies为空 ├── A模块(core-service,通用模块) │ ├── 核心职责:提供公共数据层、服务层能力,所有子模块都需要 │ ├── 依赖:mybatis-plus、redis、数据库驱动、公共entity/dao/service/工具类 │ └── 不包含:web、security等非公共依赖 ├── B模块(业务模块) │ ├── 依赖:A模块 + 自身需要的security等依赖 │ └── 核心职责:业务逻辑实现(需要security) ├── C模块(业务模块) │ ├── 依赖:只依赖A模块 │ └── 核心职责:业务逻辑实现(不需要security) └── D模块(业务模块) ├── 依赖:只依赖A模块 └── 核心职责:业务逻辑实现(不需要security)

四、关键注意点(避坑核心)

结合本次排查,总结10个高频避坑点,覆盖依赖管理和模块设计:

  1. 父模块核心原则:只管理版本,不引入依赖,所有依赖通过dependencyManagement锁定,dependencies标签为空,避免子模块被动继承不必要的依赖;

  2. bom导入顺序:自定义版本的bom(如spring-security-bom)必须放在spring-boot-dependencies前面,否则会被SpringBoot默认版本覆盖;

  3. 通用模块A的定位:只放“所有子模块都需要”的依赖,非公共依赖(如security、web)绝对不放入;

  4. 版本统一:所有核心依赖(tomcat、security、mybatis-plus等)在父模块properties中定义变量,统一管理,避免硬编码;

  5. 依赖传递:子模块依赖通用模块时,会自动继承其所有依赖,因此通用模块必须“干净”,不引入非公共依赖;

  6. 避免排除滥用:排除(exclusion)只用于临时救急,长期解决方案是“按需依赖”,拆分模块,而非大量使用排除;

  7. 版本验证:修改依赖后,用mvn dependency:tree命令验证版本是否正确,避免隐性冲突;

  8. SpringBoot与组件版本兼容性:SpringBoot 3.3.13默认对应spring-security 6.3.x,升级至6.5.x需确认API兼容性(本次实践无问题);

  9. 模块命名规范:通用模块建议命名为core-service、common-core等,明确其定位,避免混淆;

  10. 长期维护:定期清理无用依赖,保持模块“瘦身”,避免依赖冗余导致的冲突和性能问题。

五、总结

SpringBoot多模块项目的核心痛点的是“依赖冲突”和“模块设计不合理”,本次实践通过“父模块版本统一管理+通用模块瘦身+按需依赖”,彻底解决了tomcat、spring-security版本冲突,以及非公共依赖污染问题。

核心思路:模块化设计的本质是“职责清晰、按需依赖”,父模块管版本,通用模块管公共能力,业务模块管自身需求,这样既能保证代码复用,又能避免依赖混乱,提升项目可维护性。

本文所有配置均经过实际项目验证,可直接复制使用,若遇到类似问题,可对照排查,也欢迎在评论区交流补充。

结尾彩蛋:关注我,后续分享更多SpringBoot多模块实战技巧,避坑不踩雷!

http://www.jsqmd.com/news/712648/

相关文章:

  • 走进涠洲岛环岛路,解锁火山海岸原生态风光
  • 气动阀环保的品牌有哪些好用的,无锡华通气动值得选吗 - 工业品牌热点
  • 刷到无数农村帮扶暖心瞬间,藏着最朴素的善意与坚守
  • 如何增加网站外链?实测月增500点击,附发件模板与耗时明细
  • LLM长时上下文管理的KV缓存优化与压缩策略
  • 【图像加密解密】XOR和置乱和Arnold变换图像加解密【含GUI Matlab源码 15385期】
  • 多语言大模型安全漏洞分析与防御实践
  • Speech Seaco Paraformer实战案例:如何用热词功能提升专业术语识别率
  • 常见问题解决 --- 装有系统的固态硬盘错误
  • mapbox popup(动态定位)查询属性方法
  • 2025开源AI智能体框架全景解析与应用指南
  • Docker Remote 未授权访问漏洞修复方法
  • 2026年小程序商城SaaS平台
  • Zeptoclaw:基于DMA的无中断舵机驱动库,释放MCU性能
  • 文件上传漏洞:边界检验的艺术
  • 终极实战指南:iOS 15-16设备激活锁离线绕过完整解决方案
  • 详解CN域名注册:流程、要求、材料及注意事项全解析
  • 二次元插画创作指南:用real-anime-z快速生成角色与宣传图
  • LLM长时上下文处理:双路径压缩与LoRA蒸馏优化
  • Evently:.NET开源事件管理引擎,简化事件驱动架构开发
  • 长短期记忆网络大跨桥梁振动响应时频分解系统【附代码】
  • SonnetDB:.NET 生态下的高性能嵌入式时序数据库
  • 470-510MHz频段无线通信系统设计与CC1100E+CC1190方案优化
  • 文件上传漏洞实验1(PortSwigger_Labs)
  • 钩子机制如何实现动态逻辑注入
  • CSS Grid布局完全指南:构建复杂的响应式布局
  • 模力方舟Moark:构建中国AI自主生态的关键基础设施
  • 2026年3月铜覆钢供应商推荐,让你选对供应商,铜覆钢角钢/铜排焊接模具/石墨接地绳/镀铜钢管,铜覆钢制造企业推荐 - 品牌推荐师
  • ARMv9内存管理:TCR2MASK_EL1寄存器详解与应用
  • 故障仿真与数据驱动融合高速列车轴箱轴承故障识别【附代码】