Maven是Apache软件基金会的一个开源项目,用来处理Java工程的自动化构建,它有两个主要特征:
- 构建
- 自动化处理依赖关系
构建就是将源代码、第三方的依赖包(jar包)、各种资源和配置打包成为一个可执行文件(如war包)的过程。现在的高级IDE,比如IDEA,Eclipse都有自动化构建的功能,我们只需要点一下run
按钮,IDE就会自动的帮我们构建项目,并部署IDE内置的容器(Tomcat)中,供我们预览和调试。
既然有了这些高级IDE,为什么Maven还存在呢?实际上Maven项目建立之初,还没有这些高级的IDE,关于自动构建工具的发展史如下:
自动构建工具发展史其中Maven取代Ant的时候,高级的IDE还没有出现,现在即使有这些高级的IDE,它也仅仅是解决了在开发阶段自动构建的问题,并没有解决项目的依赖问题,更何况是在DevOps中的持续交互和持续集成。
1 Maven安装与配置
要运行Maven需要下载JDK,这里不做JDK的安装的介绍。
到Maven官网下载Maven后,解压到指定的目录,这里我放在了/usr/local/maven
目录下。Mac系统环境变量配置,在/etc/paths.d
目录下新建maven文件,写入maven目录的路径,需要切换到root用户。
echo "/usr/local/maven/bin" > /etc/paths.d/maven
使用mvn -v
查看Maven的信息:
mvn -v
Apache Maven 3.6.0 (97c98ec64a1fdfee7767ce5ffb20918da4f719f3; 2018-10-25T02:41:47+08:00)
Maven home: /usr/local/maven
Java version: 1.8.0_151, vendor: Oracle Corporation, runtime: /Library/Java/JavaVirtualMachines/jdk1.8.0_151.jdk/Contents/Home/jre
Default locale: zh_CN, platform encoding: UTF-8
OS name: "mac os x", version: "10.14", arch: "x86_64", family: "mac"
用户tree
命令查看Maven的目录结构:
# Maven的目录结构
tree -L 1 /usr/local/maven/
/usr/local/maven/
├── LICENSE
├── NOTICE
├── README.txt
├── bin
├── boot
├── conf
└── lib
比较重要的是conf/setting.xml
文件,它是Maven的配置文件,有以下几个标签需要注意:
-
localRepository 表示的Maven本地仓库的位置,默认值是
${user.home}/.m2/repository
,及当前用户根目录下。仓库的概念放在后面说,这里大概知道是用来存放从网上下载下来的第三方jar包的。 - mirrors 表示要从哪里下载第三方jar,及中央仓库的镜像(后面说),Maven的中央仓库在国外,一般为了提高下载速度,我们都会配置国内的镜像,这里我配置了阿里云的镜像,也可以配置多个镜像。
<mirrors>
<mirror>
<id>alimaven</id>
<name>aliyun maven</name>
<url>http://maven.aliyun.com/nexus/content/groups/public/</url>
<mirrorOf>central</mirrorOf>
</mirror>
</mirrors>
2 构建过程
Maven的构建过程大致分为以下步骤:
构建阶段- Clean 清空编译结果,为下一次编译做准备
- Compile 编译主程序源文件,Maven将源文件分为主程序源文件和测试程序源文件,他们放在不同的目录
- Test 编译测试程序源文件
- Package 将主程序编译后的class文件、资源文件以及配置文件,打包为对应的类型(jar包或war包,需要在pom.xml中设置)
- Install 将打包后的文件按照一定规则(groupId+artifactId的方式)安装到本地仓库中
- Deploy 部署程序
构建过程是按上面的顺序依次进行的,我们可以通过IDE或CLI选择执行到某一步。比如我们选择执行package
打包这一步,Maven会先执行package
前面的所有步骤,最后才做打包这个步骤,也就是说,不管我们要执行哪一个步骤,Maven都会从Clean
开始,顺序往后执行到我们选择的那一步。
通过CLI的方式执行构建命令时,只需在项目的根目录输入mvn后面加要执行到的步骤即可。例如,要执行打包这一步,可在项目的根目录中输入以下命令:
mvn package
3 Maven工程目录结构
Maven工程采用约定大于配置(Convention over Configuration)的设计,及源文件放哪个目录,测试文件放哪个目录是约定好了的,如果不按这个约定存放,那么Maven会不能正确的构建这个项目。
maven_webApp
├── pom.xml
└── src
├── main
│ ├── java
│ ├── resrouce
│ └── webapp
│ ├── WEB-INF
│ │ └── web.xml
│ └── index.jsp
└── test
├── java
└── resource
上面是Maven WebApp的一个目录结构,在根目录下有一个src
目录和pom.xml
,pom.xml
是Maven工程的标志,可以简单的理解为只有有了pom.xml
文件,这个工程才是Maven工程。
src
目录下面有main
目录和test
目录,分别用来存放主程序和测试程序,其下面又分为java
和resource
目录,java
目录用来存放源码文件,resource
目录用来存放资源文件。如果是webapp的工程,建议(非强制约定)在main
目录下添加webapp的目录,用来存放于web相关的资源。
4 仓库与坐标
4.1 仓库
仓库是用来存放东西的,以方面我们引用,这些东西大致可以分类3类:
-
存放Maven构建过程需要的plugin,Maven核心程序仅定义了构建的接口,具体的实现是由单独的plugin完成的。比如pom.xml文件中定义如下的plugin插件
<build> <pluginManagement> <plugin> <artifactId>maven-compiler-plugin</artifactId> <version>3.8.0</version> </plugin> </pluginManagement> </build>
maven-compiler-plugin
是Maven的编译插件,在执行mvn compile
命令时,由Maven核心程序调用。它存放在~/.m2/repository/org/apache/maven/plugins/maven-compiler-plugin
目录中。 -
存放通过
mvn install
命令安装的本地工程 -
存放第三方的jar包
仓库按位置又可以分为:本地仓库,私有仓库,中央仓库以及中央仓库镜像。比如,我们需要引用一个jar包,Maven会现在本地仓库查找,如果没有就到私有仓库中查找,私有仓库有该包,将该报下载到本地仓库;如果私有仓库没有该包,从中央仓库(镜像)中下载该包到私有仓库和本地仓库。这里私有仓库并不是必须的,一般有条件的企业会通过Nexus等软件自行搭建一个私有仓库,以方便企业内部开发者对jar包的引用。中央仓库的镜像的目录是提升jar包的下载速度。
4.2 坐标
坐标是jar包的元数据,用来描述jar包在仓库中的位置。包括groupId组织名称,artifactId项目名称,version信息等,通过这三个属性,Maven就能准确的找到该包。比如,程序中要引用mysql驱动,可以在pom.xml文件中添加如下的配置(依赖),该配置中包含了坐标信息,Maven通过这个坐标就能找到对应的jar包。
<!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.47</version>
</dependency>
5 依赖
我们创建了一个项目A,如果它要完成某些工作,需要引入项目B,那么我们就说A依赖于B。在没有用Maven时,我们需要手动的将B复制到项目A中,并添加引用关系。使用Maven后,我们只需在pom.xml文件中做如下配置即可,Maven会为我们下载对应坐标的包,以及添加引用。
<dependency>
<groupId>组织名称</groupId>
<artifactId>项目名称</artifactId>
<version>版本号</version>
</dependency>
如果B又依赖C,那么A就间接依赖C。为了区分间接依赖,我们也可以说A是直接依赖B,如下图所示:
maven依赖Maven会将B加入工程A中,而项目C作为A的间接依赖,也为加入到A中,我们叫这种情况为依赖传递。
5.1 依赖传递原则
依赖传递的原则:就近原则+声明先后原则。
考虑如下情况:
maven-依赖项目C有两个版本,分别是V1和V2版本。项目A直接依赖B和C V2版本,B直接依赖C的V1版本,此时Maven会通过就近原则添加C V2版本。就近原则基本是按步长来计算的,那什么是步长呢?比如A到C V2只要一步,而A到C V1需要通过B,及A到B为一步,B到C V1是一步,总计2步,所以A到C V2的步长小于A到CV1,此时Maven会添加C V2到项目中。
如果A到达C的两个版本的步长是相同的,Maven会按pom中的定义的先后顺序加入对应的依赖,先定义的会加入到A的依赖中。
maven依赖上面两种情况,A到C的步长是相同的,Maven会更加定义的先后顺序添加C,因为C1比C2先定义,所以Maven不会添加C2版本。
5.2 依赖排除
依赖排除实际上是通过配置pom.xml文件对间接依赖的排除,这个不是常用。具体的做法是在dependency
标签中使用exclusions
标签
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>4.3.22.RELEASE</version>
<exclusions>
<exclusion>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
</exclusion>
</exclusions>
</dependency>
我们知道spring-core
模块依赖commons-logging
模块做日志输出,如果我们不希望加载commons-logging
,可以使用上面的配置将logging排除在依赖之外。
5.3 依赖的范围
依赖的范围和构建的阶段(过程)是密不可分的,Maven使用scope
标签来标识依赖的范围,
-
compile - scope的默认值,标识依赖在整个生命周期都适用。
-
provided - 仅在编译和测试阶段有效,也不做依赖的传递。比如我们在开发web app时,在开发阶段我们需要引用servlet-api的jar包,而当我们部署app到tomcat容器时,tomcat容器为我们提供了servlet-api的jar包,如果我们将引用的servlet-api打包到war包中,就会与容器的jar冲突。此时我们可以标记servlet-api为provided,使得这个jar包只在编译和测试阶段有效,而我们在打包项目时,将该jar排除在外,以解决冲突。
<!-- https://mvnrepository.com/artifact/javax.servlet/javax.servlet-api --> <dependency> <groupId>javax.servlet</groupId> <artifactId>javax.servlet-api</artifactId> <version>3.1.0</version> <scope>provided</scope> </dependency>
-
runtime - 这个scope的范围表示依赖不在参与编译阶段,这个依赖是服务与执行阶段的,这个很少用到。
-
test - 仅在编译和测试阶段有效,不参与打包等其他阶段,标记了test的依赖不会做依赖传递。
-
system - 与
provided
相似, 只是这个依赖包不会从仓库中查找,这个也很少用到。
源码编译阶段 | 测试源码编译阶段 | 打包阶段 | 部署阶段 | |
---|---|---|---|---|
compile | ✔ | ✔ | ✔ | ✔ |
test | ✔ | ✔ | - | - |
provided | ✔ | ✔ | - | - |
注意test和scope的区别:test在源码编译和测试源码编译都会参与,这个依赖包只是做测试用的,后面打包部署的时候不会添加该依赖包,容器中也不会提供该依赖包;provided在源码编译和测试源码编译都会参与,打包的时候,也不会加入该依赖包。但是,一般是tomcat等容器会提供该依赖包。
6 POM
上面零散的说了些POM的内容,并没有提到POM的概念,这里补充一下,POM(Project Object Model)翻译过来是项目对象模型。我们可以将POM项目对象模型分开理解,一个Maven工程就是一个项目对象,比如,一个Mavne的java工程和一个Maven的web工程,他们都是一个独立的项目对象。知道了项目对象后,我们对项目对象进行抽象,建立一个模型,用这个模型来描述这个Maven工程,我们这个模型是POM项目对象模型。
我们抽象出来的模型总的有个地方存,存在个模型的文件就是pom.xml,也可说POM的具体表现形式就是pom.xml。前面提到的工程的坐标,用到的插件,依赖的包等都是用来描述Maven工程的,这些信息就是模型的一部分,所以我们定义在pom.xml文件中。除了上面提到的模型信息外,下面在介绍几个模型的抽象。
6.1 Properties
可以把properties
想象为POM的自定义占位符,用来替换POM中的硬编码。下面的例子使用properties中自定义的标签spring.version替换spring-webmvc的版本号。
<properties>
<spring.version>4.3.22.RELEASE</spring.version>
</properties>
<dependenies>
<dependeny>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>${spring.version}</version>
</dependeny>
</dependenies>
6.2 更多参考
Apache官网上列出POM模型的更多参考。连接地址
网友评论