Maven初入
maven 是一个项目管理工具, 它包含了一个 项目对象模型(Project Object Model POM), 一组标准集合, 一个项目生命周期(Project Lifecycle), 一个依赖管理系统(Dependency Management System), 和用来运行 定义在 生命周期阶段中插件目标的逻辑.
Maven的核心只做一些基础的事情, 解析XML文档, 管理生命周期, 插件, 仅此而已; Maven的主要职责是委派给各种各样的插件, 如编译源码, 打包二进制代码, 发布站点 和 其他构建任务, 而插件从 maven仓库获得.
文档参考自: Maven权威指南
Maven初步
安装自不必赘述, Maven的相关配置在 ~/.m2/settings.xml,
~/.m2/repository 是本地仓库目录.
maven核心概念
-
插件和目标
就像在前面所提到过的, Maven的核心所做的工作很少, 大部分工作都会叫给插件来做, 一个插件 是一个或多个目标的集合, 插件的直观表现就是一个或多个jar包, 目标指的就是 某一个具体的方法, 可以配置相应的参数, 同时需要给定一部分必须参数;
简写: pluginId:goalId
-
生命周期(Lifecycle)
生命周期是包含在一个项目中一系列有序的阶段, 在maven中配有默认的生命周期, 以验证项目的基本完整性开始, 以把项目发布成产品结束.
其中, 插件目标是可以附着在生命周期上的, 会随着生命周期的阶段 一步步执行, 当 maven执行一个阶段的时候, 他首先会有序执行前面的所有阶段, 并执行绑定在阶段上的默认目标, 到命令指定的那个阶段为止;
recourses: recourses; Recourses插件的 recourses目标绑定在了 recourses阶段, 这个目标复制 src/main/recourses 下的所有资源 和 其他任何配置的资源目录, 到输出目录;
compiler: compile 绑定到了 compile阶段,编译 src/main/java 下的所有源代码 和 其他任何配置的资源目录, 到输出目录.
recourses: testRecourses Resources插件的 testRecourses目标绑定到了 test-recourses阶段, 对应 src/test/resources
compiler: testCompile 对应 src/test/java
surefire: test surefire插件的目标test 绑定到 test阶段, 这个目标运行所有的测试, 并创建那些 捕捉 详细测试结果的文件, 默认情况下, 如果有测试失败, 这个目标会终止
jar:jar 绑定到 package阶段, 把输出目录打包成 jar文件.
-
Maven 坐标
POM为项目命名, 提供了项目的一组唯一标识符(坐标), 并通过 依赖(dependencies), 父(parents) 和 先决条件(prerequisite) 来定义和其他项目的关系.
Maven定义了一组坐标, 他们可以用来标识一个项目, 一个依赖, 或者MavenPom里一个插件.
GroupId: 团体, 公司, 组织等其他, 就是java中的 com.company.project
artifactId: 项目标识
version: 版本号, 一般会通过加上 "SNAPSHOT"标记, 标识正在开发中.
packing(非必须): 默认为 jar, 项目打包后输出, war 表示web 项目.
同时需要注意的是: 在 artifactId中最好不要使用 "."
-
Maven仓库
路径为, 相对于仓库根目录:
/<groupId>/<artifactId>/<version>/<artifactId>-<version>.<packing>
-
Maven依赖
对于依赖, 目前只需要知道依赖具有传递性,即可.
Maven注意事项
-
优化, 降低依赖重复
-
上移共同的依赖至 dependencyManagement
如果多于一个项目依赖于一个特定的依赖, 就可以在 dependencyManagement中列出这个依赖, 父POM包含一个版本和一组排除配置, 在子POM中需要使用 groupId和artifactId引用这个依赖, 如果依赖已经在 dependencyManagement中列出, 子项目可以额忽略版本和排除配置;
<properties>
<hibernate.annotation.version>3.3.0</hibernate.annotation.version>
</properties>通过这种方式将版本信息定义为 常量, 通过 ${ }引用变量
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-annotation</artifactId>
<version>${hibernate.hibernate.annotation.version}</version>
</dependency> 为兄弟项目使用内置的 version 和 groupId
使用相同的 groupId 和 ${project.version}.
上移共同的 plugin 至 pluginManagement
显示的声明依赖, 不要通过依赖传递性来引用 jar包
-
使用 Maven Denpendency插件优化:
通过 mvn dependency:analyze 对项目进行分析, 但增删依赖判断的先决条件是单元测试;
在 analyze中, 生命周期依次为:
recourses, compile, testRecourses, testCompile, analyze;会将执行中的错误, 未声明的 jar, 声明未使用的jar 种种体现出来.
而使用 mvn dependency:tree , 会列出项目中所有的直接和传递性依赖.
-
几个帮助插件
mvn help:active-profiles 列出当前构建中活动的 Profile
mvn help:effective-pom 显示当前构建的实际 pom
mvn help:effective-settings 打印实际的setting, 包含全局和用户级别的
mvn help:describe 描述插件的属性, 无需在项目目录下运行, 但必须提供插件的 groupId 和 artifactId
如: mvn help:describe -Dplugin=complier [Dmojo=compile] [-Dfull]
第二个参数表示插件中的某一个目标, 第三个参数为目标的所有信息, 同时在:
"-D<name>=<value>" 这种方式设定 调用 mvn 插件目标 ,传入目标中的参数中的 值. 需要注意的是, 这并非是 Maven的语法, 其实是 java 用来设置系统属性的方式.
-
Maven Pom
所有的 Maven POM 都继承自超级POM, 超级POM所处目录为: {Maven HOME}/lib/maven-model-builder-{Maven Version}.jar 内 org.apache.maven.model 下的 POM-4.0.0.xml.
文件主要定义了:
-
单独的远程仓库, https://repo.maven.apache.org/maven2
<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> 为核心插件提供了默认的版本
我们自己建立的 POM最顶层继承自 超级 POM, 其次是所有的 父级POM, 自上向下 会一层层 覆盖之前的默认配置, 通过 mvn help:effective 可以查看当前项目的有效pom.
下面就开始一步步看POM.xml文件中的配置:
-
version
<major version>.<minor version>.<incremental version>-<qualifier> 1.3.5-beta-01
其中1为主版本号, 3为次版本号, 5为增量版本号, beta-01为限定版本号(alpha 和 beta). 如果版本号符合规则要求, 则按照版本号来比较, 否则按照字符串规则来进行比较. 比如 a10 是 排在 a2前面的, 因此 a10属于旧版本, 但在我们的定义中, 却应该是相反的.
版本界限配置方式:
(loVersion, hiVersion) 不包含
[loVersion, hiVersion] 包含<version>[3.8, 4.1.2]</version>
当不指定上界/下界时, 则为以上/以下
SNAPSHOT
如果你在版本中使用了 字符串 SNAPSHOT, 表示项目正处于 活动的 开发状态, Maven会在安装 或部署的时候将 符号展开为 一个时间和日期,转换为 UTC.(也就是快照版本)
如在创建的时候, 默认为 0.1-SNAPSHOT, 而后将项目部署到了 Maven仓库, Maven会将版本展开为 0.1-YYYYMMDD-HH24MISS-1, 这里的时间 为UTC时间.
如果项目依赖的一个组件正处于开发过程中, 依赖它的 SNAPSHOT版本, 则在运行构建的时候, 会自动从仓库下载最新的 SNAPSHOT, 同时, 要依赖 SNAPSHOT版本, 用户必须在 POM中使用 repository 和 pluginRepository 开启下载 SNAPSHOT功能.
<!--选择对应的 坐标, 将 enabled 属性设置为 true 即可-->
<repositories>
<repository>
<id>central</id>
<name>Central Repository</name>
<url>https://repo.maven.apache.org/maven2</url>
<snapshots>
<enabled>true</enabled>
</snapshots>
</repository>
</repositories> <pluginRepositories>
<pluginRepository>
<id>central</id>
<name>Central Repository</name>
<url>https://repo.maven.apache.org/maven2</url>
<snapshots>
<enabled>true</enabled>
</snapshots>
</pluginRepository>
</pluginRepositories>而发布到 非 snapshot的 Maven仓库的构建不能依赖于 SNAPSHOT版本, 仅用于开发过程.
LATEST 和 RELEASE
当依赖于一个插件 或 依赖, 可以使用特殊值:
LATEST: 最新发布的版本 或 快照版.
RELEASE: 仓库中 最后一个非快照版本.
在项目发布的时候, 尽量不要使用 这三个关键字, 仅在开发时使用
-
变量引用
在Maven中有三个内置隐士变量:
env: 表示 environment, 系统的环境变量, 如 ${env.PATH}, 需要注意的是需要大写, 如 ${env.JAVA_HOME}
settings: 这个指的是 settings.xml中的属性, 但一直引用不成功.
project: 引用当前 project中的属性 如 ${project.artifactId}.
${basedir}, 可以访问当前项目根目录
Java系统属性
凡是可以被 java.lang.System 中的 getProperties()方法访问的属性
可以在Java中通过 System.getProperties().list(System.out); 在Maven中通过 ${} 直接访问, 如 ${java.runtime.name}
自定义属性:
<properties>
<attribute>value</attribute>
</properties>访问方式即: ${attribute.value}
-
依赖范围
<version>1.0</version>
<scope>test</scope>compile: 编译范围, 默认范围, 在所有的classpath都可以使用, 也会被打包
provided: 已提供范围, 只有当 JDK/容器已经提供该依赖之后才使用, 如在 开发中 需要 Servlet API编译一个 Servlet, 但在打包发布之后这部分应该由 容器/服务器本身来提供. 仅在编译时可用. 不可传递, 不会打包.
runtime: 运行时范围, 在编译时不需要, 在运行时需要.
test: 测试范围, 仅在测试时需要, 测试运行, 测试编译.
system: 系统范围, 必须提供 systemPath, 即本地jar文件的路径.
可选依赖:
<version>1.0</version>
<optional>true</optional>通过这种方式生命的依赖, 不会被传递. 需要在子项目中再度生命
-
依赖的传递
test范围不会被传递, provided范围 仅在 provided中被传递, runtime 和 compile 在 四种范围都会被传递.
e.g. : A 包含对 B的测试范围依赖, B 包含对 C的编译时依赖, 则 C为A的测试范围依赖
A:
<dependency>
<groupId>B</groupId>
<artifactId>B</artifactId>
<version>1.0</version>
<scope>test</scope>
</dependency>
B:
<dependency>
<groupId>C</groupId>
<artifactId>C</artifactId>
<version>1.0</version>
</dependency>e.g. : A包含对 B的测试范围依赖, B包含对C的测试范围依赖, 则 A与C无关.
A:
<dependency>
<groupId>B</groupId>
<artifactId>B</artifactId>
<version>1.0</version>
<scope>test</scope>
</dependency>
B:
<dependency>
<groupId>C</groupId>
<artifactId>C</artifactId>
<version>1.0</version>
<scope>test</scope>
</dependency>依赖追踪:
在 Maven中, 如果多个项目依赖于同一个 project, 则Maven会找到 所有依赖中版本最新的 依赖, 作为最终的选择, 所以可以排除对应的依赖, 且同时 可以 更换自己想要的版本.
e.g.:
<dependency>
<groupId>B</groupId>
<artifactId>B</artifactId>
<version>1.0</version>
<exclusions>
<exclusion>
<groupId>C</groupId>
<artifactId>C</artifactId>
</exclusion>
</exclusions>
</dependency> -
依赖管理
这在之前 优化 已经提到, 采取在父级项目中定义 dependencyManagement, pluginManagement, 在子级中沿用父级的版本, 仅需要列出所选取的 依赖坐标, 而无需再度定义版本(如果定义子级版本, 父级就会被忽略);
多模块项目:
<modules>
<module>project-a</module>
<module>project-b</module>
</modules>多模块项目的打包总是一个 POM 而非构建, 其中各个模块可以为 POM 或者 jar
项目继承:
<parent>
<groupId>C</groupId>
<artifactId>C</artifactId>
<version>1.0-SNAPSHOT</version>
</parent>可以被继承的项目:
- 定义符: groupId 和 artifactId 必须有一个被重写, 不能有完全相同的坐标.
- 依赖
- 开发者和贡献者
- 插件列表
- 报告列表
- 插件执行
- 插件配置
继承中, 当 父级 POM 在 父目录../pom.xml 或者 在 本地仓库目录时 可用, 否则的话需要指明 父级POM的相对位置.
<parent>
<groupId>C</groupId>
<artifactId>C</artifactId>
<version>1.0-SNAPSHOT</version>
<relativePath>../a-parent/pom.xml</relativePath>
</parent>
暂时告一段落.