微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

三. 坐标和依赖

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

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


迭代器模式(Iterator)迭代器模式(Iterator)[Cursor]意图:提供一种方法顺序访问一个聚合对象中的每个元素,而又不想暴露该对象的内部表示。应用:STL标准库迭代器实现、Java集合类型迭代器等模式结构:心得:迭代器模式的目的是在不获知集合对象内部细节的同时能对集合元素进行遍历操作
高性能IO模型浅析服务器端编程经常需要构造高性能的IO模型,常见的IO模型有四种:(1)同步阻塞IO(BlockingIO):即传统的IO模型。(2)同步非阻塞IO(Non-blockingIO):默认创建的socket都是阻塞的,非阻塞IO要求socket被设置为NONBLOCK。注意这里所说的N
策略模式(Strategy)策略模式(Strategy)[Policy]意图:定义一系列算法,把他们封装起来,并且使他们可以相互替换,使算法可以独立于使用它的客户而变化。应用:排序的比较方法、封装针对类的不同的算法、消除条件判断、寄存器分配算法等。模式结构:心得:对对象(Context)的处理操作可
访问者模式(Visitor)访问者模式(Visitor)意图:表示一个作用于某对象结构中的各元素的操作,它使你在不改变各元素的类的前提下定义作用于这些元素的新操作。应用:作用于编译器语法树的语义分析算法。模式结构:心得:访问者模式是要解决对对象添加新的操作和功能时候,如何尽可能不修改对象的类的一种方
命令模式(Command)命令模式(Command)[Action/Transaction]意图:将一个请求封装为一个对象,从而可用不同的请求对客户参数化。对请求排队或记录请求日志,以及支持可撤消的操作。应用:用户操作日志、撤销恢复操作。模式结构:心得:命令对象的抽象接口(Command)提供的两个
生成器模式(Builder)生成器模式(Builder)意图:将一个对象的构建和它的表示分离,使得同样的构建过程可以创建不同的表示。 应用:编译器词法分析器指导生成抽象语法树、构造迷宫等。模式结构:心得:和工厂模式不同的是,Builder模式需要详细的指导产品的生产。指导者(Director)使用C
设计模式学习心得《设计模式:可复用面向对象软件的基础》一书以更贴近读者思维的角度描述了GOF的23个设计模式。按照书中介绍的每个设计模式的内容,结合网上搜集的资料,我将对设计模式的学习心得总结出来。网络上关于设计模式的资料和文章汗牛充栋,有些文章对设计模式介绍生动形象。但是我相信“一千个读者,一千个
工厂方法模式(Factory Method)工厂方法模式(Factory Method)[Virtual Constructor]意图:定义一个用于创建对象的接口,让子类决定实例化哪一个类,使一个类的实力化延迟到子类。应用:多文档应用管理不同类型的文档。模式结构:心得:面对同一继承体系(Produc
单例模式(Singleton)单例模式(Singleton)意图:保证一个类只有一个实例,并提供一个访问它的全局访问点。应用:Session或者控件的唯一示例等。模式结构:心得:单例模式应该是设计模式中最简单的结构了,它的目的很简单,就是保证自身的实例只有一份。实现这种目的的方式有很多,在Java中
装饰者模式(Decorator)装饰者模式(Decorator)[Wrapper]意图:动态的给一个对象添加一些额外的职责,就增加功能来说,比生成子类更为灵活。应用:给GUI组件添加功能等。模式结构:心得:装饰器(Decorator)和被装饰的对象(ConcreteComponent)拥有统一的接口