一句话总结今天的学习内容:我们通过对maven进阶过程中的多模块开发自然而然的联想到maven的前置知识以及生命周期的执行以及原理,后面我们通过学习依赖配置,聚合继承,属性等高级特性来解决依赖冲突、统一版本管理和标准化项目结构,最终实现高效、可维护的复杂项目构建。
前言:
++当一个项目变得庞大(比如包含几百个类文件),如果所有代码都堆在一个项目里(即单体架构),会带来很多问题:代码难以理解、逻辑耦合严重、改一处牵全身、编译打包耗时、团队协作容易冲突。++
++分模块开发就是把一个庞大的单体项目,按照业务或功能拆分成多个独立的小项目(模块)。这样做的好处很明显:++
Maven的多模块开发:
将原始模块按照功能拆分成若干子模块,方便模块间的相互调用,接口共享。
在讲解这个内容之前我们先复习一下前面学习的Maven基础部分。
前沿知识
第一部分也是最基础的,Maven在开发中起到什么作用,我们初学者容易搞不清,在面对学习众多的概念和技术的时候。
我们可以把Maven想象成一个全自动化的项目管家。在开发中,它主要帮助我们解决三个核心痛点:
|---------------------------------------------------------------------------------------------------------------------------------------------------------|
| 管理依赖(不需要下载jar包) :以前做Java项目,需要自己上网找jar包下载,然后手动复制到项目里,还得操心版本冲突。有了Maven,只需要在配置文件(pom.xml)里声明用了什么框架(比如Spring、MySQL驱动),它就会自动从中央仓库下载并管理好这些jar包及其依赖的其他库。 |
| 标准化构建(不需要手动点编译) :它把项目的生命周期(清理、编译、测试、打包、部署)都变成了标准化的命令。你只需敲一个 mvn package,它就会自动执行编译、运行测试,最后生成一个可以直接部署的jar或war包,省去了在IDE里各种手工操作。 |
| 项目结构管理:它强制规定了一套标准的项目目录结构(比如Java源码放哪儿,配置文件放哪儿),让所有Maven项目看起来都差不多。这样,一个新人接手老项目,或者在不同IDE之间切换,都能很快上手。 |
简单来说,用了Maven,就能把精力更多集中在写业务代码上,而不是和环境配置打交道。
多模块开发的好处:
|------------------------------------------------------------------|
| 职责清晰:每个模块只关心自己的事(比如专门处理用户的模块、处理订单的模块、处理短信的模块),代码结构一目了然。 |
| 独立部署:如果配合微服务架构,每个模块甚至可以独立打包、独立上线,实现更灵活的交付。 |
| 复用性强:一些通用功能(如工具类、权限校验)可以做成独立模块。以后其他项目要用,直接引用这个模块就行,不用再重复造轮子。 |
| 并行开发:只要模块之间的接口约定好,团队成员就可以各自负责一个模块独立开发,互不干扰,极大提升团队效率。 |
关于接口调用:
"接口共享"是分模块开发后的一个关键动作 ,主要解决模块之间如何互相配合的问题。
假设把项目拆成了A模块(用户管理)和B模块(订单管理)。现在B模块需要知道"当前登录的用户是谁",这就得调用A模块的功能。但A和B现在是两个独立的项目,B不能直接去new一个A的对象,这时候就需要接口共享。
接口调用的流程:
|------------------------------------------------------------------------------------------------|
| 定义接口 :A模块的开发者会定义一个接口(Interface),声明"我能提供查询用户信息的功能"。 |
| 打成jar包:把这个只包含方法声明(没有具体实现代码)的接口单独抽取出来,打包成一个独立的jar包,然后把这个jar包"共享"出去(比如上传到Maven私服,或直接让B模块引用)。 |
| 引用接口 :B模块在它的pom.xml里声明依赖这个接口jar包。这样,B模块就知道A模块能做什么了。 |
| 远程调用:B模块通过这个接口,通过网络去调用实际部署在A模块里的代码(在微服务架构中,这通常通过Feign等组件实现)。 |
分模块开发的具体流程:
第一步:创建对应的Maven模块
第二步:书写模块代码
第三步(最重要的部分):要把创建的Maven模块执行生命周期中的install指令,将此Maven模块安装到本地仓库
补充(关于maven执行生命周期的理解)
我们可以把Maven的生命周期想象成一套标准的"流水线作业流程"。无论你最后想要什么产品,都得按固定的工序来。
Maven有三套独立的生命周期,但最常用的是default生命周期,它负责项目构建。
|-----------------------------------------------------------------------------|
| validate:验证阶段。启动流水线前,先检查项目结构和依赖配置是否正确。 |
| compile :编译阶段。读取src/main/java下的源码,将其编译成.class字节码文件。 |
| test :测试阶段。运行src/test/java下的单元测试代码。如果测试不通过,流水线会立即中断,并报错。 |
| package :打包阶段。将编译后的代码按项目类型打包,比如Java项目打成jar包,Web项目打成war包。 |
| verify:验证阶段。对打包结果进行集成测试或质量检查,确保包的质量合格。 |
| install:安装阶段。把打好的包(比如jar文件)安装到本地的Maven仓库(可以理解为"本地成品库")中,这样其他项目在本地就能使用它。 |
| deploy:部署阶段。把包上传到中央或公司的远程仓库,供所有开发者或生产环境使用。 |
[生命周期]
比如你执行 mvn install,Maven会自动先帮你完成 validate -> compile -> test -> package -> verify,最后才执行 install。它保证了你装到仓库里的,永远是通过完整测试和验证的合格产品。
然后我们重点解释一下install生命周期的原理:
这是多模块开发中最关键的一环。先解释本地仓库是什么:我们可以把它想象成电脑上的一个共享文件夹,有一个默认存放的位置。这个文件夹里存着各种jar包,包括第三方框架和我们的项目产物。
如果不执行install,只在本机开发,会出现一个问题:
假设:
有一个电商项目,拆成了三个模块:
-
common-utils (通用工具模块)
-
user-service (用户模块,依赖 common-utils)
-
order-service (订单模块,依赖 common-utils)
当你在开发user-service时,需要在pom.xml里声明依赖 common-utils。Maven收到这个指令后,会先去本地仓库 里找 common-utils的jar包。但此时,common-utils只是你硬盘上另一个地方的源代码,并没有被安装到本地仓库,所以Maven会报错,说找不到这个依赖。
install就解决了这个问题:
我们会先进入 common-utils 模块的目录,执行 mvn install。
Maven会按照生命周期,把这个模块编译、测试、打包,最后把它生成的jar包复制一份,放到本地仓库的指定目录下。
现在,本地仓库里就有 common-utils 的jar包了。
我们再回到 user-service 模块,执行任何操作(比如 compile),Maven再去本地仓库找 common-utils,就能找到了,构建成功。
这样自然而然就涉及到了Maven的核心机制:
Maven的核心机制主要基于三点:
-
坐标定位系统(GAV)
每个模块在
pom.xml里都有唯一标识,称为坐标:|-------------------------------------------------------------------------|
| groupId :公司或组织名(如com.company) |
| artifactId :项目名(如common-utils) |
| version :版本号(如1.0.0) 这个三元组就像商品的条形码,是Maven在仓库中查找和定位jar包的唯一依据。 | -
本地仓库的索引作用
当执行
install时,Maven根据该模块的pom.xml中的坐标,在本地仓库中创建相应的目录结构,然后把打包好的jar和pom.xml文件放进去。当其他模块需要依赖它时,Maven就根据声明的坐标,去这个目录路径下查找对应的jar包。 -
依赖传递机制
假如
order-service不仅依赖user-service,还间接依赖common-utils(因为user-service依赖它),Maven的机制会自动处理这种情况。它会读取user-service的pom.xml,发现它还需要common-utils,然后自动去本地仓库拉取common-utils。这也是为什么需要先把依赖树底层的模块(如common-utils)先install到本地仓库的原因。
依赖:
我们知道maven的依赖传递关系,但同时也会产生一些依赖冲突(同一依赖的不同不同版本的选择问题)我们通过一些优先级可以解决这些,不用过多的 追究。
可选依赖(不透明)与排除依赖(不需要):
排除依赖是指在引入某个项目时,手动声明不要包含它的某个传递性依赖。
为了解决什么问题?
主要为了解决版本冲突和引入不需要的垃圾的问题。
举个例子
假设你在开发一个订单系统(order-service),你要用到两个工具:
-
A工具 (一个日志框架):它依赖了
common-lib的 1.0 版本。 -
B工具 (一个JSON解析库):它依赖了
common-lib的 2.0 版本。
现在,你的项目里同时存在 common-lib 的 1.0 和 2.0。Maven虽然会按策略处理(通常选近的或先声明),但这经常导致NoSuchMethodError 或ClassNotFoundException,因为1.0和2.0的代码可能不兼容。
解决办法 :我们决定统一使用2.0版本。于是,在引入A工具的时候,你可以排除掉它自带的1.0版本。
<dependency>
<groupId>com.example</groupId>
<artifactId>tool-A</artifactId>
<version>1.0.0</version>
<exclusions>
<!-- 排除掉A工具传递进来的common-lib -->
<exclusion>
<groupId>com.example</groupId>
<artifactId>common-lib</artifactId>
</exclusion>
</exclusions>
</dependency>
可选依赖 是指在项目的 pom.xml 中声明某个依赖时,加上 <optional>true</optional>。这表示:我这个模块在某些情况下需要这个库,但不强迫你也必须用。
为了解决什么问题?
主要为了解决冗余依赖 和避免强迫用户引入不需要的庞大框架的问题。
<!-- PDF引擎:可选的,只有需要转PDF的人才用 -->
<dependency>
<groupId>com.example</groupId>
<artifactId>pdf-lib</artifactId>
<version>1.0.0</version>
<optional>true</optional>
</dependency>
<!-- Excel引擎:可选的,只有需要转Excel的人才用 -->
<dependency>
<groupId>com.example</groupId>
<artifactId>excel-lib</artifactId>
<version>1.0.0</version>
<optional>true</optional>
</dependency>
继承与聚合:
聚合工程的开发:
第一步:创建Maven模块,并在pom.xml文件中设置打包模式为pom
<packaging>pom</packaging>
每个maven工程都有对应的打包方式,默认为jar,web工程打包方式为war
第二步:设置当前聚合工程所包含的子模块名称
<modules>
<module>../maven_ssm</module>
<module>../maven_pojo</module>
<module>../maven_dao</module>
</modules>
注意事项
聚合工程中所包含的模块在进行构建时会根据模块间的依赖关系设置构建顺序,与聚合工程中模块的配置书写位置无关参与聚合的工程无法向上感知是否参与聚合,只能向下配置哪些模块参与本工程的聚合
继承工程的开发:
第一步:设置maven模块,设置打包类型为pom
第二步:在父工程中配置所需的依赖,(子工程将沿用父工程的依赖关系 ),
第三步:配置子工程中可选的依赖(不是所有子工程都需要的,但是这样写我们可以统一管理版本号)
<dependencyManagement><dependencies>
<dependency>
<groupId>com.alibaba</groupId><artifactId>druid</artifactId><version>1.1.16</version></dependency>
</dependencies>
</dependencyManagement>
第四步:在子工程中配置当前工程所继承的父工程
<!--定义该工程的父工程-->
<parent>
<groupId>com.itheima<AgroupId><artifactId>maven_parent</artifactId><version>1.0-SNAPSHOT</version>
<!--填写父工程的pom文件-->
<relativePath>../maven_parent/pom.xml</relativePath></parent>
第五步:在子工程中选择父工程中可选的依赖(只管理版本号的依赖)
<dependencies>
<dependency>
<groupId>com.alibaba</groupId><artifactId>druid</artifactId>
</dependency></dependencies>
子工程中还可以定义父工程中没有定义的依赖关系
子工程中使用父工程中的可选依赖时,仅需要提供群组id和项目id,无需提供版本,版本由父工程统一提供,避免版本冲突
属性:
用于统一修改版本号(相当于java中的定义变量)
定义属性
<!--定义自定义属性-->
<properties>
<spring.version>5.2.10.RELEASE</spring.version><junit.version>4.12</junit.version></properties>
引用属性
<dependency>
<groupId>org.springframework</groupId><artifactId>spring-context</artifactId><version>${spring.version}</version></dependency>
最后给大推荐如果您是计算机领域(AI / 大数据 / 网络 / 软件工程等)的研究者,近期有成果想投稿发表,这几个高认可度的国际会议可以参考(均支持 EI/SCOPUS 检索):
第七届计算机信息与大数据应用会议:
第九届先进算法与控制工程会议:
最后的最后,感谢大家观看到最后,如果对你有帮助,请一键三连,点赞,关注(拜托了),收藏,你的支持就是我最大的鼓励,临近年关,祝大家新年快乐,平安顺遂!
