Maven 多模块管理
多模块管理简单地理解就是一个 Java 工程项目中不止有一个 pom.xml 文件,会在不同的目录中有多个这样的文件,进而实现 Maven 的多模块管理
在多人使用Maven协作开发项目时,尤其是稍微上点规模的项目,每个RD的工作都细分到具体功能和模块,有些模块甚至还要单独部署。
我们假设有这样一个商城项目,包括以下几个模块:
- 商城前台(shop)
- 管理后台(admin)
- 数据库交互模块(dao)
- 通用业务模块(service)
- 接口模块(api)
- 通用工具(util)
其中shop和admin需要单独部署,dao、service、util你可能想要一些经验丰富的人来维护,如果使用一个应用来管理的话,所有的功能和模块都会耦合在一起,所有人都可以随意修改代码,这显然不是我们所期望的。
而且使用一个应用来管理的话,任何一个点的代码有变更,整个项目就需要重新build,使用模块化开发的另一个好处是如果dao的代码被修改,只需要重新build dao模块就可以了。web模块可以build成war,dao、service、util等可以build成jar,只需要配置好依赖关系,就可以实现模块间的解耦合。这样的设计才是遵循“高内聚,低耦合”设计原则的。
我们使用上面的例子进行演示,先进行合理的优化,我们希望dao和service作为通用的底层工具来使用,把它们合并成一个核心模块(core),build成core.jar,简单的Maven模块化项目结构如下:
---------- mall //顶级项目|------ pom.xml //packaging = pom|------ mall-util //通用工具| |--- pom.xml //packaging = jar|------ mall-core //核心模块| |--- pom.xml //packaging = jar|------ mall-web-api //接口模块| |--- pom.xml //packaging = war|------ mall-web-admin//管理后台| |--- pom.xml //packaging = war|------ mall-web-shop//商城前台| |--- pom.xml //packaging = war
这些模块中api、admin、shop(war)均是可以单独部署的web应用,相互之间没有依赖关系,但都依赖于core模块,而core模块依赖于util模块。
模块拆分策略:推荐按照功能拆分,后期方便转换成微服务架构
按职责划分:

按功能拆分:
例如,在电商系统中如下module

创建项目

然后删掉src(父项目必须遵循),只保留:.idea 文件夹 、项目 pom 文件、以及一个 *.iml 文件
注意: 因为父模块只做依赖管理,不需要编写代码,所以 src 文件夹可以直接删除。编写parent的pom.xml只是为了在各个模块中减少重复的配置。
在项目下创建子模块:



然后会发现module 的 pom 文件发生了变化:

新增了两段配置
<packaging>pom</packaging><modules><module>module-util</module>
</modules>
pom 是最简单的打包类型。不像jar和war,它生成的构件只有它本身。将 packaging 申明为 pom 则意味着没有代码需要测试或者编译,也没有资源需要处理。
由于我们使用了聚合,所以打包方式必须为pom,否则无法构建。
module的值是子模块相对于当前 POM 的路径。
再看子模块中的 pom:

也是分成两个部分
<parent><groupId>com.wqlm</groupId><artifactId>module</artifactId><version>1.0-SNAPSHOT</version>
</parent><artifactId>module-util</artifactId>
<parent><groupId>com.wqlm</groupId><artifactId>module</artifactId><version>1.0-SNAPSHOT</version><!--<relativePath/>-->
</parent>
声明了该模块继承自 com.wqlm:module:1.0-SNAPSHOT,其实这里面还省略了
<relativePath></relativePath> 由于 relativePath 默认是 …/pom.xml 而我们的子项目确实在父项目的下一级目录中,所以是可以不用填写的
Maven首先在当前构建项目的环境中查找父pom,然后项目所在的文件系统查找,然后是本地存储库,最后是远程repo。
artifactId 是子模块的组件id,由于继承了父pom,所以groupId、version 也可以不写,不写的话就默认继承自父pom
使用多模块
如上所示,在创建多个模块之后,可以在父pom中添加公共配置,然后所有的子模块都会继承这些配置。除此之外,还可以通用对子模块进行 编译、打包、安装… 操作
如果子模块间相互依赖,需要在 dependency 中引入要依赖的子模块,如图

