maven是一个十分强大且应用广泛的自动化构建工具及依赖管理工具,以下是阅读《maven实战》后做的的一些整理笔记。

maven学习笔记

概述

maven是一个十分强大且应用广泛的自动化构建工具及依赖管理工具。

开发过程的一般过程:

  1. 需求阐述
  • 需求用例
  • 界面原型
  1. 简要设计
  • 接口
  • 模块结构

坐标和依赖

坐标配置

maven构件的坐标通过groupIdartifactIdversionpackagingclassifier五个属性唯一确定,以下是五个属性的解释

  • groupId:定义当前maven项目隶属的实际项目,一般为项目所属组织或公司加上项目名,例如:org.sonatype.nexus

  • artifactId:定义实际项目中的一个Maven项目(模块),一般为项目名加上模块,例如nexus-indexer

  • version:定义Maven项目当前所处的版本

  • packaging:定义Maven项目的打包方式,默认为jar

  • classifier:定义构建输出的一些附属构件

依赖配置

一般依赖包含的元素:

<project>
    ……
    <dependencies>
        <dependency>
            <groupId>……</groupId>
            <artifactId>……</artifactId>
            <version>……</version>
            <type>……</type>
            <scope>……</scope>
            <optional>……</optional>
            <exclusions>
                <exclusion>
                    ……
                </exclusion>
                ……
            </exclusions>
        </dependency>
        ……
    </dependencies>
    ……
</project>

依赖范围:

依赖范围就是用来控制依赖与这三种classpath(编译classpath、测试classpath、运行classpath)的关系,Maven有以下几种依赖范围:

  • compile:编译依赖范围。如果没有指定,就会默认使用该依赖范围。使用此依赖范围的Maven依赖,对于编译、测试、运行三种classpath都有效。典型的例子是spring-core,在编译、测试和运行的时候都需要使用该依赖。

  • test:测试依赖范围。使用此依赖范围的Maven依赖,只对于测试classpath有效,在编译主代码或者运行项目的使用时将无法使用此类依赖。典型的例子是JUnit,它只有在编译测试代码及运行测试的时候才需要。

  • provided:已提供依赖范围。使用此依赖范围的Maven依赖,对于编译和测试class-path有效,但在运行时无效。典型的例子是servlet-api,编译和测试项目的时候需要该依赖,但在运行项目的时候,由于容器已经提供,就不需要Maven重复地引入一遍。

  • runtime:运行时依赖范围。使用此依赖范围的Maven依赖,对于测试和运行class-path有效,但在编译主代码时无效。典型的例子是JDBC驱动实现,项目主代码的编译只需要JDK提供的JDBC接口,只有在执行测试或者运行项目的时候才需要实现上述接口的具体JDBC驱动。

  • system:系统依赖范围。该依赖与三种classpath的关系,和provided依赖范围完全一致。但是,使用system范围的依赖时必须通过systemPath元素显式地指定依赖文件的路径。由于此类依赖不是通过Maven仓库解析的,而且往往与本机系统绑定,可能造成构建的不可移植,因此应该谨慎使用。systemPath元素可以引用环境变量,如:

<dependency>
    <groupId>javax.sql</groupId>
    <artifactId>jdbc-stdext</artifactId>
    <version>2.0</version>
    <scope>system</scope>
    <systemPath>${java.home}/lib/rt.jar</systemPath>
</dependency>
  • import(Maven 2.0.9及以上):导入依赖范围。该依赖范围不会对三种classpath产生实际的影响。

传递性依赖:

account-mail有一个compile范围的spring-core依赖,spring-core有一个compile范围的commons-logging依赖,那么commons-logging就会成为account-email的compile范围依赖,commons-logging是account-email的一个传递性依赖,如下图所示: 传递性依赖关系图

依赖范围不仅可以控制依赖与三种classpath的关系,还对传递性依赖产生影响。以下是依赖范围与传递依赖的关系表,行为第一依赖范围,列为第二依赖范围: 依赖范围与依赖传递关系表

依赖调解:

即第一依赖下的依赖或间接依赖为同一构件(版本不同),这时候就需要按照maven的依赖调节原则决定引入哪个版本的构件:

  • 第一原则:路径最近者优先。如下则2.0版本会被引用
A->B->C->X(1.0)、A->D->X(2.0)
  • 第二原则:第一声明者优先。如下则1.0版本会被引用
A->B->Y(1.0)、A->C->Y(2.0)

可选依赖: 关系如下所示,即使B对于A是compile范围的依赖,X及Y也不会成为A的传递性依赖 可选依赖

依赖排除: 有时候需要将第一依赖下的第二依赖排除,并将第二依赖的另一版本显式地引入,这就是依赖排除,格式如下:

