一. 概念
Maven是一个用于项目构建和管理Jar包依赖的工具。
二. Maven的优势
1. 项目构建。Maven整合了从清理、编译、测试到生成报告,再到打包和部署的全套自动化构建过程。
2. Jar管理。通过Maven的坐标系统,在仓库中能轻松的定位到任何一个java类库,我们可以借助它来有序的管理复杂的jar依赖。
三. Maven与Gradle的区别
1. 多模块构建
在现在的项目中,我们往往会将项目拆分成多个模块,在Maven中需要定义一个parent的pom.xml作为一组module的通用 配置,子模块通过<module>进行定义。在gradle中也支持多模块构建,build.gradle中可以通过subproject或allproject来对 子模块进行一些依赖上的定义,针对子模块的声明则在settings.gradle中通过include语句来实现。
2. 依赖管理系统
在Maven中通过GroupId,ArtifactId以及Version组成一个唯一的标识,而Gradle支持动态的版本依赖,在版本号后使用+号 就可以实现动态的版本管理。
3. 一致的构建模型
Maven设置了标准的项目周期,包含验证、初始化、生成原始数据、处理原始数据、生成资源、处理资源、编译、处理类 等等。而Gradle更加的灵活,可以创建一个task,随时通过depends建立与已有的task的依赖关系。
4. 一致的项目结构
Maven中指定了一套标准的项目结构作为java项目结构(src/main/java,src/main/resource,src/test/java,src/test/resource), Gradle也使用了这套标准。在Gradle中只需要在文件中包含applyplugin:'java',系统即可自动识别。
5. 插件机制
两者都采用了插件机制。
四. Maven基本命令
1. mvn clean: 清理。将根目录下的target目录清理掉。
2. mvn compile: 编译。将项目中的.java文件编译成.class文件。
3. mvn test: 测试。执行项目src/test/java路径下的单元测试类。
4. mvn package: 打包。将项目打包并放置在项目根目录的.target目录下。
5. mvn install: 把项目安装至本地仓库。
6. mvn deploy: 将项目部署到私服环境中。
7. mvn package -DskipTests: 打包并跳过测试。
mvn denpendency:tree: 查看依赖的树形结构,用于排除依赖冲突。
9. mvn install:install-file -Dfile=Jar包的完整路径 -DgroupId=自定义groupId -DartifactId=自定义artifactId -Dversion=自定义 version -Dpackaging=jar 将现有的jar包打包(上传)至本地仓库
10.mvn deploy:deploy-file -Dfile=Jar包的完成路径 -DgroupId=自定义groupId -DartifactId=自定义artifactId -Dversion=自定义 version -Dpackaging=jar -Durl=http://ip:port/nexus/content/repositories/thirdparty/ -DrepositoryId=thirdparty 将本地已有的jar打包(上传)至私服
五. Maven的特性
5.1 依赖管理
1. 坐标
以下是坐标相关的属性:
1. groupId: 项目所属组织的唯一标识,一般由域和公司名称组成
2. artifactId: 项目名称
3. version: 项目当前所处的版本号
4. packaging: 项目的打包方式,默认为jar
5. classifier: 用于指明同一个pom的不同构件。比如针对相同groupId,artifactId,version,可以通过指定不同的JDK版本或者 组成部分(如源代码source,文档doc,类文件等),以此来获取不同的构件。
2. 依赖范围scope
Maven在编译主代码时,需要使用一套classpath,测试代码时需要使用另一套classpath,运行代码时也会使用一套独立的 classpath。依赖范围就是用来控制依赖与这三种classpath。maven有以下几种依赖范围:
1. compile: 编译依赖范围,默认使用该依赖范围。对编译、测试和运行时使用的三类classpath都有效。
2. test: 测试依赖范围。只对测试的classpath有效,只在编译测试代码和运行测试单元时有效。
3. provided: 已提供依赖的范围。在编译和测试的classpath中都有效,但在运行时无效。
4. runtime: 运行时依赖。在测试和运行时有效,但在编译时无效。
5. system: 系统依赖范围。与provided的依赖范围完全一致,必须通过systemPath元素显示的指定依赖文件的路径。
6. import: 导入依赖范围。不对上述三种classpath产生任何影响。
3. 依赖的传递性
比如A依赖了B,B又依赖了C,则把B称作A的第一依赖,C称作A的第二依赖,C之所以能够被A依赖,是因为借助了依赖 的传递性。如下图所示,最左边一列表示第一直接依赖范围,最上方一行表示第二直接依赖范围。
注意: 当第一依赖是compile,第二依赖是runtime时,根据依赖的传递性,传递依赖的范围是runtime。因为C在B中时就没 参与项目的编译,况且A使用的是B,所以C没有必要支持A项目的编译。
| compile | test | provided | runtime |
compile | compile | -- | -- | runtime |
test | test | -- | -- | test |
provided | provided | -- | provided | provided |
runtime | runtime | -- | -- | runtime |
4. 依赖调解
项目A有这样的依赖关系: A->B->C->X(1.0),A->D->X(2.0),那么在项目A中,哪个版本的X会被Maven解析使用呢?
Maven依赖调解的第一原则: 路径最近者优先。因此上述问题中,X(2.0)将会被解析。
若依赖路径的长度相同,比如A->C->X(1.0),A->D->X(2.0),依赖的长度相同,那么Maven会如何解析呢?
Maven依赖调解的第二原则: 第一声明者优先。上述问题中,如果C的依赖声明在D之前,则X(1.0)将会被解析。
5. 可选依赖
被 <optional>true</optional> 所修饰的依赖就是可选依赖。它们只对当前项目产生影响,不会被传递。
6. 排除依赖
依赖的传递性会为项目隐式的引入了许多依赖,虽然极大的简化了依赖管理,但有时候并非所有的依赖在当前项目中都被 需要,此时我们就可以对传递而来的依赖进行排除。做法如下:
<exclusions><exclusion>...</exclusion></exclusions>
5.2 生命周期
1. validate: 验证工程(项目)是否正确,所有的资源是否可用。
2. compile: 编译项目的源代码。
3. test: 使用合适的单元测试框架来已编译的源代码。测试时无需对项目进行打包和部署。
4. package: 把已编译的代码打包成可以发布的格式,具体格式参考pom.xml文件中的packaging元素的值,默认打包成Jar。
5. verify: 运行所有检查,验证包是否有效且达到了质量标准。
6. install: 把包安装到Maven的本地仓库中,可以被本地其他工程作为依赖来使用。
7.deploy: 在集成或发布环境下执行。将最终版本的包上传至远程私服的仓库中,供其他开发者的其他工程使用。
5.3 仓库
Maven仓库分为本地仓库和远程仓库,而远程仓库又可以被分成中央仓库、私服以及其他公共库。
Maven中任何一个依赖、插件或者项目构建的输出,都可以被称为构件。任何一个构件都会有唯一的坐标,根据坐标可以定 义其在仓库中的唯一存储路径。Maven仓库是基于简单文件系统存储的。
1. 本地仓库
可以在Maven的配置文件settings.xml中进行如下配置:
<localRepository>本地仓库的绝对路径地址</localRepository>
2. 远程仓库
当在本地仓库内搜索不到依赖时,就需要访问远程仓库。远程仓库可以配置在某一个需要项目的pom.xml文件中,也可以 配置在全局的settings.xml中。
1. 在项目的pom.xml内配置远程仓库地址
<repositories>
<repository>
<id>jboss</id>
<name>JBoss Repository</name>
<url>http://repository.jboss.com/maven2/</url>
<releases>
<!-- Maven从远程仓库检查更新的频率(比较本地pom和远程pom的时间戳)
daily:每天都检查一次(默认) never:从不检查
always: 每次构件都检查更新 interval n:每隔n分钟检查一次-->
<updatePolicy>daily</updatePolicy><!-- never,always,interval n -->
<enabled>true</enabled>
<!-- maven检查和检验文件的策略,warn为默认值,还可以选择ignore和fail,
表示当maven检查检验和文件失败之后应该采取的行动 -->
<checksumPolicy>warn</checksumPolicy><!-- fail,ignore -->
</releases>
<snapshots>
<!-- 是否从该仓库中获取不稳定的SNAPSHOT版本的构件 -->
<enabled>false</enabled>
</snapshots>
<layout>default</layout>
</repository>
</repositories>
出于安全考虑,访问某些仓库时需要验证身份,而鉴权相关的代码只能在settings.xml中编写。我们可以打开 settings.xml,找到<servers></servers>标签,用以下方式书写鉴权代码:
<servers>
<server>
<id>company-snapshots</id>
<username>admin</username>
<password>123456</password>
<server>
</servers>
2. 在Maven的配置文件settings.xml中配置全局的远程仓库地址(所有项目共享)
打开settings.xml,找到<profiles></profiles>标签,接着就可以书写<repositories>了,方法与在项目的pom.xml中一样。
3. 私服
私服是一种特殊的远程仓库,为了节省网络带宽和构件的下载时间,有条件的情况下,建议在局域网内架设一个,通过 私服来代理所有的远程仓库。
4. 中央仓库
Maven本身已经为我们配置好了中央仓库的地址,具体位置在$MAVEN_HOME/lib/maven-model-builder-3.6.3.jar,解压或直接反编译jar包,打开org.apache.maven.model包下的pom-4.0.0.xml,可以看到如下语句:
<repositories>
<repository>
<id>central</id>
<name>Central Repository</name>
<url>https://repo.maven.apache.org/maven2</url>
<layout>default</layout>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
</repositories>
其中,https://repo.maven.apache.org/maven2就是中央仓库的远程访问地址。
settings.xml文件的配置说明
1. 声明规范
xmlns用于声明命名空间,这就意味着,在<settings>标签内的元素都必须遵守命名空间给出的规范。
<?xml version="1.0" encoding="UTF-8"?>
<settings xmlns="http://maven.apache.org/SETTINGS/1.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/SETTINGS/1.0.0 http://maven.apache.org/xsd/settings-1.0.0.xsd">
2. localRepository
定义在settings标签内部。指明了本地仓库的物理路径。默认值为${user.home}/ .m2/repository
<localRepository>/Users/jack/repo</localRepository>
3. interactiveMode
定义在settings标签内部。maven是否需要和用户交互(一串提示),以便获得输入。默认值为false。
<interactiveMode>true</interactiveMode>
4. usePluginRegistry
maven是否需要使用plugin-registry.xml来管理插件版本。若需要让maven使用${user.home}/ .m2/plugin-registry.xml来管 理版本插件,则设置为true,默认值为false。
<usePluginRegistry>false</usePluginRegistry>
5. offline
maven是否需要在离线模式下运行。如果构件系统需要在离线模式下运行,则设置为true,否则设置为false。比如由于 网络原因或者安全因素,构件服务器无法连接远程仓库时,就可以使用这个属性。默认为false。
<offline>false</offline>
6. pluginGroups
5.4 pom.xml 配置
5.5 plugins打包插件
1. maven-shade-plugin
2. maven-dependency-plugin
3. maven-assembly-plugin
5.6 常见内置变量
${basedir} 项目根目录
${project.build.directory} 构建目录,缺省为target
${project.build.outputDirectory} 构建过程输出目录,缺省为target/classes
${project.build.finalName} 产出物名称,缺省为${project.artifactId}-${project.version}
${project.packaging} 打包类型,缺省为jar
${project.xxx} 当前pom文件的任意节点的内容
六. 值得注意的问题
1. 继承父项目的方式有两种,分别是通过<parent>和在<denpendencyManagement>中使用<scope>为import,<type>为<pom>实现多继承。值得注意的是,前者继承而来的依赖的版本号可以通过在父项目中声明<properties>标签内进行修改和覆盖,而后者则不允许这么做。比如当前项目是A,打算导入pom.xml文件的依赖是B,B中包含了C(版本1.0),而A中在<properties>标签内声明了C的版本号为2.0,且依赖了C(没有显示的写出版本号),那么运行的结果是,A项目依赖的是1.0版本的C,<properties>不会起作用!原因在于maven在扫描A的<denpendencyManagement>时,C会直接因为B而被构建完成(版本1.0),因为构建的顺序导致此时properties中的版本号声明不会起到任何作用。
想要覆盖import而来的版本号的问题有两个解决办法:1. 在<denpendencyManagement>中重新声明依赖C并写明版本号。2. 在启项目启动时使用-DXXX.version=XXXX来指定版本号
2 直接引用本地第三方jar包可以使用<systemPath>,但这样做不能将jar打包到最后的包中,因此还需要使用<includeSystemScope>true</includeSystemScope>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<includeSystemScope>true</includeSystemScope>
</configuration>
</plugin>
</plugins>