上图中子模块 module-common:1.0-SNAPSHOT 依赖了 module-util:1.0-SNAPSHOT。
子模块间的相互依赖,需要管理好依赖项的版本号,负责容易依赖版本冲突。
简单来说就是把公共依赖及版本号在父 pom 中申明,子项目引入依赖时只需要指定 groupId、artifactId 不需要指定版本号
如下,先在父 pom 中申明依赖及版本号

再在子项目中引入依赖项,注意,不需要指定版本号,默认查找父pom中定义的版本号

多项目实例
以一个普通 Spring Boot 项目为例,首先放一张图,看一下整体项目完成后的结构

其中目录结构为
- detail-page- detail-client- detail-service- detail-start
- detail-client 用于放需要打包传到 maven 库的代码
- detail-service 用于放置主要的业务逻辑代码
- detail-start 用于放启动代码
其中需要注意的是 pom.xml 的文件的配置,该配置决定了父子模块之间的关系
- detail-page:父模块- detail-client:子模块,无依赖- detail-service:子模块,依赖detail-client- detail-start:子模块,依赖detail-service
注意:在依赖引用过程中,千万不可以出现循环依赖,比如 client 引用了 service,service 也引用了 client,如果出现这种情况 maven 在打包的时候会直接报错
1、父模块 detail-page 的 pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"><parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>2.2.6.RELEASE</version></parent><modelVersion>4.0.0</modelVersion><groupId>com.drawcode</groupId><artifactId>detail-page</artifactId><version>0.0.1-SNAPSHOT</version><packaging>pom</packaging> <!-- 此处必须为pom --><name>detail-page</name><properties><java.version>1.8</java.version><maven.compiler.source>1.8</maven.compiler.source><maven.compiler.target>1.8</maven.compiler.target></properties><!-- modules即为父子关系 --><modules><module>detail-client</module><module>detail-service</module><module>detail-start</module></modules><!-- dependencyManagement非常重要,决定了子pom.xml是否可以直接引用父pom.xml的包 --><dependencyManagement><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter</artifactId><version>2.2.6.RELEASE</version></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId><version>2.2.6.RELEASE</version></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope><exclusions><exclusion><groupId>org.junit.vintage</groupId><artifactId>junit-vintage-engine</artifactId></exclusion></exclusions></dependency><!--注意这个包就是项目本身的模块--><dependency><groupId>com.drawcode</groupId><artifactId>detail-service</artifactId><version>${project.version}</version><!-- 这个版本就表示0.0.1-SNAPSHOT --></dependency><!--注意这个包就是项目本身的模块--><dependency><groupId>com.drawcode</groupId><artifactId>detail-client</artifactId><version>${project.version}</version></dependency></dependencies></dependencyManagement><build><plugins><!-- 注意此处为空 --></plugins></build></project>
detail-start 的 pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"><!--parent使用的即为父pom.xml的信息--><parent><groupId>com.drawcode</groupId><artifactId>detail-page</artifactId><version>0.0.1-SNAPSHOT</version><relativePath>../pom.xml</relativePath></parent><modelVersion>4.0.0</modelVersion><artifactId>detail-start</artifactId><packaging>jar</packaging> <!-- 注意此处要配置为jar --><name>detail-start</name><!--子pom.xml不必添加dependencyManagement--><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter</artifactId></dependency><!--这里可以看到因为父pom.xml已经引用了自身项目的包模块,所以这里可以不加version直接使用--><dependency><groupId>com.drawcode</groupId><artifactId>detail-service</artifactId></dependency></dependencies><build><plugins><!--因为启动类在detail-start中,所以此处必须添加该plugin--><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId></plugin></plugins></build></project>
detail-service 的 pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"><parent><groupId>com.drawcode</groupId><artifactId>detail-page</artifactId><version>0.0.1-SNAPSHOT</version><relativePath>../pom.xml</relativePath> <!-- lookup parent from repository --></parent><modelVersion>4.0.0</modelVersion><artifactId>detail-service</artifactId><packaging>jar</packaging><name>detail-service</name><!--detail-service依赖于detail-client--><dependencies><dependency><groupId>com.drawcode</groupId><artifactId>detail-client</artifactId></dependency></dependencies>
</project>
detail-start 的 pom.xml
因为 detail-start 没有任何依赖所以比较简单
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"><parent><groupId>com.drawcode</groupId><artifactId>detail-page</artifactId><version>0.0.1-SNAPSHOT</version><relativePath>../pom.xml</relativePath> <!-- lookup parent from repository --></parent><modelVersion>4.0.0</modelVersion><artifactId>detail-client</artifactId><packaging>jar</packaging><name>detail-client</name><dependencies></dependencies><build></build></project>
其中建议除了各个子模块单独使用的包之外,其他的都要在父模块下的 pom.xml 中配置包信息,这样便于包的版本控制

