目录
写作原由
说起Maven,它不是一个新的东西,大家多少都了解一些。之前为了了解的更多一些,我看了一本中文的Maven介绍书籍,但是那时一方面是经验尚浅,对一些概念的理解不深,一方面是书写的思路不是很清晰,后续没有相关总结,现在说不上一二。近期对Maven重新找了一本比较好的外文书籍,初读感觉很清爽,打算做一下读书笔记。(这里不是崇洋媚外,^_^)所以这里的原创其实是抱着了巨人的大腿。计划写八、九篇,大约一二周出一篇。
第一讲,围绕Maven的核心要素POM(Project Object Model)。这一讲是纲领,也许有些介绍看起来是粗粒度的,但是浓缩的都是精华,抽取提炼,更容易看出Maven工程的骨干。
Maven的安装(略)
这个部分本应该包含Maven的下载安装、本地仓库的配置、远程代理的设置、IDE的配置,但是相关资料很容易找到,这里不想多说了,百度一下,很容易的。
POM文件
pom.xml是Maven工程的描述文件,就像一般的JavaEE web工程总有一个web.xml。 一般的pom文件包含以下内容:
<project>
<!-- 本项目切身描述信息 -->
<parent>...</parent>
<modelVersion>4.0.0</modelVersion>
<groupId>...</groupId>
<artifactId>...</artifactId>
<version>...</version>
<packaging>...</packaging>
<name>...</name>
<description>...</description>
<url>...</url>
<inceptionYear>...</inceptionYear>
<licenses>...</licenses>
<organization>...</organization>
<developers>...</developers>
<contributors>...</contributors>
<!-- 依赖信息 -->
<dependencies>...</dependencies>
<dependencyManagement>...</dependencyManagement>
<modules>...</modules>
<properties>...</properties>
<!-- 构建信息 -->
<build>...</build>
<reporting>...</reporting>
<issueManagement>...</issueManagement>
<ciManagement>...</ciManagement>
<mailingLists>...</mailingLists>
<scm>...</scm>
<prerequisites>...</prerequisites>
<!-- 仓库配置信息 -->
<repositories>...</repositories>
<pluginRepositories>...</pluginRepositories>
<distributionManagement>...</distributionManagement>
<profiles>...</profiles>
</project>
这里只给出了一级的标签,这些就是龙骨。对于其中的标签后续会展开。这些标签最好是记住,根据代码中的分块,很容易记忆的。
POM文件的分类
- 超级pom
- 父pom
- 子pom
继承是面向对象语言的一大核心,妥善的处理可以减少代码的冗余和重复。Maven底层是用Java写的,自然不能缺少这一特性。子pom的配置内容可以继承父pom文件的相关配置。子pom文件的parent标签指明父pom文件的位置。parent的具体内容如下:
<parent>
<groupId>org.wso2.carbon</groupId>
<artifactId>platform-parent</artifactId>
<version>4.2.0</version>
<relativePath>../parent/pom.xml</relativePath>
</parent>
其中relativePath是父pom.xml相对于本工程模块的相对路径。这个默认值是…/pom.xml。举例来说,如果是下面展示的路径,模块B使用A的作为父pom,就可以省略relativePath配置。
├── Module_A
│├── Module_B
│└── pom.xml
└── pom.xml
当parent标签省略时,项目默认继承超级pom文件。这个就像是Java的默认继承Object类。超级pom文件在MAVEN_HOME/lib/maven-model-builder-3.2.3.jar - org/apache/maven/model/pom-4.0.0.xml。现在你应该明白一级标签modelVersion的含义了。
POM文件的继承和重写
使用以下命令可以查看当前pom文件继承父文件后的所有属性。
$ mvn help:effective-pom
如果需要重写(Overide)父文件的某一项配置,可以在子文件中个性化对应的配置。但是注意相应的标签级次要完整,必要的标签不可少,如下面的artifactId,这个就是下面说的Maven坐标。
<build>
<pluginManagement>
<plugins>
<plugin>
<artifactId>maven-release-plugin</artifactId>
<version>2.5</version>
</plugin>
</plugins>
</pluginManagement>
</build>
POM文件的组件坐标(标识)
<groupId>com.packt</groupId>
<artifactId>sample-one</artifactId>
<version>1.0.0</version>
上面的配置,多数人知道分别代表公司或组织的域名、工程名称、版本号。但是你知道Maven是怎么定位对应的jar文件(或war)的吗?Maven会在本地仓库的USER_HOME/.m2/repository/com/packt/sample-one/1.0.0/ 下面寻找对应的文件。
当然,对于插件的定位,不需要groupId,具体的原因后续插件的专讲中会提到。
几个简单的例子
这部分用几个简单的例子,来说明pom文件继承的妙用。
举例一、继承不如组合
Java单继承的缘故,有时候继承不如组合更灵活。
对于大型项目的依赖版本的管理,一种做法是通过父pom文件的dependencyManagement统一配置。即如下图所示,父pom的版本属性由配置约定,子pom继承时,无需配置版本,但是依然可以重写父类的版本。
<!-- 父pom -->
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.apache.axis2</groupId>
<artifactId>axis2-transport-mail</artifactId>
<version>${axis2-transports.version}</version>
</dependency>
<dependency>
<groupId>org.apache.ws.commons.axiom.wso2</groupId>
<artifactId>axiom</artifactId>
<version>${axiom.wso2.version}</version>
</dependency>
</dependencies>
</dependencyManagement>
<properties>
<axis2.wso2.version>1.6.1.wso2v10</axis2.wso2.version>
</properties>
<!-- 子pom -->
<dependencies>
<dependency>
<groupId>org.apache.axis2.wso2</groupId>
<artifactId>axis2</artifactId>
</dependency>
<dependency>
<groupId>org.apache.ws.commons.axiom.wso2</groupId>
<artifactId>axiom</artifactId>
</dependency>
</dependencies>
但组合的处理是这样的,把共有的几个依赖抽取出来,单独作为一个pom文件,注意下面的packaging属性是pom。用到依赖的pom文件就能简化:
<project>
<modelVersion>4.0.0</modelVersion>
<groupId>com.packt</groupId>
<artifactId>axis2-client</artifactId>
<version>1.0.0</version>
<packaging>pom</packaging>
<dependencies>
<dependency>
<groupId>org.apache.axis2</groupId>
<artifactId>axis2-kernel</artifactId>
<version>1.6.2</version>
</dependency>
<dependency>
<groupId>org.apache.axis2</groupId>
<artifactId>axis2-adb</artifactId>
<version>1.6.2</version>
</dependency>
<dependency>
<groupId>org.apache.axis2</groupId>
<artifactId>axis2-transport-http</artifactId>
<version>1.6.2</version>
</dependency>
<dependency>
<groupId>org.apache.axis2</groupId>
<artifactId>axis2-transport-local</artifactId>
<version>1.6.2</version>
</dependency>
<dependency>
<groupId>org.apache.axis2</groupId>
<artifactId>axis2-xmlbeans</artifactId>
<version>1.6.2</version>
</dependency>
</dependencies>
</project>
<project>
<modelVersion>4.0.0</modelVersion>
<groupId>com.packt</groupId>
<artifactId>my-axis2-client</artifactId>
<version>1.0.0</version>
<dependencies>
<dependency>
<groupId>com.packt</groupId>
<artifactId>axis2-client</artifactId>
<version>1.0.0</version>
</dependency>
</dependencies>
</project>
举例二、可选依赖
假设这样的一个场景,我们需要基于两种OSGi运行时进行编程,因而我们对两种的各自APIs都做了调用。但是实际程序运行的时候,依据实际底层的OSGi,只有一类的APIs在被调用。简言之,运行时(runtime)有一个jar,但是编译的时候却需要两个jar。对此,加入optional可选依赖。
<project>
<modelVersion>4.0.0</modelVersion>
<groupId>com.packt</groupId>
<artifactId>osgi.client</artifactId>
<version>1.0.0</version>
<dependencies>
<dependency>
<groupId>org.eclipse.equinox</groupId>
<artifactId>osgi</artifactId>
<version>3.1.1</version>
<scope>compile</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.apache.phoenix</groupId>
<artifactId>phoenix-core</artifactId>
<version>3.0.0-incubating</version>
<scope>compile</scope>
<optional>true</optional>
</dependency>
</dependencies>
</project>
<project>
<modelVersion>4.0.0</modelVersion>
<groupId>com.packt</groupId>
<artifactId>my.osgi.client</artifactId>
<version>1.0.0</version>
<dependencies>
<!-- 对实际使用的明确指定 -->
<dependency>
<groupId>org.eclipse.equinox</groupId>
<artifactId>osgi</artifactId>
<version>3.1.1</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>com.packt</groupId>
<artifactId>osgi.client</artifactId>
<version>1.0.0</version>
<scope>compile</scope>
</dependency>
</dependencies>
</project>
举例三、排除依赖
有两个项目分别是这样的:
<!-- 项目1 -->
<project>
<modelVersion>4.0.0</modelVersion>
<groupId>com.packt</groupId>
<artifactId>jose</artifactId>
<version>1.0.0</version>
<dependencies>
<dependency>
<groupId>com.nimbusds</groupId>
<artifactId>nimbus-jose-jwt</artifactId>
<version>2.26</version>
</dependency>
<dependency>
<groupId>net.minidev</groupId>
<artifactId>json-smart</artifactId>
<version>1.0.9</version>
</dependency>
</dependencies>
</project>
<!-- 项目2 -->
<project>
<modelVersion>4.0.0</modelVersion>
<groupId>com.packt</groupId>
<artifactId>jose.ext</artifactId>
<version>1.0.0</version>
<dependencies>
<dependency>
<groupId>com.nimbusds</groupId>
<artifactId>nimbus-jose-jwt</artifactId>
<version>2.26</version>
</dependency>
<dependency>
<groupId>net.minidev</groupId>
<artifactId>json-smart</artifactId>
<version>1.1.1</version>
</dependency>
</dependencies>
</project>
虽然他们的依赖json-smart版本不同,但是在各自的项目里,他们各自为政,不会出现问题。但是如果有第三个项目同时引用以上的项目:
<!-- 项目3 -->
<project>
<modelVersion>4.0.0</modelVersion>
<groupId>com.packt</groupId>
<artifactId>jose.war</artifactId>
<version>1.0.0</version>
<version>war</version>
<dependencies>
<!-- 项目1 -->
<dependency>
<groupId>com.packt</groupId>
<artifactId>jose</artifactId>
<version>1.0.0</version>
</dependency>
<!-- 项目2 -->
<dependency>
<groupId>com.packt</groupId>
<artifactId>jose.ext</artifactId>
<version>1.0.0</version>
</dependency>
</dependencies>
</project>
由于Maven的传递性,各依赖项目原始所需的jar包也会引入。最终会引入json-smart 1.1.1的jar。但是如果我们想使用1.0.9的jar,我们使用exclusions来配置排除掉传递进来的属性。
<project>
<modelVersion>4.0.0</modelVersion>
<groupId>com.packt</groupId>
<artifactId>jose.war</artifactId>
<version>1.0.0</version>
<version>war</version>
<dependencies>
<dependency>
<groupId>com.packt</groupId>
<artifactId>jose</artifactId>
<version>1.0.0</version>
</dependency>
<dependency>
<groupId>com.packt</groupId>
<artifactId>jose.ext</artifactId>
<version>1.0.0</version>
<exclusions>
<exclusion>
<groupId>net.minidev</groupId>
<artifactId>json-smart</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
</project>
术语说明
对于artifact的翻译,本讲是“项目”,但是这个其实就是一个区分用的字符标识。准确的说是jar包(或war包等)的名称(groupId组成路径,artifactId和version组成jar文件名称)。当然,你翻译为工艺品也没毛病,愿每个项目都是我们开发的工艺品。