0%

Maven 使用

maven 是一种软件项目管理和理解的工具。基于项目对象模型的概念。


前言

maven 一直当作导jar包的工具,在实际开发中遇到了maven基础知识不够的大坑

本篇大部分复制于茶轴的青春的文章《一文带你彻底搞懂 Maven》

简介

maven是一种软件项目管理和理解的工具。基于项目对象模型的概念。

定位

多数博客或者视频都将maven定义为自动化构建工具。那什么是自动化构建工具呢?

首先来解释构建

  1. 一个BS项目最终运行的并不是动态web工程本身,而是这个动态web工程“编译的结果”
  2. 将java源文件变成字节码,交给JVM去执行
  3. 编译
  4. 部署

构建各个过程的步骤

清理: 将以前编译得到的旧字节码删除掉

编译: 将java源代码变成字节码

测试: 执行test文件夹中的测试程序

报告: 显示测试程序执行的结果

打包: 动态Web工程打成war包,Java工程打成jar包

安装: Maven的特定概念—将打包得到的文件复制到”仓库”中指定的位置

部署: 将动态Web工程生成的war包复制到Servlet容器中指定的目录下,使其可以运行

自动化构建,其实上述步骤,在 elipse 和 IDEA 中也可以完成,只不过没那么标准。

既然 IDE 已经可以完成这些工作了,那么还要 maven 干什么呢?

日常开发中,以下几个步骤是我们经常走的

编译、打包、部署、测试

这几个步骤是程式化的,没有太大的变数或者说根本就没有变数。程序员们很希望从这些重复的工作中脱身出来,将这些重复的工作交给工具去做。此时Maven的意义就体现出来了,它可以自动的从构建过程中的起点一直执行到终点。

核心概念

POM

POM: a project object model. 项目对象模型

学习 Maven 就是学习 pom.xml 文件中的配置

坐标

使用如下三个向量在 Maven 的仓库中唯一的确定一个 Maven 工程

1
2
3
4
5
6
[1] groupid:公司或组织的域名倒序+当前项目名称
[2] artifactId:当前项目的模块名称
[3] version:当前模块的版本
<groupId>com.atguigu.maven</groupId>
<artifactId>Hello</artifactId>
<version>0.0.1-SNAPSHOT</version>

引入对象的依赖非常的简单,去Maven的中央仓库,输入jar包的名字,选择需要的版本,然后将复制仓库给出的依赖,粘贴在pom.xml文件中的标签下就可以了。

仓库

仓库可以理解为存放jar包的地方。

本地仓库: 当前电脑上的部署的仓库目录,为当前电脑上的所有Maven工程服务。默认在用户家目录下的 ~/.m2/repository

远程仓库: 私服,搭建在局域网环境中,为局域网范围内的所有Maven工程服务。

中央仓库: 架设在Internet上,为全世界范围内所有的Maven工程服务。

中央仓库镜像: 为了分担中央仓库的流量,提升用户的访问速度。如阿里云

生命周期

Maven 生命周期定义了各个构建环节的执行顺序,有了这个清单,Maven 就可以自动化的执行构建命令了。

Maven有三套互相独立的生命周期

  • Clean Lifecycle 在进行真正的构建之前进行一些清理工作
  • Default Lifecycle 构建的核心部分,编译,测试,打包,安装,部署等等
  • Site Lifecycle 生成项目报告,站点,发布站点

它们是相互独立的,你可以仅仅调用 clean 来清理工作目录,仅仅调用 site 来生成站点。

当然你也可以直接运行 mvn clean install site 运行所有这三套生命周期。

这个生成站点的意思就是: 在本地生成有关你项目的相关信息。具体没用过。。感兴趣的自看使用maven 插件site 生成站点

Clean生命周期

1
2
3
1 pre-clean 执行一些需要在 clean 之前完成的工作
2 clean 移除所有上一次构建生成的文件
3 post-clean 执行一些需要在 clean 之后立刻完成的工作

Site生命周期

1
2
3
4
5
6
1 pre-site 执行一些需要在生成站点文档之前完成的工作
2 site 生成项目的站点文档
3 post-site 执行一些需要在生成站点文档之后完成的工作,并且为部署做准备
4 site-deploy 将生成的站点文档部署到特定的服务器上
这里经常用到的是 site 阶段和 site-deploy 阶段,用以生成和发布 Maven 站点,这可是 Maven相当强大的功能,
Manager比较喜欢,文档及统计数据自动生成,很好看。

Default 生命周期是 Maven 生命周期中最重要的一个,绝大部分工作都发生在这个生命周期中。这里,只解释一些比较重要和常用的阶段

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
validate
generate-sources
process-sources
generate-resources
process-resources 复制并处理资源文件,至目标目录,准备打包。
compile 编译项目的源代码。
process-classes
generate-test-sources
process-test-sources
generate-test-resources
process-test-resources 复制并处理资源文件,至目标测试目录。
test-compile 编译测试源代码。
process-test-classes
test 使用合适的单元测试框架运行测试。这些测试代码不会被打包或部署。
prepare-package
package 接受编译好的代码,打包成可发布的格式,如 JAR 。
pre-integration-test
integration-test
post-integration-test
verify
install 将包安装至本地仓库,以让其它项目依赖。
deploy 将最终的包复制到远程的仓库,以让其它开发人员与项目共享