项目内部存在了包的依赖之后,不同模块之间的代码即可进行使用,比如 detail-service 依赖 detail-client,那么 detail-client 中的 Test2 就可以被 detail-service 使用了

但是反过来 detail-client 不可以使用 detail-service 中的类,因为依赖是单向的关系
如何启动
启动指令如下
$ mvn clean install && mvn spring-boot:run -pl detail-start
其中 spring-boot:run 可以使用就是因为 spring-boot-maven-plugin 的存在
-pl detail-start 则代表的是有 application 启动类的子模块目录

相关文章:
Maven 多模块管理
多模块管理简单地理解就是一个 Java 工程项目中不止有一个 pom.xml 文件,会在不同的目录中有多个这样的文件,进而实现 Maven 的多模块管理 在多人使用Maven协作开发项目时,尤其是稍微上点规模的项目,每个RD的工作都细分到具体功能…...
crash 内核调试工具 ps 指令 显示的进程状态 RU, IN, UN, ZO, ST, TR, DE, SW, WA, PA 什么意思
crash> help ps | grep "the task state" 5. the task state (RU, IN, UN, ZO ,ST, TR, DE, SW, WA, PA, ID, NE) 参考linux-4.19.113内核源码(include/linux/sched.h),有如下定义 /** Task state bitmask. NOTE! These bits…...
Spring《二》bean的实例化与生命周期
🍎道阻且长,行则将至。🍓 上一篇:Spring《一》快速入门 下一篇:Spring《三》DI依赖注入 目录一、bean实例化🍍1.构造方法 ***2.静态工厂 *使用工厂创建对象实例化bean3.实例工厂 ***使用示例工厂创建对象实…...
java与kotlin 写法区别
原文链接:https://gitcode.net/mirrors/mindorksopensource/from-java-to-kotlin?utm_sourcecsdn_github_accelerator#assigning-the-null-value Print to Console 打印到控制台 Java System.out.print("Amit Shekhar"); System.out.println("Amit…...
服务器运行深度学习代码使用指南
该内容配置均在九天毕昇下配置。 当前系统使用的linux版本为:Ubuntu 18.04 LTS。 当前版本安装的是:cuda10.1。 九天毕昇平台:https://jiutian.10086.cn/edu/#/home 一、linux下运行python的操作 ls 为列出当前目录中的文件 cd 文件名 进入…...
计算机组成原理 - 2. 数据的表示和运算
整理自天勤高分笔记,购书链接: 24 天勤高分笔记 要记住的几个数字 📓: 215327682^{15} 3276821532768 216655362^{16} 6553621665536 23121474836482^{31} 21474836482312147483648 23242949672962^{32} 4294967296232429496…...
【js】基础知识点--语句,break和continue,switch,with,for..in,do-while,while
一、break和continue语句,常用 break 语句会立即退出循环,强制继续执行循环后面的语句。而 continue 语句虽然也是立即退出循环,但退出循环后会从循环的顶部继续执行 var num 0; for (var i1; i < 10; i) {if (i % 5 0) {break;}num; …...
【C++】迭代器
内容来自《C Primer(第5版)》9.2.1 迭代器、9.2.3 begin和end成员、9.3.6 容器操作可能使迭代器失效、10.4.3 反向迭代器 目录 1. 迭代器 1.1 迭代器范围 1.2 使用左闭合范围蕴含的编程假定 2. begin和end成员 3. 容器操作可能使迭代器失效 3.1 编…...
数据可视化在前端中的应用
前端开发中,数据可视化是一种非常重要的技术。它可以将复杂的数据以图形化的方式展示出来,让用户更容易理解和分析数据。在前端中,VUE是一种非常流行的JavaScript框架,可以用来实现各种数据可视化效果。 首先,让我们来看看一些常见的数据可视化方式: 表格:表格是数据可…...
FFmpeg 合并视频文件没声音,不同步原因
查了不少帖子也没搞明白,可能懂的人不会遇到吧。 1 没声音是因为我几个视频文件中,有的没音轨,就是用文字生成了个视频,需要先给它加个dummy的音轨才行。 2 视频不同步是因为各个视频格式不一样,参数挺多我也不知道具…...
绕不开的“定位”
绕开“定位”这个词谈企业战略和品牌 相当于揪住头发离开地球 定位这个词,已经进入商业界的心智中去了 发明这个词的特劳特和里斯的思想有啥差异? 《定位屋》刨析的很到位 趣讲大白话:把握概念的源头,就理解对了大部分 【趣讲信息…...
《Effective Objective-C 2.0 》 阅读笔记 item12
第12条:理解消息转发机制 1. 消息转发机制 当对象接收到无法解读的消息后,就会启动“消息转发”机制,开发者可经由此过程告诉对象应该如何处理未知消息。 消息转发分为两大阶段 第一阶段:先征询接收者所属的类,看其…...
云原生计算能消除技术债务吗?
云原生计算可以将行业领域驱动的设计、GitOps和其他现代软件最佳实践汇总起来,如果企业实施得当,可以减少技术债务。 云原生计算是企业IT的一种新范式,它涉及现代技术的方方面面,从应用程序开发到软件架构,再到保持一…...
9. 回文数
题目 给你一个整数 xxx ,如果 xxx 是一个回文整数,返回 truetruetrue ;否则,返回 falsefalsefalse 。回文数是指正序(从左向右)和倒序(从右向左)读都是一样的整数。 例子 输入&am…...
[SV]SystemVerilog线程之fork...join专题
SystemVerilog线程之fork...join专题 Q:fork-join_none开辟的线程在外部任务退出后也会结束吗? A:后台线程不会结束,任何由fork开辟的线程(join、join_any、join_none),无论其外部任务ÿ…...
你看这个spring的aop它又大又宽
aop🚓AOP 分类AspectJ | 高级但是难用Spring AOP | 易用但仅支持方法aop 原理明月几时有,把酒问青天。——唐代李白《将进酒》 AOP 分类 在 Spring Boot 中,AOP 的实现主要有以下几种: 基于 AspectJ 的 AOP:这是一种基…...
设计模式-创建-单例模式
4.1.1 模式介绍 定义 单例模式(Singleton Pattern)是 Java 中最简单的设计模式之一,此模式保证某个类在运行期间,只有一个实例对外提供服务,而这个类被称为单例类。 作用 保证一个类只有一个实例为该实例提供一个全…...
使用mybatis-plus-generator配置一套适合你的CRUD
1、maven引入 mybatis-plus-generator 和模板引擎,你也可以使用freemarker之类的,看个人 <!-- mybatisplus代码生成器 --><dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-generator</artifactI…...
MATLAB实现各种离散概率密度函数(概率密度/分布/逆概率分布函数)
MATLAB实现各种离散概率密度函数(概率密度/分布/逆概率分布函数) 1 常见离散概率分布的基本信息2 常见离散概率分布计算及MATLAB实现2.1 二项分布(Binomial Distribution)2.1.1 常用函数2.2 负二项分布(Negative Binomial Distribution)2.2.1 常用函数2.3 几何分布(Geom…...
指针的基本知识
我们不会用bit去表达一个数据,因为只能放0和1,能表达的数据太少了,内存地址最小单位是字节 11111111 0x0011 1字节8bit,8bit才算作一个地址,地址是以字节为最小单位&#…...
TFT LCD屏幕硬件解析:从XPT2046触摸屏到背光控制的完整指南
TFT LCD屏幕硬件解析:从XPT2046触摸屏到背光控制的完整指南 在工业控制面板和医疗设备显示屏等专业领域,TFT LCD屏幕凭借其高精度显示和可靠触控性能成为首选方案。不同于消费级产品的通用设计,专业场景下的屏幕需要工程师深入理解从触摸采样…...
GitHub下载加速终极指南:3分钟让你的克隆速度提升100倍
GitHub下载加速终极指南:3分钟让你的克隆速度提升100倍 【免费下载链接】Fast-GitHub 国内Github下载很慢,用上了这个插件后,下载速度嗖嗖嗖的~! 项目地址: https://gitcode.com/gh_mirrors/fa/Fast-GitHub 如果你经常需要…...
SeqGPT-560M智能客服问答系统部署指南
SeqGPT-560M智能客服问答系统部署指南 1. 引言 想象一下这样的场景:你的电商平台每天收到上千条客户咨询,从"这个衣服有货吗"到"怎么申请退货",问题五花八门。传统客服需要一个个手动回复,效率低下还容易出…...
Hitboxer终极指南:免费开源SOCD清洁工具让游戏操作更丝滑
Hitboxer终极指南:免费开源SOCD清洁工具让游戏操作更丝滑 【免费下载链接】socd SOCD cleaner tool for epic gamers 项目地址: https://gitcode.com/gh_mirrors/so/socd 还在为游戏中的方向冲突而烦恼吗?当你在激烈的对战中同时按下左右方向键&a…...
蓝桥杯备赛:Floyd、Bellman-Ford、Dijkstra,三大最短路算法到底怎么选?(附场景对比与代码模板)
蓝桥杯竞赛:Floyd、Bellman-Ford、Dijkstra三大最短路算法实战指南 在算法竞赛的战场上,最短路问题就像是一道必考题,而Floyd、Bellman-Ford和Dijkstra这三大算法则是解题的利器。但很多选手在面对具体问题时常常陷入选择困难:该用…...
别再死磕理论了!用Python+Pytorch实战多示例学习(MIL)图像分类,附完整代码
用PythonPytorch实战多示例学习图像分类:从数据到模型的完整指南 当你第一次听说"多示例学习"(Multiple Instance Learning, MIL)时,是不是也被那些抽象的理论弄得一头雾水?作为计算机视觉领域的重要技术&am…...
避开这5个坑!用MediaRecorder+Vue3实现高兼容性语音输入
Vue3MediaRecorder实战:5个关键技巧打造高兼容语音输入方案 在移动优先的时代,语音输入已成为提升用户体验的重要交互方式。但当你兴奋地在Vue3项目中集成MediaRecorder API时,可能会遇到iOS设备上的静默失败、Android机型上的格式兼容性问题…...
Aurix TC397内存不够用?三种方法教你手动指定变量到LMU或DSRR地址空间
Aurix TC397内存优化实战:精准分配变量到LMU与DSRR的三大策略 当你在Aurix TC397项目开发中遇到"PSPR空间不足"的报错时,那种突如其来的编译中断感就像赛车手在弯道突然失去动力。这款强大的多核微控制器虽然配备了PSRR、DSRR、DLMU、LMU等多…...
从idea ai插件到在线原型:用快马平台快速构建你的智能代码生成器
最近在开发中频繁使用IDEA的AI插件辅助编码,发现这类工具能大幅减少重复劳动。但插件功能往往局限于当前IDE环境,于是萌生了一个想法:能否把这种智能生成能力搬到线上,做成一个轻量级的Web工具?经过在InsCode(快马)平台…...
ICLR 2025论文解读│PointOBB-v2:单点监督下的高效有向目标检测新突破
1. PointOBB-v2:单点监督的革命性突破 有向目标检测一直是计算机视觉领域的重要研究方向,特别是在遥感图像分析、自动驾驶和工业检测等实际应用中。传统的有向边界框(OBB)标注需要人工精确标注目标的旋转角度和四个顶点坐标&…...
