ARTICLE DETAIL

资讯详情

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

Spring Boot多模块依赖管理:父工程与子工程区别及最佳实践

Spring Boot多模块依赖管理:父工程与子工程区别及最佳实践 Spring Boot 多模块项目依赖管理父工程与子工程的区别与最佳实践先聊一个我在实际项目里经常遇到的场景。你接手一个维护了一两年的系统代码全塞在一个Maven工程里里面Service层几千行各种工具类堆在同一个包下面pom.xml里几百个依赖版本有的是2.3.5.RELEASE有的是3.1.2甚至同一个依赖出现了两三次不同的版本。你说要升级一下某个底层库能把人折腾掉半条命因为根本不知道哪边依赖了它、哪个版本在生效。这种时候多模块项目就成了一个绕不开的答案。这篇文章就围绕Spring Boot项目里的多模块依赖管理这个话题展开重点拆解父工程和子工程到底有什么区别、各自该干什么活以及在真实项目里怎么把依赖版本管得清清楚楚。我尽量把每一步怎么做、为什么这么做、踩过什么坑都讲透适合刚接触Maven多模块的新手也适合已经在用但想把依赖管理做得更规范的同学。读完你至少能自己搭出一个结构干净、依赖不混乱的多模块Spring Boot工程并且明白每一步操作背后的逻辑。1. 多模块项目的整体设计与思路拆解1.1 为什么非要多模块不可先把话说透多模块不是银弹一个只有三五个接口的小项目硬拆成四五个模块纯属自己给自己找麻烦。但一旦项目进入持续迭代、多人协作、模块复用的阶段多模块的价值就会非常明显地体现出来。我从几个实际痛点来说。第一是代码复用。没有多模块之前所有公共代码都在同一个工程里A功能想用B功能里一个工具类直接import就完了看起来很方便但时间一长模块之间互相引用、互相纠缠边界感完全消失。多模块之后公共的东西放到一个common模块业务A和业务B各自成模块谁需要什么、谁依赖谁在pom.xml里一目了然。第二是构建效率。单体大工程每次改一行代码都要全量编译、全量打包。模块化之后改到order-service就只构建这个模块Maven的增量构建特性会把关联模块也编译但整体范围已经小了很多尤其是涉及大型项目的场景这个效率差距很可观。第三是职责清晰与团队协作。不同团队维护不同模块互不干扰代码冲突率明显降低。模块的边界说清楚之后大家的注意力自然就集中在自己的领域内代码评审、权限控制也好做。第四是依赖管理的可治理性。这个点跟本文主题直接相关。没有统一父工程时每个模块都想当然地写自己的依赖版本你的fastjson是1.2.83我的是1.2.68一合并就出幺蛾子。有了父工程做dependencyManagement所有版本统一收口回归到“一处定义处处生效”。1.2 一个合理的模块结构长什么样我见过很多团队在模块划分上走极端要么一个模块大到失控要么几十个模块碎成一地。比较稳的常见做法是切分成这几类模块parent纯POM模块不写业务代码只做依赖管理、插件管理和公共属性配置。common工具类、统一返回对象、常量、枚举、异常定义等。dao或repository数据访问层放实体、Mapper、Repository接口。service业务逻辑层也是类型最多的模块可以按业务域继续细分比如order-service、user-service。web或adminController层、启动类所在的模块是最终打包部署的入口。举个我最近在用的工程结构作为参考my-project/ ├── pom.xml // 父工程packagingpom ├── common/ // 公共模块 │ ├── src/main/java │ └── pom.xml ├── dao/ // 数据访问模块 │ ├── src/main/java │ └── pom.xml ├── service/ // 业务逻辑模块 │ ├── src/main/java │ └── pom.xml └── web/ // Web入口模块 ├── src/main/java ├── src/main/resources └── pom.xml注意web模块是唯一包含启动类的模块其他模块都是普通jar。后面我会细说为什么启动类只能有一个以及这个设计对依赖管理的影响。1.3 拆分边界把握的两个原则模块怎么拆其实没有绝对标准但有两个原则我一直很推荐。第一个原则是按业务域拆而不是按技术层次拆。很多人会把一个Spring Boot项目拆成controller模块、service模块、dao模块这种做法从技术上看挺合理但从业务迭代的角度看非常痛苦。因为一次下单操作要改Controller、Service、Mapper三层如果这三个在三个模块里你一次改动要跨模块同步了很多次。更好的做法是垂直拆分也就是按业务域拆比如订单域、用户域、支付域每个域内含controller、service、dao。这样每个域本身是完整闭环跨域依赖用接口隔离整体架构更接近DDD的思想。第二个原则是独立部署的模块才真正值得拆出来。如果拆出来的模块只是被别的模块引用、自己从不单独部署那你要慎重它可能更适合作为包而不是模块。模块之间如果A要依赖BB又要依赖A说明边界本身就没划清这个情况一定要重构。2. 父工程与子工程的核心差异解析2.1 父工程到底是个什么角色很多人一提到多模块就想到继承觉得子工程pom.xml里的parent一写父工程的配置就自动全部“传”给子工程了。这个理解方向没错但里面有非常多的细节需要理清楚尤其是依赖管理方面的机制。先说结论。在Maven多模块项目中父工程的核心角色是管理者而不是传递者。它统一声明依赖版本、插件版本、公共属性子工程通过parent继承这些配置。但关键是父工程里声明“版本”和声明“依赖”是两回事稍不留神就会把依赖全部强塞给每个子项目。这就引出两个容易混淆的标签dependencyManagement和dependencies。在技术圈最容易被误解的地方就在这里。我从用法和效果两个角度拆对比维度dependencyManagementdependencies用途统一管理依赖版本定义“版本字典”直接为当前工程引入依赖是否被子模块继承子模块默认不继承只继承版本清单子模块会自动继承依赖全部传递声明位置通常写在父工程POM写在父工程POM会全量传给子模块对子模块的影响子模块需要自己显式声明依赖可以省略版本号子模块无需声明即可使用该依赖典型场景父工程统一版本、子工程各取所需父工程强制所有模块都用某个公共依赖用生活化的类比来解释dependencyManagement相当于一份“采购清单”告诉所有人某样东西的标准型号是什么但真正要买什么、买多少由各子模块自己决定dependencies则相当于直接“强制采购”父工程买的每一样东西所有子模块都得人手一份。2.2 继承机制子工程能从父工程获得什么子工程通过parent继承父工程的POM具体能继承的东西包括properties属性定义比如java.version、spring-boot.version等变量。dependencyManagement子模块能用自己的groupId/artifactId去省略版本号。pluginManagement跟dependencyManagement一个道理定义插件版本和配置子模块按需启用。插件配置如果父工程直接在buildplugins里声明插件子工程会继承并执行。仓库配置repositories、pluginRepositories等。但有几点继承规则要特别注意我踩过的坑不少这里一起列出来第一子工程可以覆盖父工程的配置。子工程POM里的配置优先级更高父工程定义了一个依赖版本子工程如果自己又写了一个版本号以子工程为准。这个特性很灵活但也带来了安全隐患——版本统一失效了。所以在团队规范里一般约定版本一律不准在子模块里直接指定父工程统一管理避免有人偷懒绕过版本中心。第二继承不是没有成本的传递。如果父工程直接在dependencies里写了依赖那所有子模块就会无条件继承这份依赖哪怕它根本用不到。这个机制有时候很方便比如把lombok、common模块放进父工程的dependencies里省得每个子模块都用重复代码去声明。但滥用的话也会造成依赖爆炸每个模块都被迫携带一堆跟业务无关的jar包。第三父工程构建顺序有讲究。Maven按照模块的依赖关系自动决定构建顺序父工程先构建然后按拓扑排序构建子模块。如果模块之间存在循环依赖构建直接失败。这个在项目初期就要从工程结构上规避。2.3 两种常见的多模块管理方式在Spring Boot项目里父工程的处理方式主要有两种各有优劣。方式一继承Spring Boot官方父工程parent groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-parent/artifactId version3.2.5/version relativePath/ /parent这种情况下Spring Boot官方父工程已经内置了海量第三方依赖的版本管理而且帮你在maven-compiler-plugin、maven-surefire-plugin、spring-boot-maven-plugin等插件上做了默认配置。你不需要自己维护依赖版本清单官方已经替你做了一轮。前提是你的多模块项目必须服从这个版本体系不能在现代Spring Boot之外的场景自由伸缩。方式二自定义父POM用import方式引入BOMdependencyManagement dependencies dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-dependencies/artifactId version3.2.5/version typepom/type scopeimport/scope /dependency /dependencies /dependencyManagement这种方式不继承Spring Boot官方父工程而是把它当成一个BOM清单导入到自己的dependencyManagement里。好处是你可以完全掌控自己项目父工程的结构把Spring Boot版本管理和其他框架的BOM比如Spring Cloud Alibaba BOM、MyBatis Plus BOM并列放在一起取交集、做定制。坏处是官方父工程里预置的插件配置没有了需要自己配置spring-boot-maven-plugin、Java编译参数等。我在代团队时生产级项目基本都有自定义父POM引入多个BOM。这样既满足Spring Boot版本统一管理又不受官方父工程约束还能把内部自研公共库的版本一并纳入管理。这个方案后面实操部分会重点演示。3. 实操从零搭建Spring Boot多模块工程3.1 环境准备与版本选择动手之前先把环境准备好。我用的是JDK 17Spring Boot 3.x要求至少17Maven 3.9.xIDEA 2023.2Spring Boot版本选当前稳定版比如3.2.5或3.3.x。这里多说一句Spring Boot 3.x和2.x在依赖管理上的核心逻辑是一样的本文的配置在2.x上也能跑通只是版本号、部分依赖的groupId可能有变化比如javax改成jakarta。新建父工程时Maven archetype选普通的maven-archetype-quickstart即可后面再把pom.xml内容替换成我们的目标配置。父工程不需要任何Java代码它的src目录可以整个删掉只保留一个pom.xml。3.2 父工程pom.xml的完整配置这是整个多模块依赖管理的核心枢纽。我给出一个经过实战打磨的模板重点注释放在关键位置?xml version1.0 encodingUTF-8? project xmlnshttp://maven.apache.org/POM/4.0.0 xmlns:xsihttp://www.w3.org/2001/XMLSchema-instance xsi:schemaLocationhttp://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd modelVersion4.0.0/modelVersion !-- 自定义父POMgroupId/artifactId/version 按自己公司规范填写 -- groupIdcom.example/groupId artifactIdmy-project-parent/artifactId version1.0.0-SNAPSHOT/version !-- 父工程只做依赖管理必须是 pom 类型 -- packagingpom/packaging namemy-project-parent/name description项目统一依赖管理与构建配置/description !-- 子模块列表按构建顺序排列 -- modules modulecommon/module moduledao/module moduleservice/module moduleweb/module /modules !-- 统一属性版本集中定义改版本只改这里 -- properties java.version17/java.version spring.boot.version3.2.5/spring.boot.version mybatis-plus.version3.5.7/mybatis-plus.version hutool.version5.8.27/hutool.version project.build.sourceEncodingUTF-8/project.build.sourceEncoding /properties !-- 依赖版本字典父工程只管理版本号不强制引入 -- dependencyManagement dependencies !-- 引入 Spring Boot BOM官方的版本规则都归它管 -- dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-dependencies/artifactId version${spring.boot.version}/version typepom/type scopeimport/scope /dependency !-- 内部公共模块定义版本子模块通过坐标直接引用 -- dependency groupIdcom.example/groupId artifactIdcommon/artifactId version${project.version}/version /dependency !-- 第三方常用组件统一收口 -- dependency groupIdcom.baomidou/groupId artifactIdmybatis-plus-spring-boot3-starter/artifactId version${mybatis-plus.version}/version /dependency dependency groupIdcn.hutool/groupId artifactIdhutool-all/artifactId version${hutool.version}/version /dependency /dependencies /dependencyManagement !-- 所有子模块统一的构建配置 -- build pluginManagement plugins plugin groupIdorg.springframework.boot/groupId artifactIdspring-boot-maven-plugin/artifactId version${spring.boot.version}/version /plugin plugin groupIdorg.apache.maven.plugins/groupId artifactIdmaven-compiler-plugin/artifactId version3.13.0/version configuration source${java.version}/source target${java.version}/target encoding${project.build.sourceEncoding}/encoding /configuration /plugin /plugins /pluginManagement /build /project注意这里最核心的机制是spring-boot-dependencies的import方式引入它是一份巨大的版本清单包含了Spring Boot官方测试过的所有依赖版本。之后我们在子模块里引入Spring生态组件时都只需要写groupId和artifactId连version都不用写因为父工程已经通过BOM知道了该用什么版本。3.3 子工程pom.xml配置减少重复并保持清晰接下来看子模块怎么写。先说简单但关键的common模块它一般被多个模块依赖本身不依赖复杂业务通常只引入工具类、参数校验、通用返回值等依赖。?xml version1.0 encodingUTF-8? project xmlnshttp://maven.apache.org/POM/4.0.0 xmlns:xsihttp://www.w3.org/2001/XMLSchema-instance xsi:schemaLocationhttp://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd modelVersion4.0.0/modelVersion !-- 继承父工程 -- parent groupIdcom.example/groupId artifactIdmy-project-parent/artifactId version1.0.0-SNAPSHOT/version relativePath../pom.xml/relativePath /parent artifactIdcommon/artifactId packagingjar/packaging dependencies !-- 依赖版本由父工程的 dependencyManagement 统一控制 -- dependency groupIdcom.fasterxml.jackson.core/groupId artifactIdjackson-databind/artifactId /dependency dependency groupIdcn.hutool/groupId artifactIdhutool-all/artifactId /dependency dependency groupIdorg.projectlombok/groupId artifactIdlombok/artifactId scopeprovided/scope /dependency /dependencies /project这里我故意不写version让Maven从父工程的dependencyManagement中解析版本。如果你在IDEA里看到版本号没有变红报错说明父工程BOM里的版本解析成功。这种写法的好处是父工程升级某个依赖版本时所有子模块无需改动代码重新构建即同步生效。再来看业务模块service它依赖dao模块。如果service里用到了dao中的类就需要显式声明依赖dependencies !-- 内部模块依赖 -- dependency groupIdcom.example/groupId artifactIddao/artifactId /dependency !-- Spring Boot Web Starter如果业务里需要做远程调用/接口封装 -- dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId /dependency /dependencies因为我们在父工程的dependencyManagement里已经为com.example:dao定义了版本为${project.version}所以这里同样不需要写版本号。${project.version}这个变量在父工程中就等于1.0.0-SNAPSHOT并且在子模块解析时会被替换成父工程当前版本这样模块之间同步升版本不会漏改。3.4 web模块的打包细节与启动类唯一性问题这个模块是整个项目的入口我们需要特别关注两个点。第一启动类只能放在web模块。因为Spring Boot在打包可执行jar时是通过spring-boot-maven-plugin的repackage目标重新封装jar这个操作会定位带有main方法的类作为启动类。如果多个模块添加了这个插件或者定义了启动类Spring Boot的自动配置扫描范围就会出问题最常见的就是启动时报找不到Controller或者Mapper。第二web模块需要配置可执行打包。父工程里我们在pluginManagement中配置了spring-boot-maven-plugin但pluginManagement只是定义插件的“可用版本与配置”并不会自动执行。所以web模块中需要显式声明使用这个插件build plugins plugin groupIdorg.springframework.boot/groupId artifactIdspring-boot-maven-plugin/artifactId /plugin /plugins /build这里版本号依然省略因为在父工程的pluginManagement里已经管理了插件版本。而且其他非入口模块不要加这个插件否则每个jar都会有repackage干扰启动的时候反而找不到主类。3.5 在IDEA中运行与验证项目导入IDEA的时候以父工程my-project-parent作为Maven项目导一次IDEA会自动识别modules下的子模块。这一步要注意如果之前是逐个导入子模块的很可能造成模块重复加载或依赖解析异常我遇到过好多次。最稳的做法是删除本地仓库里关于这个项目的旧元数据然后重新从父pom导入。导入之后在web模块的启动类上直接运行main方法。启动成功后你可以在IDEA的Maven面板看到整个reactor构建列表从父工程到四个子模块用mvn clean install命令验证构建mvn clean install这条命令会按模块依赖顺序自动构建。如果遇到某个模块编译不过错误信息会明确显示是哪个子模块失败这就是模块化之后排查问题的第一个好处。4. 项目中的版本管理策略与常见问题排查4.1 多模块项目依赖管理最佳实践清单实操部分结束这部分从方法论层面总结一下我在多个项目中沉淀下来的最佳实践。这些点很多人只有在代码仓库变成一锅粥之后才会回头体会版本一律收口在父工程子模块严禁出现version标签。如果非要写也要有充分理由并且通过代码评审。最直接的做法是设置Checkstyle或专门的Maven校验插件如maven-enforcer-plugin来禁止子POM直接声明版本我团队里就启用过requireReleaseDeps与自定义规则做行政约束。内部模块依赖用版本变量不用固定数字。比如内部common模块推荐在父工程里用version${project.version}/version来定义而不是写死1.0.0-SNAPSHOT这样全项目晋级版本时不需要逐个子模块去找内部依赖的版本号。区分公共依赖与业务依赖。可以在父工程的dependencies里声明一小撮“全项目强制依赖”比如lombok、common模块让所有子模块自动带上。但公共依赖的数量要克制原则是“少而关键”否则每个模块都扛着无关jar包最终打包体量膨胀不说还可能因为相互传递依赖导致意外的版本覆盖。外部BOM按需引入。很多人一上来就把Spring Cloud全家桶BOM拉进来结果只用到其中两三个组件白白引入一堆版本约束将来还可能跟业务用的组件版本冲突。正确的做法是用到哪个系列的组件再把对应BOM引进来。BOM的引入顺序也有讲究Spring Boot自己的BOM放在最前面其他框架的BOM放后面后声明的版本覆盖先声明的这才是利用Maven版本覆盖机制的正确姿势。保持module列表的构建顺序可预测。父工程的modules中列出的顺序尽量遵循依赖关系虽然Maven会根据实际依赖自动调整但保持一个清晰的列表对维护者来说可读性极好。4.2 高频踩坑spring-boot-maven-plugin重复打包这个坑出现频率非常高。症状表现是构建成功后普通子模块的jar包被repackage成了Spring Boot可执行jar体积明显变大但里面找不到Main-Class或者启动时报错“Unable to find a single main class”。原因很清楚父工程如果直接在一级buildplugins里配置了spring-boot-maven-plugin那么所有子模块都会继承并执行repackage目标。普通模块common、service没有main方法这个插件要么报错要么把jar处理成一个非预期的可执行包。解决方案就是我上面实操里采用的父工程中只放到pluginManagement真正需要执行的web模块再显式声明。这个原则跟dependencyManagement的设计哲学一脉相承——父工程定义规范子模块按需启用。还有一种情况是web模块引用了本地模块比如commonspring-boot-maven-plugin默认会把依赖模块的jar也复制进BOOT-INF/lib如果你发现打出来的包里重复出现内部模块jar检查一下打包配置一般不需要特殊处理这是正常现象。4.3 依赖冲突的两种表现与排查思路依赖冲突是多模块项目里最容易出问题的环节。因为模块引用了模块第三方依赖被层层传递最后某个类拿到的是一个旧的、不兼容的版本。冲突的表现方式通常有两种一是编译错误提示某个类不存在或方法签名不对二是运行期异常比如NoSuchMethodError、NoClassDefFoundError、ClassNotFoundException这种尤其隐蔽编译没问题一启动就炸。排查方法我建议按这个顺序来先查看依赖树在子模块目录下执行mvn dependency:tree或者用IDEA的Maven窗口打开Dependencies面板用CtrlF搜索冲突的jar包名。依赖树会列出所有传递依赖及其版本你一眼就能看到是否是同一groupId/artifactId出现了多个版本。然后是解决冲突在多模块项目里优先在父工程的dependencyManagement中直接声明该依赖的目标版本这样全局统一。如果只想在某个模块内覆盖就在该子模块的dependencies里显式声明一次指定版本但需要代码评审把关。最后检查依赖来源。父工程引入的BOM如果先后顺序不对旧版本可能就会覆盖新版本这导致一种非常无语的情况子模块里明明写了一个更新版本的依赖但实际生效的还是BOM里那个旧版本。遇到这种情况看看父工程BOM里有没有对应坐标有的话就调整父工程BOM顺序或版本。这里补充一个规则子模块里显式声明的依赖版本 父工程dependencyManagement里的版本 BOM import的版本但前提是子模块真的写了版本号。4.4 常见问题速查表把这几年积累的多模块项目常见问题整理成一张速查表方便大家快速定位问题现象可能原因推荐处理编译报错“Cannot resolve symbol”依赖未声明或版本解析失败检查子模块pom中是否有依赖坐标以及父工程dependencyManagement是否正确解析版本启动报“No qualifying bean of type”类扫描不到或模块依赖未引入确认相关模块是否在pom中被依赖启动类是否在web模块ComponentScan是否正确配置启动报“Unable to find a single main class”多个模块配置了spring-boot-maven-plugin移除普通子模块的插件配置只保留web模块某个类运行时报NoSuchMethodError依赖版本冲突或本地仓库缓存了旧包用dependency:tree查冲突在父工程统一指定版本必要时mvn clean install -U刷新子模块构建顺序异常模块间循环依赖或顶层污染检查pom依赖方向重新梳理模块边界版本修改不生效本地仓库缓存旧POM对父工程执行mvn clean install -N强制刷新依赖版本被强制覆盖BOM导入顺序导致调整父工程dependencyManagement的BOM顺序后导入的BOM覆盖先导入的版本4.5 多模块项目里的一个隐藏细节插件版本管理很多文章把依赖管理讲得很细但插件管理容易一带而过。实际上插件的版本混乱也够喝一壶的。最常见的场景是不同模块用了不同版本的maven-compiler-plugin导致编译行为不一致或者某个模块编译时JDK版本对不上。插件的管理思路和依赖一样在父工程pluginManagement里统一声明版本与公共配置。子模块需要某个插件时只声明groupId和artifactId版本从父工程里来。这样你升级编译插件版本时只需要动父工程一处。再补充一个容易被忽略的配置在父工程里设置maven.compiler.source和maven.compiler.target对应的属性或者直接在pluginManagement的maven-compiler-plugin中配置。Spring Boot官方父工程里会默认设置编译参数如果你走自定义父POM路线这块千万别忘了否则运行时各种class版本错误会让你怀疑人生。4.6 另一个容易忽略的隐患传递依赖泄漏多模块项目中common模块往往被多个业务模块依赖如果common里顺手引入了spring-boot-starter-web那么所有依赖common的模块都会被这个starter污染。轻则每个模块多出几百个class重则某些模块本不该支持Web功能却因为传输依赖被迫引入了内嵌Tomcat环境造成启动端口冲突等诡异问题。所以公共模块的依赖要“轻”尽量只放纯工具类、纯POJO、标准库依赖。如果需要把Spring上下文相关的东西比如通用的JacksonConfig、WebMvcConfigurer抽到公共模块里建议单独拆出一个common-web模块把需要Web场景的依赖隔离在那里让需要Web能力的业务模块显式依赖它而不是通过透明传递被动拉进来。这个原则其实一句话就能说完传递依赖要克制显式依赖要清晰。模块之间宁可多写几行pom坐标也不要依赖隐式传递带来的“便利”因为代码能跑的时候你不会觉得有什么但出问题时隐式依赖的排查成本是最高的。5. 从依赖管理的层面聊聊这套架构的收益写到这里可能有人会觉得多模块不就是拆几个子目录、pom里多写几行配置嘛至于说这么多吗但从我经历过的项目来看依赖管理的收益恰恰不是“能跑起来”那一刻体现的而是体现在后续漫长的迭代里。举一个真实的例子。一个业务系统Spring Boot 2.7打算升到3.2。单体工程面临的麻烦是所有第三方依赖兼容性都要重新验证全部混在一起升级动作根本没有边界但如果是多模块common模块涉及的依赖集合非常小几乎不影响业务模块只需对照Spring Boot的迁移文档调整javax到jakarta的导入变更真正要动大改的只有web入口模块和相关配置。也就是说多模块天然为技术升级划了一条清晰的边界升级风险可以被精确隔离在少数模块内部。这就是依赖管理设计得好带来的直接收益这个账很多团队是等到大版本升级时才真正算明白的。再比如团队里来了新人让他负责一个订单模块只需要打开service/order这个模块看它的pom和代码就能搞清楚这个功能域用了哪些依赖、依赖了哪些内部模块完全不需要走进一大堆无关代码里去“考古”。这种可读性在多模块做好依赖管理之后几乎免费获得。我自己的体会是多模块项目的山不是搭出来的而是改出来的。第一版搭得规整不难难的是每次加依赖、调版本、加模块时都守住父工程统一管理的底线。如果哪次图方便在子模块里写了版本号或者为了让某个功能快速跑通往common里塞了个重量级依赖架构就会朝着失控的方向滑一步。所以这些原则和执行约束最好在项目启动第一天就写进团队约定里并且在代码评审时严格把关。最后再分享一个小习惯。我一直建议团队里维护一份“依赖升级记录”放在父工程POM的description里或者另一个专门的文档每次升级BOM、调整版本号都记一句为什么升、影响面是什么、由谁负责验证。这个文档在平时看起来没什么用但在处理一年一度的安全漏洞升级或者关键依赖兼容性问题时价值极高。多模块的版本管理不是把版本号集中在一处就结束了真正的核心是把变更的历史和原因也一并管理起来这才是“集中管理”四个字的完整含义。
返回列表