码迷,mamicode.com
首页 > 其他好文 > 详细

《Maven实战》整理三:坐标和依赖

时间:2015-06-04 18:55:59      阅读:228      评论:0      收藏:0      [点我收藏+]

标签:

1.1 何为Maven坐标

正如之前所说的,Maven的一大功能就是管理项目依赖。为了能自动化地解析任何一个Java构件,Maven就必须将它们唯一标识,这就依赖管理的底层基础——坐标。

1.2 坐标详解

Maven坐标的元素包括:groupId,artifactId、version、packaging、classifier。先看一组坐标定义,如下:

<groupId>org.sonatype.nexus</groupId>
<artifactId>nexus-indexer</artifactId>
<version>2.0.0</version>
<packaging>jar</packaging>

这是nexus-indexer的坐标定义,nexus-indexer是一个对Maven仓库编纂索引并提供搜索功能的类库,它是Nexus项目的一个子模块。下面详细解释一下各个坐标元素:

  • groupId:定义当前Maven项目隶属的实际项目。上例中,groupId为org.sonatype.nexus,org.sonatype表示Sonatype公司创建的一个非营利性组织,nexus表示Nexus这一实际的项目,该groupId与域名nexus.sonatype.org对应,即该groupId表示Sonatype公司的Nexus项目。
  • artifactId:该元素定义实际项目中的一个Maven项目(模块),推荐的做法是使用项目名称作为artifactId的前缀。比如上例中的artifactId是nexus-indexer,使用了实际项目名nexus作为前缀,这样可以方便寻找实际构件。之后使用了indexer表示其所属的具体子项目(模块)。
  • version:该元素定义了Maven项目所处的版本,如上例中的nexus-indexer的版本是2.0.0。
  • packaging:该元素定义Maven项目的打包方式,默认值是jar。打包方式与所生成构件的文件拓展名对应,如上例中的packaging为jar,最终的文件名为nexus-indexer-2.0.0.jar。
  • classifier:该元素用来帮助定义构建输出一些附属构件,让附属构建也拥有唯一的坐标。

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

同时,项目构建的文件名是与坐标相对应的,一般的规则为artifactId-version[-classifier].packaging,其中[-classifier]表示可选。比如上例的nexus-indexer的主构件为nexus-indexer-2.0.0.jar,附属构建有nexus-indexer-2.0.0-javadoc.jar。

1.3 依赖

通过上面的学习,我们配置了一个Maven项目的groupId, artifactId等信息。当我们需要进行开发的时候,我们就需要导入我们这个项目所依赖的一些构建(Jar包)。假设这时候我们需要导入一个用于JUnit测试的依赖,代码如下:

<?xml version="1.0" encoding="UTF-8"?>
<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">

    <groupId>org.sonatype.nexus</groupId>
    <artifactId>nexus-indexer</artifactId>
    <version>2.0.0</version>
    <packaging>jar</packaging>

    <dependencies>
        <!--dependency>
          <groupId>com.chanshuyi.demo</groupId>
          <artifactId>[the artifact id of the block to be mounted]</artifactId>
          <version>1.0-SNAPSHOT</version>
        </dependency-->

        <!-- JUnit -->
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.10</version>
            <scope>test</scope>
        </dependency>
    </dependencies>
</project>

通过上面我们可以看到,导入依赖是在<dependencies>元素下的<dependency>元素进行配置。通过查询我们可以知道JUnit这个依赖的groupId,artifactId都是:junit,并且我们需要的是4.10版本,依赖的范围为test(即测试范围内可用)。其实一个标准的依赖可以包含的元素有:

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

其中:

  • groupId、artifactId和version:这是依赖的基本坐标,Maven通过坐标找到依赖的构件。你可以通过这里查询构建的这3个元素信息。
  • type:依赖的类型,对应于项目坐标定义的packaging。大部分情况下,该元素必须声明,默认值为jar。
  • scope:依赖的范围,下文详解。
  • optional:标记该依赖是否可选,下文详解。
  • exclusions:用来排除传递性依赖,下文详解。

