`

三. 坐标和依赖

 
阅读更多

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

 

分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics