Maven多模块项目搭建
项目搭建
Maven中有模块的概念,项目与模块不一定是一对一关系
如SpringFramework这一项目,其对应的Maven模块有spring-core、spring-context等,因此一个项目往往会被划分为很多模块
单模块项目而言,模块名=项目名(也就没有了模块的概念)
多模块项目而言,模块名≠项目名
(以IDEA为例)
1、单模块项目搭建
1.1、搭建步骤
File--> New--> Project --> Spring Initializr
- Group(groupId):定义项目属于哪个组(组织或公司)
- 如com.xiaomi.mitv
- Artifact(artifactId):项目(模块)名称,是当前项目在组中的唯一标识。
- 推荐的做法是使用实际项目名称作为artifact的前缀, 如appstore项目下的升级模块:appstore-upgrade。这样做的好处是方便寻找实际构件(jar包)默认情况下,Maven默认生成的构件会以artifactId作为开头,如appstore-upgrade-1.0.jar
- Group+Artifact实际对应Java包的结构,是src/java/main目录中Java的目录结构,如com.xiaomi.mitv.appstore
1.2 、延伸
- Maven坐标
- Maven其中一个核心的作用就是管理项目的依赖,引入我们所需的各种jar包等
- 为了能自动化地解析任何一个Java构件,Maven必须将这些jar包或者其他资源进行唯一标识,这是管理项目的依赖的基础,也就是我们要说的坐标
- 包括我们自己开发的项目,也是要通过坐标进行唯一标识的,这样才能与其它项目中进行依赖引用
- 在Maven仓库中存储所有Maven项目共享的构件,每个构件都有一个唯一的坐标,对应在仓库中的唯一存储路径,该路径与坐标的大致关系为:groupId/artifactId/version/artifactId-version.packaging
- pom、jar包和war包:
- pom
- 一般作为父工程存在,父工程主要是进行统一的版本申明,并不定义具体的依赖关系,常见于多模块或者说聚合工程中使用
- jar
- Java Archive(Java归档文件),jar包就是java的类进行编译生成的class文件打包的压缩包,包里面就是一些class文件
- 在声明了main方法的类,并在jar文件中的META_INF/MANIFEST.MF文件中配置了Main-Class之后,是可以直接用 java -jar xxx.jar 通过内置的tomcat运行的,比较方便,简单
- war:
- Web application Archive,与jar基本相同,但它通常表示这是一个Java的Web应用程序的包,是可以直接运行的web模块
- Web项目有一个web资源目录,默认位置为src/main/webapp,其中需要包括WEB-INF目录,里面包含class文件、web.xml配置文件及前端页面文件,依赖的jar包在WEB-INF下的lib目录下
- war包需要发布到一个容器里面,如将war包放在tomcat的webapps目录下,直接启动tomcat即可
- pom
1.3、项目结构介绍
- src/main/java:项目主代码目录,项目的主代码最终会被打包到最终的构件中(jar包)
- src/main/resources:项目主资源文件目录
- src/main/test:项目测试代码目录,测试代码不会被打包
- target:Maven构建的所有输出都在target目录中,项目主代码编译至target/classes,测试代码编译至target/test-classes
- .idea存放项目的配置信息,包括历史记录,版本控制信息等(不会提交,可以设置隐藏)
- .gitignore:用git做版本控制时 用这个文件控制那些文件或文件夹 不被提交(不用git的话可删除 没影响)
- HELP.md:项目的帮助文档(不需要的话可删除 没影响)
- mvnw(Maven wrapper):linux上处理mevan版本兼容问题的脚本(可删除 没影响)
- mvnw.cmd:windows 上处理mevan版本兼容问题的脚本(可删除 没影响)
- .mvn:Maven-wrapper.properties文件中记录你要使用的Maven版本,当用户执行mvnw clean 命令时,发现当前用户的Maven版本和期望的版本不一致,那么就下载期望的版本,然后用期望的版本来执行mvn命令(可删除)
- appstore.iml:每个导入IDEA的项目都会生成一个项目同名的 .iml文件 用于保存你对这个项目的配置 (删了程序重新导入后还会生成 但由于配置丢失可能会造成程序异常)
2、多模块项目搭建
2.1、为什么搭建多模块项目?
- 对项目按功能、业务模块划分,使项目结构更清晰,降低项目耦合性
- 如电视商店项目,将电视商店的接口服务和升级服务拆分为单独的模块
- 抽取公共模块,实现一处开发多处引用,提高代码复用率和开发效率,更利于项目后期的维护和升级
- 如抽取common模块,放置共用的配置类、工具类、常量、枚举等
- 子模块的pom文件会继承父工程的pom文件,依赖jar包版本统一交由父pom来管理
2.2 、多模块项目建议
- 一个项目的子模块都应该使用相同的groupId
- 如果它们一起开发和发布,还应该使用同样的version
- 一个项目的子模块的artifactId还应该使用一致的前缀,以方便同其他项目区分
- 子模块的目录名称应当与其artifactId一致,即artifactId=项目名
- 子模块的包名,如groupId为com.xiaomi.mitv,artifactId为appstore-common,那么此子模块的包名应为 com.xiaomi.mitv.appstore.common
2.3、多模块项目搭建示例:
2.3.1、新建父模块
- File-->New-->Project-->Maven
- Next
- Finish
- 删除src目录
因为我们创建的是父模块,只作为聚合管理模块,其中还包含子模块,因此不需要src目录,删掉即可
- 修改pom.xml,将打包方式改为 pom:<packaging>pom</packaging>
2.3.2、添加公共子模块
仅作为可独立部署模块的依赖,以common模块为例
- 在父模块上右键 New-->Module-->Maven
- 无需勾选archetype,Next
- 选择Parent,然后填写模块名即可,GroupId、ArtifactId、Version会自动填充
- Finish,父模块pom.xml中会自动在modules标签内添加新增的子模块
<parent> <artifactId>appstore</artifactId> <groupId>com.xiaomi.mitv</groupId> <version>1.0</version> </parent> <modelVersion>4.0.0</modelVersion> <artifactId>appstore-common</artifactId>
parent元素声明了父模块,parent下的子元素artifactId,groupId,version指定了父模块的坐标,这三个元素是必须的
元素relativePath元素表示父父模块pom的相对路径,默认值为 ../pom.xml,也就是说,Maven默认父pom在上一层目录下
父模块与子模块的目录结构不一定是父子关系,也可以是平行目录结构。这时,需要修改relativePath元素的值,指定父pom的位置,父模块pom中module元素的值也需要做相应的修改,以指向正确的子模块目录
- 若不需要,如common模块,可删除resources和test 目录
2.3.3、添加子模块(可独立部署SpringBoot模块)
- 在父模块上右键 New-->Module-->Spring Initializr
注意:SpringBoot子模块不会自动填充Group、Version等
- Group与父模块保持一致
- Artifact以父模块名作为前缀
- Version与父模块保持一致
- Next
- 选择SpringBoot版本和需要的依赖
- Next
- 确认模块名和路径,一般在父模块目录下
- Finish
- 模块创建完成,可删除mvnw、mvnw.cmd、.mvn
- 修改父模块pom
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.5</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.xiaomi.mitv</groupId>
<artifactId>appstore</artifactId>
<packaging>pom</packaging>
<version>1.0</version>
<modules>
<module>appstore-common</module>
<module>appstore-upgrade</module>
</modules>
-
- 添加parent:spring-boot-starter-parent
- 手动添加刚创建的子moudle:appstore-upgrade
- 修改pom
<parent>
<artifactId>appstore</artifactId>
<groupId>com.xiaomi.mitv</groupId>
<version>1.0</version>
</parent>
<artifactId>appstore-upgrade</artifactId>
<version>1.0</version>
<name>appstore-upgrade</name>
<description>电视应用商店应用升级服务</description>
<dependencies>
<dependency>
<groupId>com.xiaomi.mitv</groupId>
<artifactId>appstore-common</artifactId>
<version>1.0</version>
</dependency>
</dependencies>
-
- 修改parent为父模块
- 可删除groupId元素和properties元素,从父pom继承
- 添加appstore-common模块依赖
2.3.4、多模块目录结构
2.4、多模块项目介绍
多模块项目由管理一组子模块的聚合器 pom 构建,也就是父模块的pom
2.4.1、父模块
一个多模块的项目有一个父模块,也叫聚合模块,它仅仅包含一个pom文件,不像其他模块有src/main/java等目录,聚合模块仅仅是帮助聚合其他模块的工具,它本身并无实质的内容
父模块的pom文件中packaging标签值为pom:
<packaging>pom</packaging>
我们可以通过在一个打包方式为pom的Maven项目中声明任意多的module元素来实现模块的聚合
2.4.2、POM的继承
一个项目的POM之间可能会有很多相同的配置,groupId、version、一些相同的jar包依赖、插件等配置,pom的继承能让我们抽取出重复的配置。
另外子模块中会隐式从父模块继承groupId和version两个元素,如果子类这两个元素不同,可以在子POM中显式声明。子artifactId是必须显示声明的。
可继承的POM元素(只列举常见的):
- groupId:项目组Id
- version:项目版本
- distributionManagement:项目的部署配置
- properties:自定义的Maven属性
- dependencies:项目的依赖配置
- dependencyManagement:项目的依赖管理配置
- repositories:项目的仓库配置
- build:包括项目的源码目录配置、输出目录配置、插件配置、插件管理配置等
2.4.3、反应堆(Reactor)
- 在一个多模块的Maven项目中,反应堆是指所有模块组成的一个构建结构
- 对于单模块的项目,反应堆就是该模块本身
- 对于多模块项目来说,反应堆就包含了各模块之间继承与依赖的关系,从而能够自动计算出合理的模块构建顺序。
⚠️注意:在父模块上执行打包,会按照模块的反应堆依次构建各个模块,依次生成每个模块的构件(jar包),有依赖模块,会先构建其依赖模块,其依赖模块无需上传到本地仓库,也可成功构建
- 反应堆的构建顺序
Maven按照父POM中modules标签的顺序读取POM,如果该POM没有依赖模块,那么就构建该模块,否则就先构建其依赖模块,如果该依赖还依赖其他模块,则进一步先构建依赖的依赖。
模块间的依赖关系会将反应堆构成一个有向非循环图(Directed Acyclic Graph,GAP),各个模块是该图的节点,依赖关系构成了有向边。这个图不允许出现循环,也就是Maven不允许循环依赖,当出现模块A依赖于B,而B又依赖于A的情况时,Maven就会报错
上面项目的反应堆构建顺序:
[INFO] Reactor Build Order:
[INFO]
[INFO] appstore [pom]
[INFO] appstore-common [jar]
[INFO] appstore-upgrade [jar]
2.4.4、自定义模块之间的依赖
注意:一个构件只有在本地仓库中,才能由其他Maven项目使用。
appstore-upgrade项目依赖appstore-common模块,因此appstore-common模块必须先安装到本地仓库中
因此当我们修改了依赖的模块代码,项目启动报错时,应先将被依赖的子模块 mvn clean install到本地仓库中
3、多环境配置
在实际项目中,需要面对不同的运行环境,比如开发环境、预览环境、生产环境等,每个运行环境的数据库、Redis服务器等配置都不相同。
如果每次发布测试、更新生产都需要手动修改相关系统配置。这种方式特别麻烦,费时费力,而且出错概率大
因此,我们的项目要能支持针对不同的环境并使用对应的配置,实现灵活配置
3.1、Spring Boot方案
Spring Boot为我们提供了更加简单方便的配置方案来解决多环境的配置问题
3.1.1、创建多环境配置文件
创建多环境配置文件时,需要遵循Spring Boot允许的命名约定来命令,格式为application-{profile}.properties,其中{profile}为对应的环境标识
在resources目录下分别创建application-dev.properties、application-pre.properties、application-prd.properties,分别对应开发、预览、生产环境的配置文件
其中application.properties为项目主配置文件,包含项目所需的所有公共配置
当激活的环境对应的文件中配置项与application.properties中配置项相同时,会覆盖application.properties中的配置项
3.1.2、多环境的切换
- application.properties配置文件中增加如下配置项:
spring.profiles.active=dev
- IDEA编译器指定项目启动环境
- 我们可以在IDEA的Run/debug Configuration 页面配置项目启动环境。
- 命令行启动指定项目环境
通过java -jar命令启动项目时,指定启动环境:
java -jar xxx.jar --spring.profiles.active=dev
问题:
- xml配置文件不能进行多环境配置,如logback.xml
- 不能根据Redis、MySQL拆分单独的配置文件
- 一个项目中多模块不能共用配置文件
好处:
- 配置简单方便
- 一次打包,多环境运行
3.2、Maven Profile
Maven在项目构建的时候就需要能够识别所在的环境并使用对应的配置,生成不同的构件,实现灵活构建,灵活配置
为了能让构建在不同环境下方便地移植,Maven引入了Profile的概念,通过在构建时激活不同的Profile,生成不同的构件,以实现构建在不同环境下的移植
Profile与我们项目中的环境是一一对应的
使用Profile实现不同环境生成不同构件示例:
3.2.1、在项目根目录下创建多环境配置文件目录
3.2.2、在pom中配置profiles
在不同环境下构建时,复制对应环境下的配置文件到 target/classes目录下
<profiles>
<profile>
<id>dev</id>
<properties>
<active.env>dev</active.env>
</properties>
<activation>
<activeByDefault>true</activeByDefault> <!--默认激活此profile-->
</activation>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-resources-plugin</artifactId>
<executions>
<execution>
<id>copy-resources</id>
<phase>validate</phase>
<!--在default生命周期的validate阶段执行copy-resources操作-->
<goals>
<goal>copy-resources</goal>
</goals>
<configuration>
<!--复制到哪-->
<outputDirectory>${basedir}/target/classes</outputDirectory>
<!--资源文件属性过滤-->
<resources>
<resource>
<directory>../appstore-config/dev</directory>
<filtering>true</filtering>
<!--复制哪些文件-->
<includes>
<include>logback.xml</include>
<include>redis.properties</include>
</includes>
</resource>
</resources>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</profile>
<profile>
<id>pre</id>
<properties>
<active.env>pre</active.env>
</properties>
......
</profile>
</profiles>
3.2.3、激活profile
- pom中默认激活
<activation>
<activeByDefault>true</activeByDefault> <!--默认激活此profile-->
</activation>
- IDEA 右侧Maven Profiles勾选
- 命令行激活
mvn clean install -Ppre
3.2.4、application.properties中获取自定义配置文件的配置项
如何在application.properties中获取激活的Profile环境下对应的配置文件内容的属性配置?
如,想使用SpringBoot的自动配置,在application.properties中配置spring.datasource.xxx即可快速配置数据源,同时,又需要根据环境获取不同的自定义的mysql.properties中的数据源配置
- 3.2.4.1、Maven资源过滤(资源文件中Maven属性解析)
Maven属性只有在POM中才会被解析
Maven <properties>中配置了 db.username=xiaomi,那么${db.username}只有在POM中才会被解析为具体的值xiaomi,如果是在 src/main/resources内的配置文件中引用了${db.username},构建的时候,它仍然为${db.username},不会被解析为具体的值xiaomi。
因此,需要让Maven解析资源文件中的Maven属性
在pom中配置资源文件的目录,并开启资源属性过滤,如下,即可让maven-resources-plugin插件在将资源文件复制到编译输出目录中时,解析资源文件中的Maven属性
<build>
<resources>
<resource>
<directory>${basedir}/src/main/resources</directory>
<filtering>true</filtering>
<!--只对资源目录下的某些文件开启Maven属性过滤-->
<includes>
<include>xxx.properties</include>
</includes>
</resource>
</resources>
</build>
SpringBoot 1.3或更高版本,将Maven属性占位符修改为了 @xxx@,要想继续使用${}占位符,需要在POM properties中指定<resource.delimiter>${}</resource.delimiter>
SpringBoot项目中默认为application.properties文件开启了Maven属性解析
3.2.4.2、自定义资源文件加载到pom
接上面的问题,如何在application.properties获取对应环境下mysql.properties的属性配置
<build>
<filters>
<filter>../appstore-config/${active.env}/mysql.properties</filter>
</filters>
</build>
filter标签的作用相当于将指定环境下的配置文件中的配置加载到POM文件中,类似于POM文件中的properties标签
再加上开启资源过滤,那么在/src/main/resources中的文件就可以通过使用${},来获取mysql.properties中的属性配置了
END.
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】博客园携手 AI 驱动开发工具商 Chat2DB 推出联合终身会员
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步