maven是一个十分强大且应用广泛的自动化构建工具及依赖管理工具,以下是阅读《maven实战》后做的的一些整理笔记。
maven学习笔记
概述
maven是一个十分强大且应用广泛的自动化构建工具及依赖管理工具。
开发过程的一般过程:
- 需求阐述
- 需求用例
- 界面原型
- 简要设计
- 接口
- 模块结构
坐标和依赖
坐标配置
maven构件的坐标通过groupId
、artifactId
、version
、packaging
、classifier
五个属性唯一确定,以下是五个属性的解释
-
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定义了三套互相独立的生命周期,即clean
、default
及site
。每个生命周期包含一些阶段(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在核心为一些主要的生命周期阶段绑定了很多插件的目标,当用户通过命令行调用生命周期阶段的时候,对应的插件目标就会执行相应的任务。具体如下图所示:
自定义绑定:
用户还能够自己选择将某个插件目标绑定到生命周期的某个阶段上
<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>