<project>
    <modelVersion>4.0.0</modelVersion>
    <groupId>com.juvenxu.mvnbook</groupId>
    <artifactId>project-a</artifactId>
    <version>1.0.0</version>
    <dependencies>
    <dependency>
        <groupId>com.juvenxu.mvnbook</groupId>
        <artifactId>project-b</artifactId>
        <version>1.0.0</version>
        <exclusions>
            <exclusion>
                <groupId>com.juvenxu.mvnbook</groupId>
                <artifactId>project-c</artifactId>
            </exclusion>
        </exclusions>
    </dependency>
        <dependency>
            <groupId>com.juvenxu.mvnbook</groupId>
            <artifactId>project-c</artifactId>
            <version>1.1.0</version>
        </dependency>
    </dependencies>
</project>

关系图如下所示:

依赖排除

依赖归类:

即使用properties元素定义Maven属性,并将相关类别的依赖统一为一个版本,相当于java的提取变量

<springframework.version>2.5.6</springframework.version>
<dependencies>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-core</artifactId>
        <version>${springframework.version}</version>
    </dependency>
    ...
</dependencies>

优化依赖:

  • mvn dependency:list:查看当前项目的已解析依赖

  • mvn dependency:tree:查看当前项目依赖树

  • mvn dependency:analyze:依赖分析,包含两个部分,Used undeclared dependencies展示用到了没有显示声明的依赖;Unused declared dependencies展示了为声明但显示声明的依赖

仓库

何为仓库

在Maven世界中,任何一个依赖、插件或者项目构建的输出,都可以称为构件,而以在某个位置统一存储所有Maven项目共享的构件,这个统一的位置就是仓库。

仓库分类

分为两种,远程和本地仓库,如下所示:

仓库分类

配置远程仓库

POM文件中配置远程仓库:

<project>
……
<repositories>
    <repository>
        <id>jboss</id>
        <name>JBoss Repository</name>
        <url>http://repository.jboss.com/maven2/</url>
        <releases>
            <enabled>true</enabled>
        </releases>
        <snapshots>
            <enabled>false</enabled>
        </snapshots>
        <layout>default</layout>
    </repository>
</repositories>
……
</project>

settings.xml中配置仓库认证:

<settings>
……
    <servers>
        <server>
            <id>my-proj</id>
            <username>repo-user</username>
            <password>repo-pwd</password>
        </server>
    </servers>
……
</settings>

部署至远程仓库

POM中配置deloy仓库地址:

<project>
    ……
    <distributionManagement>
        <repository>
            <id>proj-releases</id>
            <name>Proj Release Repository</name>
            <url>http://192.168.1.100/content/repositories/proj-releases</url>
        </repository>
        <snapshotRepository>
            <id>proj-snapshots</id>
            <name>Proj Snapshot Repository</name>
            <url>http://192.168.1.100/content/repositories/proj-snapshots</url>
        </snapshotRepository>
    </distributionManagement>
    ……
</project>

镜像

任何一个可以从仓库Y获得的构件,都能够从它的镜像中获取。即镜像代理中央仓库的请求,配置如下:

<settings>
    ……
    <mirrors>
        <mirror>
            <id>maven.net.cn</id>
            <name>one of the central mirrors in China</name>
            <url>http://maven.net.cn/content/groups/public/</url>
            <mirrorOf>central</mirrorOf>
        </mirror>
    </mirrors>
    ……
</settings>

更常见的是使用nexus私服代理中央仓库的构件请求

<settings>
    ……
<mirrors>
    <mirror>
        <id>internal-repository</id>
        <name>Internal Repository Manager</name>
        <url>http://192.168.1.100/maven2/</url>
        <mirrorOf>*</mirrorOf>
    </mirror>
</mirrors>
    ……
</settings>

生命周期及插件

生命周期

maven定义了三套互相独立的生命周期,即cleandefaultsite。每个生命周期包含一些阶段(phase),这些阶段是有顺序的,并且后面的阶段依赖于前面的阶段。各生命周期内阶段互不影响,各生命周期作用如下所示:

  • clean:清理项目

  • default:构建项目

  • site:建立项目站点

clean生命周期的三个阶段:

  • pre-clean:行一些清理前需要完成的工作

  • clean:清理上一次构建生成的文件

  • post-clean:执行一些清理后需要完成的工作

