Maven目前算是我们后端开发项目管理必不可少的工具了(现在gradle用得也比较多),虽然对开发而言,大部分时候项目是已经搭建好的,个人可能只需要做些clean、package、install等常用操作。但是,如果是作为项目的搭建者,就需要关注下项目构建方面的细节了,毕竟一个好的框架结构,是基石,后续能够更好维护,其他参与你项目的伙伴用起来也比较顺手便捷。当然,另一方面多了解一下maven方面的知识,也有助于自己去更好地参与到他人的项目中,能够更快地让项目“跑”起来,依稀还记得曾经自己新接手项目时,拉到本地一片爆红,被maven支配的恐惧。
现在也是结合自己的一些经历和体会,对遇到过或者觉得比较重要的点进行简要整理和记录吧。
另外,我搭建了简单的示例demo,可以参考这样的模式搭建多模块项目,代码已上传仓库:
https://gitee.com/panda00hi/learnMaven.git
介绍
目前企业级项目因为业务复杂,并且为了方便扩展和维护,同时提高功能代码的复用性,往往会根据功能进行一些模块的拆分,这其实也就带来了一些项目管理上的更高的要求。比如,经常会遇到的情况,对公共组件,如,log4j、logback、sl4j等,如果不做约束,可能会导致每个项目都有依赖类似的组件;而对于模块之间,也可能有依赖,这时又会误引入其他模块引入的组件,总之因为多模块的方式,是需要进行某种规范性约束的,否则随着项目日益复杂,单纯搞依赖配置都要费好大劲。
针对这样的情况,官方也有解决方案,就是pom父子依赖机制,这一点我们应该都有用到,只是可能没有太关注,或者用得不够彻底。
有关于pom的详细介绍和使用案例:https://maven.apache.org/guides/introduction/introduction-to-the-pom.html
Project Inheritance vs Project Aggregation
If you have several Maven projects, and they all have similar configurations, you can refactor your projects by pulling out those similar configurations and making a parent project. Thus, all you have to do is to let your Maven projects inherit that parent project, and those configurations would then be applied to all of them.
And if you have a group of projects that are built or processed together, you can create a parent project and have that parent project declare those projects as its modules. By doing so, you'd only have to build the parent and the rest will follow.
But of course, you can have both Project Inheritance and Project Aggregation. Meaning, you can have your modules specify a parent project, and at the same time, have that parent project specify those Maven projects as its modules. You'd just have to apply all three rules:
Specify in every child POM who their parent POM is.
Change the parent POMs packaging to the value "pom" .
Specify in the parent POM the directories of its modules (children POMs)
创建示例项目
此处使用新建普通maven项目的方式,完成后的项目最为精简,可根据需要自行添加spring boot的相关依赖和启动类。
当然,也可以直接利用spring boot initializer新建项目和模块时,直接生成更丰富的项目模版。
通过继承方式进行管理,只需要应用这三项规则:
- 在每个子POM中指定其父POM是谁。
- 将父POM打包更改为值“pom”。
- 在父POM中指定其模块的目录(子POM)
创建父项目
- 新建普通maven项目,作为父项目
- pom文件中添加打包方式为
新建完成后
添加打包类型为pom
com.panda00hi.demo
learnMaven
1.0-SNAPSHOT
pom
创建子模块
如,有以下子模块
- common:一些公共使用的部分,如utils、config、model、mapper等
- service:抽象的业务服务接口和实现,通过调用dao,各个业务功能,或提供远程调用的方法等
- web:与前端交互,开放的api、静态资源等
- task:定时任务或异步相关等一些操作,一般是从web中分离出来,为了方便对一些高耗时的操作进行管理
创建时idea已经可以允许选中基于某模块作为parent创建,创建完成后,子模块自动引入parent,而parent的pom文件中,也会自动添加子模块的标签。
子模块pom文件已引入父模块的parent标签
注意:
1、子模块自己的groupId、version标签无需声明,将通过父模块约束。
2、默认是使用上一级,若需要自己指定,可使用
learnMaven
com.panda00hi.demo
1.0-SNAPSHOT
4.0.0
common
父模块pom文件已添加子模块的modules标签
common
service
web
task
完成后,maven工具栏可看到父模块的root标识
使用spring boot启动项目
1、在父模块,添加spring boot starter parent的parent声明
org.springframework.boot
spring-boot-starter-parent
2.6.6
2、在web和task模块,由于是web项目,引入web依赖
无需指定版本号,因为父模块依赖的springboot parent已经指定了
org.springframework.boot
spring-boot-starter-web
可以看到,springboot的依赖已经导入了
3、新建启动类和测试controller
启动类
package com.panda00hi.web;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
/**
* @author panda00hi
* @date 2022/4/14
*/
// 配置扫描包路径,默认当前模块根目录下及子目录
@SpringBootApplication(scanBasePackages = {"com.panda00hi.web"})
public class WebApplication {
public static void main(String[] args) {
SpringApplication.run(WebApplication.class, args);
}
}
controller
package com.panda00hi.web.controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* @author panda00hi
* @date 2022/4/14
*/
@RestController
public class TestController {
@GetMapping("/test01")
public String controller01(Integer id) {
System.out.println(id);
return "test01 receive id is :" + id;
}
}
4、启动项目,验证
启动项目,默认8080端口。浏览器请求localhost:8080/test01?id=1,成功返回。
子模块之间的依赖
如实际工作中,common模块,一般会被其他多个模块依赖,service模块,会被web和task模块所依赖。注意:若service依赖了common,那么web也将获得common依赖,无需重复添加。
操作步骤:
子模块的dependencies标签下,添加需要依赖的其他子模块即可。
注意,添加时GAV三要素中,groupId和version,由于各个子模块均有依赖共同的父模块,且继承了是用来父模块的groupId和version信息,所以,此处依赖其他子模块时,groupId和version使用引用方式即可,如${project.parent.groupId}、${project.parent.version}
${project.parent.groupId}
service
${project.parent.version}
org.springframework.boot
spring-boot-starter-web
父模块进行版本的管理dependencyManagement
为什么springboot项目引用某些依赖时,无需指定版本?
对于添加依赖是需要具备GAV三要素的,但是为什么在springboot项目的pom引入一些依赖的时候不需要指定版本呢?如,spring-boot-starter-web,引用时,并没有声明version信息,这是为什么呢?
原因:实际上,也是通过父模块进行管理的。
依次找到上一级父目录
spring-boot-starter-parent 》spring-boot-dependencies 在dependencyManagement标签下即可看到各个已经声明的依赖,很多都是常见依赖。
实践
采用类似的方式,我们在自己的父模块中,也统一进行依赖的版本管理。
父模块pom
${project.groupId}
common
${project.version}
${project.groupId}
service
${project.version}
子模块pom,如web模块
${project.parent.groupId}
service
org.springframework.boot
spring-boot-starter-web
项目构建
1、使用maven工具,进行mvn package打包操作
注意:若使用父模块进行构建,那么它下面的所有子模块也会构建。
均已构建成功。
2、构建顺序
对于多个模块的项目,尤其是第一次导入新项目,大概率是一片爆红,而且打包命令也是各种执行报错,弄不好心态还崩了,个人也是深有体会。
- 一般情况会按照父模块的modules标签声明的顺序,进行构建
- 特殊情况:子模块有先后依赖时,先尝试install被依赖的模块。如,A依赖B和C,那么需要先把B和C打包后,再进行A的打包操作;再就是打包前选中如下图标,跳过单元测试。
3、指定构建的模块
如果只想构建某一模块以及它所依赖的其他模块,可以使用命令操作。如,只打包web模块(其中依赖了service,service依赖了common)
mvn -DskipTests==true package -pk web -am
idea的maven工具执行
修改package的运行配置
添加参数package -pl web -am -f pom.xml。注意:-am参数表示将所依赖的模块也一同打包
保存后,通过启动器运行
构建完成,可以看到打包的操作只有web模块和它所依赖的模块