maven 生命周期(lifecycle)由各个阶段组成,每个阶段由 maven 的插件 plugin 来执行完成。当我们执行的 maven 命令需要用到某些插件时,Maven 的核心程序会首先到本地仓库中查找。

依赖的范围

经常可以看到 pom 配置 dependency 时出现 scope。

1
2
3
4
5
6
<dependency>
<groupId>org.apache.flink</groupId>
<artifactId>flink-runtime_2.11</artifactId>
<version>1.5.2</version>
<scope>test</scope>
</dependency>

提一下<scope>的含义

  1. compile: 默认值 他表示被依赖项目需要参与当前项目的编译,还有后续的测试,运行周期也参与其中,是一个比较强的依赖。打包的时候通常需要包含进去

  2. test: 依赖项目仅仅参与测试相关的工作,包括测试代码的编译和执行,不会被打包,例如:junit

  3. runtime: 表示被依赖项目无需参与项目的编译,不过后期的测试和运行周期需要其参与。与compile相比,跳过了编译而已。例如JDBC驱动,适用运行和测试阶段

  4. provided: 打包的时候可以不用包进去,别的设施会提供。事实上该依赖理论上可以参与编译,测试,运行等周期。相当于compile,但是打包阶段做了exclude操作

  5. system: 从参与度来说,和provided相同,不过被依赖项不会从maven仓库下载,而是从本地文件系统拿。需要添加systemPath的属性来定义路径

踩坑现场 IDEA 在使用Maven项目时,未加载 provided 范围的依赖包,导致启动时报错

明明导入了依赖也确认过没有jar冲突,死活说找不到java.lang.ClassNotFoundException,最后才发现是Run Application时,IDEA未加载 provided 范围的依赖包,导致启动时报错(eclipse里面好像默认会加载,所以在那边是能正确运行的)

主要关注 compile、test、provided

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
① complie
对主程序是否有效:有效
对测试程序是否有效: 有效
是否参与打包: 参与
是否参与部署: 参与
典型例子: Spring-core

② test
对主程序是否有效: 无效
对测试程序是否有效: 有效
是否参与打包: 不参与
是否参与部署: 不参与
典型例子: junit

③ provided
对主程序是否有效: 有效
对测试程序是否有效: 有效
是否参与打包: 不参与
是否参与部署: 不参与
典型例子: servlet-api.jar
依赖的传递性

模块A依赖于模块B。模块B又依赖于C模块,则模块A内会有C模块。

但是有时候会存在jar冲突,导致我不想用C模块的依赖

可以这样排除依赖

解决 jar 包冲突, 可以参考下面文章,个人用的最多的是 Maven Healper

依赖的原则

[1] 路径最短者优先

[2] 路径相同时,先声明者优先

这里指的先声明者优先,是指 A、B 模块在A模块中 denpendency 标签配置的先后顺序。谁在上面,谁就是先声明者。

继承

继承可以帮我们做什么?统一管理依赖的版本。一个典型的例子就是:现在项目下A、B、C三个模块,三个模块都依赖着不同的 Junit 的版本,依赖范围为 test 时,依赖又不能传递过来,但是我们希望能够统一Junit版本,可能有的人自然会想到,你让A、B、C模块pom中的Junit依赖版本写一致不就行了,这也是个办法。但是 maven 在设计的时候估计也考虑到了这种情况,这称之为继承。

继承该如何使用?

首先需要创建一个 maven 父工程,注意指定打包方式为 pom

1
2
3
4
5
<groupId>com.cxkStudy.maven</groupId>
<artifactId>Parent</artifactId>
<version>0.0.1-SNAPSHOT</version>
<description>学习Maven使用</description>
<packaging>pom</packaging>

再创建一个maven工程,在该模块中指定它的父工程是谁

1
2
3
4
5
<parent>
<groupId>com.cxkStudy.maven</groupId>
<artifactId>Parent</artifactId>
<version>0.0.1-SNAPSHOT</version>
</parent>

在父项目中确定子模块的位置

1
2
3
4
<modules>
<module>../studyDenpen</module>
<module>../Hello</module>
</modules>

module 标签中填的是相对于父项目的位置。同时这也就是聚合

这样就可以在父工程里面指定Junit的版本, 在子模块使用Junit的时候不写版本就好了

项目顶层(即父工程)的POM文件中,会看到dependencyManagement元素。通过它元素来管理jar包的版本,让子项目中引用一个依赖而不用显示的列出版本号。

父工程中执行Junit的版本

1
2
3
4
5
6
7
8
9
10
<dependencyManagement>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.9</version>
<scope>test</scope>
</dependency>
</dependencies>
</dependencyManagement>

子模块中引入Junit的时候不指定版本

1
2
3
4
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
</dependency>

请注意配置继承后, 执行安装命令要先安装父工程。

聚合

为什么要使用聚合?

将多个工程拆分为模块后,需要手动逐个安装到仓库后依赖才能够生效。修改源码后也需要逐个手动进
行 clean 操作。而使用了聚合之后就可以批量进行 Maven 工程的安装、清理工作。

聚合的好处还有继承,可以在子模块中不需要写相同依赖的版本了,上面有说。

关于继承、聚合的理解可以结合一个实际的项目来参考,我这边参考的是百度的图数据库hugegraph


参考链接