default生命周期的N个阶段(只列举重要阶段):

  • process-sources:处理项目主资源文件。一般来说,是对src/main/resources目录的内容进行变量替换等工作后,复制到项目输出的主classpath目录中
  • ……
  • compile:编译项目的主源码。一般来说,是编译src/main/java目录下的Java文件至项目输出的主classpath目录中
  • ……
  • process-test-sources:处理项目测试资源文件。一般来说,是对src/test/resources目录的内容进行变量替换等工作后,复制到项目输出的测试classpath目录中
  • ……
  • test-compile:编译项目的测试代码。一般来说,是编译src/test/java目录下的Java文件至项目输出的测试classpath目录中
  • ……
  • test:使用单元测试框架运行测试,测试代码不会被打包或部署
  • ……
  • package:接受编译好的代码,打包成可发布的格式,如JAR
  • ……
  • install:将包安装到Maven本地仓库,供本地其他Maven项目使用
  • deloy:将最终的包复制到远程仓库,供其他开发人员和Maven项目使用

site生命周期的四个阶段:

  • pre-site:执行一些在生成项目站点之前需要完成的工作
  • site:生成项目站点文档
  • post-site:执行一些在生成项目站点之后需要完成的工作
  • site-deploy:将生成的项目站点发布到服务器上

插件

插件目标:

插件功能聚合到一个插件中,而每个插件功能就是一个插件目标,CL执行格式:

mvn comliper:compile

插件绑定:

Maven的生命周期与插件相互绑定,用以完成实际的构建任务。

内置绑定:

Maven在核心为一些主要的生命周期阶段绑定了很多插件的目标,当用户通过命令行调用生命周期阶段的时候,对应的插件目标就会执行相应的任务。具体如下图所示:

clean内置绑定插件

default内置绑定插件

site内置绑定插件

自定义绑定:

用户还能够自己选择将某个插件目标绑定到生命周期的某个阶段上

<build>
    <plugins>
        <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-source-plugin</artifactId>
        <version>2.1.1</version>
        <executions>
            <execution>
            <id>attach-sources</id>
            <phase>verify</phase>
            <goals>
                <goal>jar-no-fork</goal>
            </goals>
            </execution>
        </executions>
        </plugin>
    </plugins>
</build>

插件配置:

命令行配置形式:

mvn install -D maven.test.skip=true  //跳过执行单元测试

POM中全局配置:

<build>
    <plugins>
        <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-compiler-plugin</artifactId>
        <version>2.1</version>
        <configuration>
            <source>1.5</source>
            <target>1.5</target>
        </configuration>
        </plugin>
    </plugins>
</build>

聚合及继承

聚合

模块化开发下,通过一个模块聚合构建整个项目需要的模块,格式如下:

<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/maven-v4_0_0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>com.juvenxu.mvnbook.account</groupId>
    <artifactId>account-aggregator</artifactId>
    <version>1.0.0-SNAPSHOT</version>
    <packaging>pom</packaging>
    <name>Account Aggregator</name>
    <modules>
        <module>account-email</module>
        <module>account-persist</module>
    </modules>
</project>

注: 对于聚合模块来说,其打包方式packaging的值必须为pom,否则就无法构建。

继承

定义parent模块,抽取出重复的配置,这就是POM的继承。基本配置:

<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/maven-v4_0_0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>com.juvenxu.mvnbook.account</groupId>
    <artifactId>account-parent</artifactId>
    <version>1.0.0-SNAPSHOT</version>
    <packaging>pom</packaging>
    <name>Account Parent</name>
</project>

注: 作为父模块,其packing配置节必须为POM

子模块的引用方式:

<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/maven-v4_0_0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
    <groupId>com.juvenxu.mvnbook.account</groupId>
    <artifactId>account-parent</artifactId>
    <version>1.0.0-SNAPSHOT</version>
    <relativePath>../account-parent/pom.xml</relativePath>
    </parent>
    <artifactId>account-email</artifactId>
    <name>Account Email</name>
    <dependencies>
        ……
    </dependencies>
    <build>
        <plugins>
            ……
        </plugins>
    </build>
</project>

依赖管理:

Maven提供的dependencyManagement元素既能让子模块继承到父模块的依赖配置,又能保证子模块依赖使用的灵活性。在dependencyManagement元素下的依赖声明不会引入实际的依赖,不过它能够约束dependencies下的依赖使用。

父模块配置如下:

<project
xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/maven-v4_0_0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>com.juvenxu.mvnbook.account</groupId>
    <artifactId>account-parent</artifactId>
    <version>1.0.0-SNAPSHOT</version>
    <packaging>pom</packaging>
    <name>Account Parent</name>
    <properties>
        <springframework.version>2.5.6</springframework.version>
        <junit.version>4.7</junit.version>
    </properties>
    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework</groupId>
                <artifactId>spring-core</artifactId>
                <version>${springframework.version}</version>
            </dependency>
            <dependency>
                <groupId>org.springframework</groupId>
                <artifactId>spring-beans</artifactId>
                <version>${springframework.version}</version>
            </dependency>
            <dependency>
                <groupId>org.springframework</groupId>
                <artifactId>spring-context</artifactId>
                <version>${springframework.version}</version>
            </dependency>
            <dependency>
                <groupId>org.springframework</groupId>
                <artifactId>spring-context-support</artifactId>
                <version>${springframework.version}</version>
            </dependency>
            <dependency>
                <groupId>junit</groupId>
                <artifactId>junit</artifactId>
                <version>${junit.version}</version>
                <scope>test</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>
</project>

子模块的配置如下:

<properties>
    <javax.mail.version>1.4.1</javax.mail.version>
    <greenmail.version>1.3.1b</greenmail.version>
</properties>
<dependencies>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-core</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-beans</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context-support</artifactId>
    </dependency>
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
    </dependency>
    <dependency>
        <groupId>javax.mail</groupId>
        <artifactId>mail</artifactId>
        <version>${javax.mail.version}</version>
    </dependency>
    <dependency>
        <groupId>com.icegreen</groupId>
        <artifactId>greenmail</artifactId>
        <version>${greenmail.version}</version>
        <scope>test</scope>
    </dependency>
</dependencies>

使用import范围依赖导入依赖管理配置:

<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>com.juvenxu.mvnbook.account</groupId>
            <artifactId>account-parent</artifactId>
            <version>1.0-SNAPSHOT</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    </dependencies>
</dependencyManagement>

插件管理:

类似地,Maven也提供了pluginManagement元素帮助管理插件。在该元素中配置的依赖不会造成实际的插件调用行为,当POM中配置了真正的plugin元素,并且其groupId和artifactId与pluginManagement中配置的插件匹配时,pluginManagement的配置才会影响实际的插件行为。调用方式与依赖管理大同小异

聚合与继承的关系:

关系如下: 聚合

继承

在实际项目中聚合和继承POM往往同一个,这样可以简化配置

灵活的构建

基于不同环境执行不同的构建,maven为了支持构建的灵活性,内置了三大特性,即属性、Profile和资源过滤

maven属性

分为六类,如下所示

  • 内置属性:主要为${basedir}${version}${basedir}表示项目根目录,${version}表示项目版本

  • POM属性:用户可以使用该类属性引用POM文件中对应元素的值。例如${project.artifactId}就对应了<project><artifactId>元素的值

  • 自定义属性:用户在POM的<properties>元素下自定义的Maven属性

  • Settings属性:与POM属性同理,用户使用以settings.开头的属性引用settings.xml文件中XML元素的值,如常用的${settings.localRepository}指向用户本地仓库的地址

  • Java系统属性:所有Java系统属性都可以使用Maven属性引用,例如${user.home}指向了用户目录。用户可以使用mvn help:system查看所有的Java系统属性

  • 环境变量属性:所有环境变量都可以使用以env.开头的Maven属性引用。例如${env.JAVA_HOME}指代了JAVA_HOME环境变量的值。用户可以使用mvn help:system查看所有的环境变量

Maven Profile

为了能让构建在各个环境下方便地移植,Maven引入了profile的概念。profile能够在构建的时候修改POM的一个子集,或者添加额外的配置元素。用户可以使用很多方式激活profile,以实现构建在不同环境下的移植。基于开发环境和测试环境的profile的配置如下:

<profiles>
    <profile>
        <id>dev</id>
        <properties>
            <db.driver>com.mysql.jdbc.Driver</db.driver>
            <db.url>jdbc:mysql://localhost:3306/test</db.url>
            <db.username>dev</db.username>
            <db.password>dev-pwd</db.password>
        </properties>
    </profile>
    <profile>
        <id>test</id>
        <properties>
            <db.driver>com.mysql.jdbc.Driver</db.driver>
            <db.url>jdbc:mysql://192.168.1.100:3306/test</db.url>
            <db.username>test</db.username>
            <db.password>test-pwd</db.password>
        </properties>
    </profile>
</profiles>

激活方式:

  • 命令行形式激活
mvn clean install -P dev-x,dev-y
  • settings文件显式激活:如果用户希望某个profile默认一直处于激活状态,就可以配置settings.xml文件的active-Profiles元素,如
<settings>
……
<activeProfiles>
    <activeProfile>dev-x</activeProfile>
</activeProfiles>
……
</settings>
  • 用户可以在定义profile的时候指定其默认激活
<profiles>
    <profile>
        <id>dev</id>
        <activation>
            <activeByDefault>true</activeByDefault>
        </activation>
    ……
    </profile>
</profiles>