Maven实战 -5. 坐标和依赖 - 高飞网

5. 坐标和依赖

2017-08-08 10:44:02.0

5.1 何为Maven坐标

    Maven坐标的元素包括groupId、artifactId、version、packaging、classififer。

5.2 坐标详解

<dependency>
    <groupId>org.sonatype.nexus</groupId>
    <artifactId>nexus-indexer</artifactId>
    <version>2.0.0</version>
</dependency>
  1. groupId:定义当前Maven项目的实际项目。通常是域名反写。
  2. artifactId:该元素定义实际项目中的一个Maven项目(模块),推荐的做法是使用实际项目名称作为artifactId的前缀。而Maven默认生成构件时会以artifactId开头,如nexus-indexer-2.0.0.jar
  3. version:该元素定义Maven项目当前所处的版本。
  4. packaging:该元素定义Maven项目的打包方式。对应构件的扩展名。如packaging为jar,则生成nexus-indexer-2.0.0.jar,如果是war,会生成.war的war包(tomcat使用的web项目)。默认使用jar打包方式。
  5. classifier:该元素用来帮助定义构建输出的一些附属构件。附属构件与主构件对应,如上例中的主构件是nexus-indexer-2.0.0.jar,还可以通过插件生成如nexus-indexer-2.0.0-javadoc.jar、nexus-indexer-2.0.0-sources.jar这样一些附属构件,其包含了Java文档和源代码。注意,不能直接定义项目的classifier,因为附属构件不是项目直接默认生成的,而是由附加插件帮助生成。

    上述5个元素中,groupId、artifactId、version是必须定义的,packaging是可选的(默认为jar),而classifier是不能直接定义的。

    同时,项目构件的文件名是与坐标相对应的,一般的规则为artifactId-version[-classifier].packaging,[-classifier]表示可选。

5.4 依赖配置

    一个依赖声明可以包含如下的一些元素:

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

    根元素project下的dependencies可以包含一个或多个dependency元素,以声明一个或多个项目依赖。每个依赖可以包含:

  1. groupId、artifactId和version:依赖的基本坐标,对于任何一个作事来说,基本坐标是最重要的,Maven根据坐标都能找到需要的依赖。
  2. type:依赖的类型,对应项目坐标定义的packaging。大部分情况下,该元素不必声明,默认值为jar。
  3. scope:依赖的范围。
  4. optional:标记依赖是否可选。
  5. exclusions:用来排队传递性依赖。

5.5 依赖范围

    Maven在编译项目主代码的时候需要使用一套classpath,而Maven在编译和执行测试的时候会使用另外一套classpath,最后,实际运行Maven项目的时候,又会使用一套classpath。依赖范围就是用来控制依赖与这三种classpath(编译classpath、测试classpath,运行classpath)的关系,Maven有以下几种依赖范围:

  1. compile:编译依赖范围。默认的依赖范围。使用此依赖范围的Maven依赖,对于编译、测试、运行三种classpath都有效。
  2. test:测试依赖范围。使用此依赖范围的Maven依赖,只对测试classpath有效,在编译主代码或者运行项目的使用时将无法使用此类依赖。
  3. provided:已提供范围。使用此依赖的Maven范围,对于编译和测试classpath有效,但在运行时无法效。典型的例子是servlet-api,编译和测试项目的时候需要该依赖,但在运行的时候,由于容器已提供,就不需要Maven重复地引入一遍。
  4. runtime:运行时依赖范围。使用此依赖范围的Maven依赖,对于测试和运行classpath有效,但在编译主代码时无效。典型的例子是JDBC驱动实现,项目主代码的编译只需要JDK提供的JDBC接口,只有在执行测试或运行项目时才需要实现上述接口的具体JDBC驱动。
  5. 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>
  6. import(Maven2.0.9及以上):导入依赖范围。该依赖范围不会对三种classpath产生实际影响,之后介绍。

5.6 传递性依赖

5.6.1 何为传递性依赖

    如果没有使用Maven,想使用spring时,要么把所有spring涉及到jar都加载到classpath中,要么根据报错信息,一个个的添加需要的jar,前者会用到很多无用的jar依赖,后者也很麻烦。

    使用Maven,传递性依赖机制可以很好地解决这一问题。Maven会解析各个直接依赖的POM,将那些必要的间接依赖,以传递性依赖的形式引入到当前项目中。

5.6.2 传递性依赖和依赖范围

    依赖范围不仅可以控制依赖与三种classpath的关系,还对传递性依赖产生影响。如compile依赖范围,间接依赖的范围也是compile,那么间接依赖也是compile。对于依赖A->B->C,A对B叫直接依赖,B对C是第二直接依赖,A对C是传递性依赖。第一直接的范围和第二直接依赖的范围决定了传递依赖的范围。

    上图中最左边一行表示第一直接依赖,最上一行表示第二直接依赖,中间的交叉单元格则表示传递性依赖。