1.4 依赖范围

上面提到,JUnit依赖的测试范围是test,测试范围用元素scope表示。首先需要知道,Maven在编译项目主代码的时候需要使用一套classpath,Maven在编译和执行测试的时候会使用另外一套classpath,实际运行的时候又需要一套classpath。如果在引入依赖的时候指定了scope,那么该依赖只在指定范围内有效。比如上面引入的JUnit的scope为test,则该依赖只在执行测试的时候有效,在运行的时候该依赖是无效的。因此当你尝试在主代码中引入JUnit时,你会发现Junit依赖并不存在。

依赖范围就是用来控制依赖与这三种classpath(编译classpath、测试classpath、运行classpath)的关系,Maven有以下几种依赖范围:

  •  compile:编译依赖范围。如果没有指定,就会默认使用该依赖范围。使用此依赖范围的Maven依赖,对于编译、测试、运行三种classpath都有效。典型的例子是spring-core依赖。
  • test:测试依赖范围。只对测试classpath有效,典型的例子是Junit依赖。
  • provided:已提供依赖范围。对于编译和测试classpath有效,典型的例子是servlet-api依赖。
  • runtime:运行时依赖范围。对于测试和运行有效,典型的例子是JDBC驱动依赖。
  • system:系统依赖范围。该依赖与三种classpath的关系,和provided依赖范围完全一致。但是使用system依赖范围的依赖必须通过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(Maven2.0.9及以上):导入依赖范围。了解即可,将在后面详细说到。

  [可以补充一个依赖与classpath关系的表格 MARK]

1.5 传递性依赖

1.5.1 何为传递性依赖

我有一个Maven项目account-mail,它有一个compile范围的spring-core依赖,spring-core有一个compile范围的commons-logging依赖,那么commons-logging就会成为account-email的compile范围依赖,commons-logging就是account-email的一个传递性依赖,如图:

技术分享

1.5.2 传递性依赖和依赖范围

如果A->B->C,即A对于B的依赖范围是compile,B对于C的依赖范围是compile,那么A对于C的依赖范围是compile。此时,我们称A对于B是第一直接依赖,B对于C是第二直接依赖(根据情况的不同可能会有第三、第四……直接依赖),A对于C是传递性依赖。

那么当A对于B是test范围的依赖,B对于C是compile范围的依赖,那么A对于C的依赖是什么范围呢?下面我们通过一个表格来详细讲解:

技术分享

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

如果A->B->C的传递性依赖中,如果A->B是compile依赖,B->C是runtime依赖,那么A->C就是runtime范围的依赖。

1.6 依赖调解

1.6.1 第一原则:路径最近者优先

例如在项目中可能有这样的依赖关系:A->B->C->X(1.0),A->D->X(2.0),这个时候X是A的传递性依赖,那我们该取那个为A的依赖呢?根据Maven依赖调解第一原则,我们知道应该取X(2.0)为A的依赖。

1.6.2 第二原则:第一声明者优先

当依赖路径长度相等的前提下,在POM中依赖声明的顺序决定了谁会被解析使用。比如这样的依赖关系:A->B->Y(1.0)、A->C->Y(2.0),这时候Y(1.0)和Y(2.0)的路径长度是一样的,都为2。如果B的依赖声明在C之前,那么Y(1.0)就会被解析使用。

1.7 可选依赖

假设有这样一个依赖关系,项目A依赖与项目B,项目B依赖于项目X和Y,B对于X和Y的依赖都是可选依赖:A->B、B->X(可选)、B->Y(可选),那么X、Y就是A的传递性依赖。然而,由于这里X、Y是可选依赖,依赖将不会得以传递。比如B是一个持久层隔离工具包,它支持多种数据库,包括MySQL、PostgreSQL等,在构建这个工具包的时候,需要这两种数据库的驱动程序,但在使用这个工具包的时候,只会依赖一种数据库。这时候MySQL、PostgreSQL这两个依赖对于B就是可选的。此时项目B的依赖声明代码清单如下:

<?xml version="1.0" encoding="UTF-8"?>
<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>1.0.0</modelVersion>
    <groupId>com.juvenxu.mvnbook</groupId>
    <artifactId>project-b</artifactId>
    <version>1.0.0</version>

    <dependencies>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>1.1.10</version>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>postgresql</groupId>
            <artifactId>postgresql</artifactId>
            <version>8.4-701.jdbc3</version>
            <optional>true</optional>
        </dependency>
    </dependencies>
</project>

在理想情况下,是不应该使用可选依赖的。因为在面向对象设计中,有单一职责原则,意指一个类只有一项职责。应用到上面的例子中也就是说在com.juvenxu.mvnbook.project-b项目中不应该实现了两个功能。最好的做法是为MySQL和PostgreSQL分别创建一个Maven项目,基于同样的groupId分配不同的artifactId,如:com.juvenxu.mvnbook:project-b-mysql和com.juvenxu.mvnbook:project-b-postgresql,在com.juvenxu.mvnbook:project-b-mysql实现对MySQL数据的持久化,在com.juvenxu.mvnbook:project-b-postgresql实现对postgresql数据库的持久化。

1.8 排除依赖

有时候会出现这样的情况,你的Hibernate依赖于Sun JTA API,但是因为版权原因,Sun JTA API并不在仓库中。而Apache Geronimo项目有一个对应的实现。这时你就可以排除Sun JAT API,再声明Geronimo的JTA API实现,见代码清单:

<?xml version="1.0" encoding="UTF-8"?>
<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</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>
                    <!-- 排除对project-c的依赖 -->
                    <groupId>com.juvenxu.mvnbook</groupId>
                    <artifactId>project-c</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>com.juvenxu.mvnbook</groupId>
            <artifactId>project-c</artifactId>
            <version>1.0.0</version>
        </dependency>
    </dependencies>
</project>

上述代码中,项目A依赖于项目B,但是由于一些原因,不想引入传递性依赖C,而是自己显示声明对于项目C1.0.0版本的依赖。代码中用exclusions元素可以包含多个exclusion子元素。需要注意的是声明exslusion的时候只需要groupId和artifactId,而不需要version元素。

1.9 归类依赖

当我们使用Spring Framework的依赖时,会有许多依赖,如:spring-core:2.5.6.org、org.springframework:spring-beans:2.5.6等,它们是来自同一项目下的不同模块。它们依赖的版本都是相同时,当需要升级Spring Framework时,这些依赖的版本都会一起升级。这时候我们可以用Maven属性的方式来定义一个名为springframework.version的属性,让所有的spring framework子模块都引用它。

<?xml version="1.0" encoding="UTF-8"?>
<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</groupId>
    <artifactId>project-a</artifactId>
    <version>1.0.0</version>
    
    <properties>
        <springframework.version>3.1.2.RELEASE</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-context</artifactId>
            <version>${springframework.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context-support</artifactId>
            <version>${springframework.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-aop</artifactId>
            <version>${springframework.version}</version>
        </dependency>
    </dependencies>
</project>

这样当我们需要升级Spring Framework的时候就只需要改属性值就可以了。

1.10 优化依赖

使用mvn dependency:list命令可以查看项目已解析的依赖

使用mvn dependency:tree命令可以查看项目构成的依赖树

使用mvn dependency:analyze命令可以分析当前项目的依赖

 

上一篇:《Maven实战》整理二:Maven的使用

下一篇:暂无

 

《Maven实战》整理三:坐标和依赖

标签:

原文地址:http://www.cnblogs.com/chanshuyi/p/4551961.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!