1. 何为Maven坐标
Maven定义了这样一组规范:世界上任何一个构件都可以使用Maven坐标唯一标识;
Maven坐标包括groupId、atrifactId、version、packaging、classifier。
groupId:必须,定义当前Maven项目隶属的实际项目;
atrifactId:必须,定义实际项目中的一个Maven项目(模块),推荐的做法是使用实际项目名称作为atrifactId的前缀。
version:必须,定义Maven项目当前所处的版本。
packaging:可选(默认jar),定义Maven项目的打包方式。
classifier:不能直接定义,帮助定义构建输出一些附属构建,附属构建于主构件对应。
Maven内置了一个中央仓库的地址(http://repo.maven.apache.org/maven2),Maven仓库的布局也是基于Maven坐标
2.基于一个account-email的POM
(1)首先看一下该模块的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.gqshao.myapp.account</groupId> <artifactId>account-email</artifactId> <name>Account Email</name> <version>1.0.0-SNAPSHOT</version> <dependencies> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-core</artifactId> <version>2.5.6</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-beans</artifactId> <version>2.5.6</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>2.5.6</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context-support</artifactId> <version>2.5.6</version> </dependency> <dependency> <groupId>javax.mail</groupId> <artifactId>mail</artifactId> <version>1.4.1</version> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.7</version> <scope>test</scope> </dependency> <dependency> <groupId>com.icegreen</groupId> <artifactId>greenmail</artifactId> <version>1.3.1b</version> <scope>test</scope> </dependency> </dependencies> <build> <plugins> <plugin> <!-- 使用-source5或更高版本以启动注释插件(apache-maven-3.0.5没用上) --> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <configuration> <source>1.5</source> <target>1.5</target> </configuration> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-resources-plugin</artifactId> <configuration> <encoding>UTF-8</encoding> </configuration> </plugin> </plugins> </build> </project>
该项目坐标:groupId:com.gqshao.myapp.account;artifactId:account-email;version:1.0.0-SNAPSHOT
由于该模块属于账户注册服务项目的一部分,因此,其groupId对应了account项目。该模块的artifactId仍然以account作为前缀,以方便区分其他项目的构建。最后1.0.0-SNAPSHOT表示该版本仍在开发中。
再看dependencies元素,其中包含了多个dependency子元素,这是POM中定义项目依赖的位置。
以第一个依赖为例groupId:org.springframework;artifactId:spring-core;version:2.5.6。这便是依赖坐标,任何一个Maven项目都需要定义自己的坐标,当这个Maven项目成为其他Maven项目的依赖的时候,这组坐标就体现了价值。spring-core、spring-beans、spring-context、spring-context-support是Spring Framework实现依赖注入等功能必要的构件。
在spring-context-support之后有一个javax.mail;mail;1.4.1,是实现发送必须的类库。
紧接着是junit;junit;4.7,是单元测试,这个依赖特殊的地方在于一个值为test的scope子元素,scope用来定义依赖范围。
随后的依赖是com.icegreen;greenmail;1.3.1b是开源邮件服务测试套件。
plugin org.apache.maven.plugins 是开启Java5的支持。
(2)account-email主代码
项目主代码位于src/main/java,资源文件(非Java)位于src/main/resources目录下
配置文件在src/main/resources/account-email.xml中
(3)account-email的测试代码
测试相关的Java代码位于src/test/java目录,相关的资源文件则位于src/test/resources目录
运行mvn clean test执行测试
3.构建account-email
使用mvn clean install构建account-email,Maven会根据POM配置自动下载所需要的依赖构建,执行编译、测试、打包等工作,最后将项目生成的构建account-email-1.0.0-SNAPSHOT.jar安装到本地仓库中。这时,该模块就能供其他Maven项目使用了。
4.依赖的配置
<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"> ... <dependencies> <dependency> <groupId>...</groupId> <artifactId>...</artifactId> <version>...</version> <type>...</type> <scope>...</scope> <optional>...</optional> <exclusions> <exclusion> ... </exclusion> </exclusions> </dependency> ... </dependencies> ... </project>
根元素project下的dependencies可以包含一个或多个dependency元素,以声明一个或多个项目依赖。
每个依赖可以包含的元素有:
groupId、artifactId和Version:依赖的基本坐标,对于任何一个依赖来说,基本坐标是最重要的,Maven根据坐标才能找到需要的依赖。
type:依赖的类型,对应于项目坐标定义的packaging。大部分情况下,该元素不必声明,其默认值为jar
scope:依赖的范围
optional:标记依赖是否可选
exclusions:用来排除传递性依赖
大部分情况下依赖声明只包含基本坐标,然而在一些特殊情况下,其他元素至关重要
5.依赖范围
Maven在编辑项目主代码的时候需要使用一套classpath
比如:
编译classpath:项目主代码需要用到spring-core,该文件以依赖的方式被引入到classpath中。
测试classpath:当Maven在编译和执行测试的时候会使用另外一套classpath,JUnit以依赖的方式引入到测试使用的classpath中,这里的依赖范围是test。
运行classpath:实际运行Maven项目的时候,又会使用一套classpath,上例中的spring-core需要在该classpath中,而JUnit则不需要。
依赖范围就是用来控制这三种classpath(编译、测试、运行classpath)
Maven依赖范围有如下几种:
compile:编译依赖范围(默认)。对于编译、测试、运行三种classpath都有效。比如spring-core
test:测试依赖范围。使用此依赖范围的Maven依赖,只对测试classpath有效。在编译主代码或者运行项目的时候使用时将无法使用此类依赖。比如JUnit。
provided:已提供依赖范围。使用此依赖范围的Maven依赖,只对编译和测试classpath有效。但在运行时无效。比如servlet-api
runtime:运行时依赖范围。使用此依赖范围的Maven依赖,只对测试和运行时classpath有效。但在编译主代码时无效。比如JDBC驱动实现,项目主代码的编译只需要JDK提供的JDBC接口。
system:系统依赖范围,和provided依赖范围一致。但是,必须通过systemPath元素显示的指定依赖路径。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:导入依赖范围,该依赖范围不会对三种classpath产生时间影响,该依赖范围只有在dependencyManagement元素下才有效果,使用该范围的依赖通常指向一个POM,作用是将该POM目标中的dependencyManagement合并到当前POM的dependencyManagement中。即在另一个模块中使用与另一个项目完全一样的dependencyManagement,除了复制配置或者继承这两种方式外,还可以考虑import范围依赖将这一配置导入。
<dependencyManagement> <dependencies> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-framework-bom</artifactId> <version>${spring.version}</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement>
依赖范围对三种classpath的影响
依赖范围(scope) | 编译classpath有效 | 测试classpath | 运行时classpath | 例子 |
compile | Y | Y | Y | spring-core |
test | — | Y | — | JUnit |
provided | Y | Y | — | servlet-api、jsp-api |
runtime | — | Y | Y | JDBC驱动实现、连接池 |
system | Y | Y | — | 本地Maven仓库之外的类库文件 |
6.传递性依赖
(1)何为传递性依赖
背景:一个基于Spring Framework的项目,如果不使用Maven,那么在项目中就需要手动下载相关依赖。由于Spring Framework又会依赖其它开源类库,因此实际中往往会下载一个很大的如spring-framework-2.5.6-with-dependencies.zip的包,包含了所有Spring Framework的jar包,以及所有它依赖的其它jar包。另一种做法是只下载spring-framework-2.5.6.zip,到实际使用的时候,再根据出错信息,或者查询相关文档,加入需要的其他依赖。
Maven的传递性依赖机制可以很好的解决这一问题。以account-email项目为例,该项目有一个org.springframework:spring-core:2.5.6的依赖,而实际上spring-core也有它自己的依赖,可以看到http://repo1.maven.org/maven2/org/springframework/spring-core/2.5.6/spring-core-2.5.6.pom中(可通过http://repo.maven.apache.org/maven2/ --- http://search.maven.org --- 搜索spring找到)可以看到如下信息:
<dependency> <groupId>commons-logging</groupId> <artifactId>commons-logging</artifactId> <version>1.1.1</version> </dependency>
该依赖没有声明依赖范围,那么就是默认的compile。spring-core的依赖范围也是compile。
即在account-email中有一个compile范围的spring-core依赖,spring-core有一个compile范围的commons-logging,那么commons-logging就会成为account-email的compile范围依赖。有了传递性依赖机制,在使用Spring Framework的时候就不用去考虑它依赖了什么,也不用担心引入多余的依赖,Maven会解析各个直接依赖的POM,将那些必要的间接依赖,以传递性依赖的形式引入到当前的项目中。
(2)传递性依赖和依赖范围
依赖范围不仅可以控制依赖与三种classpath的关系,还对传递性依赖产生影响
下面表格中第一列表示第一直接依赖范围,最上面表示第二直接依赖范围,中间交叉的单元格则表示传递性依赖范围
2:compile | 2:test | 2:provided | 2:runtime | |
1:compile | compile | — | — | runtime |
1:test | test | — | — | test |
1:provided | provided | — | provided | provided |
1:runtime | runtime | — | — | runtime |
7.依赖调节
背景:Maven引入的传递性依赖,简化了依赖声明。但有时候也会造成问题。
比如项目A有如下两个依赖关系 A→B→C→X(1.0) 和 A→D→X(2.0)。在这两个依赖关系中X都是A的传递性依赖,但是两条依赖路径上有两个版本的X,由于两个版本都解析会造成依赖重复,那么那个版本的X会被Maven解析使用
Maven依赖调节(Dependcy Mediation)的第一原则:路径最近者优先。上例中X(1.0)路径长度为3,X(2.0)路径长度为2,因此X(2.0)会被解析使用。
Maven依赖调节(Dependcy Mediation)的第二原则:第一声明者优先。
8.可选依赖
背景:假设有这样一个依赖关系,项目A依赖于项目B,项目B依赖于项目X和Y,B对于X和Y都是可选依赖:A→B、B→X(可选),B→Y(可选),那么,X和Y就是A的compile范围传递性依赖。但是X和Y是可选依赖,依赖将不会传递,也就是X和Y将不会对A有任何影响。
使用可选依赖的原因:B实现了两个特性,其中一个特性依赖于X,另一个特性依赖于Y,而且这两个特性是互斥的,用户不可能同时使用两个特性。比如B是一个持久层隔离工具包,支持多种数据库,包括Oracle、MySql等。
使用<optional>表示为可选依赖,它们只会对B项目产生影响,当其他项目依赖于B的时候,这两个依赖不会被传递。因此,当项目A依赖于B的时候,需要显示的声明X或Y这一依赖。
9.最佳实践
(1)排除依赖
背景:情况一,由于传递性依赖的原因,项目依赖了一个SNAPSHOT版本;情况二,有些类库不在中央仓库中。这两种情况下都需要排除依赖性传递,在声明一个稳定版本或可以替换的版本。
例子: A依赖B,B依赖C,此时不想引入传递性C,并显示声明自己对于项目C的依赖。
<project> <modelVersion>4.0.0</modelVersion> <groupId>com.gqshao.myapp</groupId> <artifactId>project-a</artifactId> <version>1.0.0</version> <dependencies> <dependency> <groupId>com.gqshao.myapp</groupId> <artifactId>project-b</artifactId> <version>1.0.0</version> <exclusions> <exclusion> <groupId>com.gqshao.myapp</groupId> <artifactId>project-c</artifactId> </exclusion> </exclusions> </dependency> <dependency> <groupId>com.gqshao.myapp</groupId> <artifactId>project-c</artifactId> <version>1.1.0</version> </dependency> </dependencies> </project>
exclusions:可以包含多个exclusion元素,因此可以排除一个或者多个传递性依赖。
exclusion:声明exclusion只需要groupId和artifactId,而不需要version元素。这是因为只需要groupId和artifactId就能唯一定位依赖图中的某个依赖。也就是说Maven解析后的依赖中,不可能出现groupId和artifactId相同,但是version不同的两个依赖。
(2)归类依赖
背景:有很多关于Spring Framework的依赖,来自同一个项目不同的模块,因此这些依赖的版本都是相同的。如果将来需要升级Spring Framework,这些依赖的版本会一起升级。
解决方法:Maven的属性,通过properties元素定义Maven属性,Maven会将所有${属性名}替换成实际值。
<?xml version="1.0" encoding="UTF-8"?> <project> <modelVersion>4.0.0</modelVersion> <groupId>com.gqshao.myapp</groupId> <artifactId>project-a</artifactId> <version>1.0.0</version> <properties> <springframework.version>2.5.6</springframework.version> </properties> <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> </dependencies> </project>
(3)优化依赖
背景:在软件开发中,程序员会通过重构等不同方式不断优化自己的代码,同理,程序员也应该对Maven项目的依赖了然于胸,并对其进行优化,如去除多余的依赖,显示的声明某些必要的依赖。
Maven会自动解析所有直接依赖和传递性依赖,并根据规则正确判断每个依赖的范围。对一些依赖冲突也能进行调节,确保任何一个构件只有唯一的版本在依赖中存在,在这个工作之后得到的那些依赖被称为已解析依赖(Resovled Dependency),
运行下面命令查看当前项目已解析依赖:
mvn dependency:list
如果要显示树状结构通过下面命令
mvn dependency:tree
显示使用但未声明的依赖与声明但未使用的依赖(由于内容太多,输出到文件)
mvn dependency:analyze >c:\analyze.txt
下载所有的依赖
mvn dependency:copy-dependencies -DoutputDirectory=./src/main/webapp/WEB-INF/lib
结果分成两个部分Used undeclared dependencies(项目中使用到,但未显示声明)和Unused declared dependencies(项目中未使用,但显示声明的依赖)
注意:由于 dependency:analyze 只会分析编译主代码和测试代码所用到的依赖,一些执行测试和运行时需要的依赖发现不了,所以Unused declared dependencies要根据实际情况分析。
10.向私库中添加中央仓库没有的依赖
例如:oracle jdbc driver
(1)从官网下载jdbc driver,放在D:\下
(2)执行命令进安装
mvn install:install-file -DgroupId=com.oracle -DartifactId=ojdbc6 -Dversion=11.2.0.1.0 -Dpackaging=jar -Dfile=ojdbc6.jar
此时在~\.m2\repository\oracle\ojdbc6\11.2.0.1.0下可以看到ojdbc6.jar文件
(3)POM中添加如下依赖信息
<dependency> <groupId>com.oracle</groupId> <artifactId>ojdbc6</artifactId> <version>11.2.0.1.0</version> </dependency>
或根据项目添加
mvn dependency:copy-dependencies