5.7 依赖调解

    Maven引入的传递性依赖机制,大大简化和方便了依赖声明,另一方面,大部分情况下只需要关心直接依赖是什么,而不用考虑这些直接依赖会引入什么传递性依赖。但有时候,当传递性依赖造成问题的时候,就需要清楚地知道该传递性依赖是从哪条依赖路径引入的。

    例如项目A有这样的依赖关系:A->B->C->X(1.0)、A->D->X(2.0),X是A的传递性依赖,但有两个版本的X,因此,Maven依赖调解(Dependency Mediation)的第一原则是:路径最近者优先。

    但有时有这样的依赖关系:A->B->Y(1.0)、A->C->Y(2.0),两者路径长度一致。在Maven 2.0.8及之前的版本中,是不确定的,在Maven 2.0.9开始,以第一声明为先。

5.8 可选依赖

    假设有这样一个依赖关系,项目A依赖于项目B,项目B依赖于项目X和Y,B对于X和Y的依赖都是可选依赖:A->B、B->X(可选)、B->Y(可选)。由于X和Y是可选的,因此X和Y不会对A有任何影响。

    这样的项目一般有多个特性,例如B项目中支持MySQL或PostgreSQL,两者是互斥的,如果A依赖于B,想使用B项目,则必须显式的声明使用MySQL或PostgreSQL。

5.9 最佳实践

5.9.1 排除依赖

    假设有一个项目依赖了一个第三方依赖,而这个第三方依赖所引用的另一个类库是SNAPSHOT版本,那么这个SNAPSHOT就会成为当前项目的传递性依赖,而SNAPSHOT的不稳定性会直接影响到当前的项目。这就需要将之排除。

<dependency>
	<groupId>...</groupId>
	<artifactId>...</artifactId>
	<version>...</version>
	<exclusions>
		<exclusion>
			<groupId>...</groupId>
			<artifactId>...</artifactId>
		</exclusion>
	</exclusions>
</dependency>

5.9.2 归类依赖

    假设现在项目中使用了spring,而spring分为n个模块,而使用一个版本的spring,各个模块的版本号是一致的,因此可以将版本号归类为一个变量,修改版本时,只要改这个变量就可以了。

...
<properties>
	<springframework.version>3.0.6.RELEASE</springframework.version>
</properties>
<dependencies>
	...
	<dependency>
		<groupId>org.springframework</groupId>
		<artifactId>spring-test</artifactId>
		<scope>test</scope>
		<version>${springframework.version}</version>
	</dependency>
	<dependency>
		<groupId>org.springframework</groupId>
		<artifactId>spring-context-support</artifactId>
		<version>${springframework.version}</version>
	</dependency>
	...
</dependencies>
...

5.9.3 优化依赖

    Maven会自动解析所有项目的直接依赖和传递性依赖,并且根据规则正确判断每个依赖的范围,对于一些依赖冲突,也能进行调节,以确保任何一个构件只有唯一的版本的依赖中存在。在这些工作之后,最后得到的那些依赖被称为解析依赖(Resolved Dependency)。可以运行如下命令查看当前项目的已解析依赖:

mvn dependency:list

结果为:

mvn dependency:list
[INFO] Scanning for projects...
[WARNING]
[WARNING] Some problems were encountered while building the effective model for cn.demo:hello-world:war:1.0-SNAPSHOT
[WARNING] 'dependencies.dependency.(groupId:artifactId:type:classifier)' must be unique: org.springframework:spring-test:jar -> duplicate declaration of version ${springframework.version} @ line 105, column 15
[WARNING] 'dependencies.dependency.(groupId:artifactId:type:classifier)' must be unique: junit:junit:jar -> version 4.4 vs 4.11 @ line 131, column 15
[WARNING]
[WARNING] It is highly recommended to fix these problems because they threaten the stability of your build.
[WARNING]
[WARNING] For this reason, future Maven versions might no longer support building such malformed projects.
[WARNING]
[INFO]
[INFO] ------------------------------------------------------------------------
[INFO] Building hello-world 1.0-SNAPSHOT
[INFO] ------------------------------------------------------------------------
[INFO]
[INFO] --- maven-dependency-plugin:2.1:list (default-cli) @ hello-world ---
[INFO]
[INFO] The following files have been resolved:
[INFO]    aopalliance:aopalliance:jar:1.0:compile
[INFO]    biz.paluch.redis:lettuce:jar:3.5.0.Final:compile
[INFO]    com.aliyun.oss:aliyun-sdk-oss:jar:2.0.6:compile
[INFO]    com.google.guava:guava:jar:17.0:compile
[INFO]    commons-beanutils:commons-beanutils:jar:1.8.0:compile

    通过上面的结果,可知有两个依赖重复了,可以删除一个。另外还列出了解析好的结果。

    如果想查看当前项目的依赖树:

mvn dependency:tree

结果为:

mvn dependency:tree
[INFO] Scanning for projects...
[INFO]
[INFO] ------------------------------------------------------------------------
[INFO] Building hello-world 1.0-SNAPSHOT
[INFO] ------------------------------------------------------------------------
[INFO]
[INFO] --- maven-dependency-plugin:2.1:tree (default-cli) @ hello-world ---
[WARNING] Missing POM for nl.justobjects.pushlet:pushlet:jar:2.0.4
[INFO] cn.demo:hello-world:war:1.0-SNAPSHOT
[INFO] +- javax.servlet:servlet-api:jar:2.5:provided
[INFO] +- jstl:jstl:jar:1.2:compile
[INFO] +- commons-fileupload:commons-fileupload:jar:1.2.2:compile
[INFO] +- commons-io:commons-io:jar:2.3:compile
[INFO] +- junit:junit:jar:4.4:test
[INFO] +- mysql:mysql-connector-java:jar:5.1.18:compile
[INFO] +- org.mybatis:mybatis:jar:3.0.6:compile
[INFO] +- org.springframework:spring-test:jar:3.0.6.RELEASE:test (scope not updated to compile)
[INFO] +- org.springframework:spring-context-support:jar:3.0.6.RELEASE:compile
[INFO] |  +- org.springframework:spring-beans:jar:3.0.6.RELEASE:compile
[INFO] |  +- org.springframework:spring-context:jar:3.0.6.RELEASE:compile
[INFO] |  \- org.springframework:spring-core:jar:3.0.6.RELEASE:compile
[INFO] +- org.springframework:spring-tx:jar:3.0.6.RELEASE:compile
[INFO] |  \- aopalliance:aopalliance:jar:1.0:compile

    使用dependency:list和dependency:tree可以帮助我们详细了解项目中所有依赖的具体信息,在此基础上,还有dependency:analyze工具可以帮助分析当前项目的依赖。

mvn dependency:analyze

结果为:

[INFO] --- maven-dependency-plugin:2.1:analyze (default-cli) @ gf-www-web ---
[WARNING] Used undeclared dependencies found:
[WARNING]    log4j:log4j:jar:1.2.16:compile
[WARNING]    commons-beanutils:commons-beanutils:jar:1.8.0:compile
[WARNING]    org.springframework:spring-web:jar:3.0.6.RELEASE:compile
[WARNING]    org.springframework:spring-beans:jar:3.0.6.RELEASE:compile
[WARNING]    commons-codec:commons-codec:jar:1.9:compile
[WARNING]    org.htmlparser:htmllexer:jar:2.1:compile
[WARNING]    org.springframework:spring-context:jar:3.0.6.RELEASE:compile
[WARNING] Unused declared dependencies found:
[WARNING]    jstl:jstl:jar:1.2:compile
[WARNING]    commons-fileupload:commons-fileupload:jar:1.2.2:compile
[WARNING]    mysql:mysql-connector-java:jar:5.1.18:compile
[WARNING]    org.springframework:spring-context-support:jar:3.0.6.RELEASE:compile
[WARNING]    org.springframework:spring-tx:jar:3.0.6.RELEASE:compile
[WARNING]    org.springframework:spring-jdbc:jar:3.0.6.RELEASE:compile
[WARNING]    org.springframework:spring-orm:jar:3.0.6.RELEASE:compile
[WARNING]    org.springframework:spring-aspects:jar:3.0.6.RELEASE:compile
[WARNING]    org.springframework:spring-aop:jar:3.0.6.RELEASE:compile
[WARNING]    org.quartz-scheduler:quartz:jar:1.8.6:compile
[WARNING]    org.slf4j:slf4j-log4j12:jar:1.6.1:compile
[WARNING]    org.tuckey:urlrewritefilter:jar:3.2.0:compile
[WARNING]    org.bouncycastle:bcprov-jdk15on:jar:1.54:compile
[WARNING]    org.apache.commons:commons-crypto:jar:1.0.0:compile
[INFO] ------------------------------------------------------------------------

    该结果中有两个部分:

Used undeclared dependencies found:指项目中使用到的,但是没有显式声明的依赖。这种依赖意味着潜在的风险,当前项目直接使用它们,例如通过import声明,而这种依赖是通过直接依赖传递进来,当升级直接依赖的时候,相关传递依赖的版本也可能发生变化,这种变化不易察觉,但是有可能导致当前项目出错。

Unused declared dependencies found:项目中未使用的,但显式声明的依赖,对于这样的依赖,不应该简单地直接删除其声明,而应仔细分析,由于dependency:analyze只会分析编译代码和测试代码需要用到的依赖,一些执行测试和运行时需要的依赖它就发现不了了。



上一篇:3. Maven使用入门 27
下一篇:6. 仓库 75