Maven教程-坐标和依赖

来源:互联网 发布:mysql 删除重复 编辑:程序博客网 时间:2024/05/29 03:21

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

1、Maven坐标

Maven为了统一管理各种java构件,定义了这样一组规则:世界上任何一个java构件都可以使用Maven坐标去唯一标识它,而一组Maven坐标是通过一些元素定义的,包括:groupId、artifactId、version、packaging、classifier。以spring-jdbc依赖为例,如下:

<dependency>  <groupId>org.springframework</groupId>  <artifactId>spring-jdbc</artifactId>  <version>4.3.2.RELEASE</version></dependency>

下面详细介绍一下各个坐标元素:

  • groupId:定义当前Maven项目隶属的实际项目。Spring
    Framework有多个子模块,例如spring-core、spring-context、spring-beans、spring-test等。

  • artifactId:该元素定义实际项目中的一个Maven项目(子模块),推荐的做法是使用实际项目名称作为artifactId的前缀,这样做的好处是方便寻找实际构件。

  • version:该元素定义Maven项目当前所处的版本,如上例中spring-jdbc的版本为4.3.2.RELEASE。

  • packaging:该元素定义Maven项目的打包方式。打包方式通常与所生成构件的文件扩展名对应。如packaging为jar,最终的文件名为artifactId-version.jar。当不定义packaging时,Maven会使用默认值jar。

  • classifier:该元素用来帮助定义构件输出的一些附属构件。附属构件与主构件对应,如上例中主构件是spring-jdbc-4.3.2.RELEASE.jar,该项目可能还会通过使用一些插件生成如spring-jdbc-4.3.2.RELEASE-javadoc.jar、spring-jdbc-4.3.2.RELEASE-source.jar这样一些附属构件。

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

2、依赖的配置

大型项目开发中,不可避免依赖一些外部模块,比如Spring、SpringMVC、OKHttp、JUnit等等。Maven项目中我们可以在pom.xml文件中声明依赖,如下:

<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/xsd/maven-4.0.0.xsd">  <modelVersion>4.0.0</modelVersion>  <groupId>com.bytebeats</groupId>  <artifactId>NgRpc</artifactId>  <version>1.0-SNAPSHOT</version>  <packaging>jar</packaging>  <name>NgRpc</name>  <url>http://maven.apache.org</url>  <dependencies>    <!-- Netty -->    <dependency>      <groupId>io.netty</groupId>      <artifactId>netty-all</artifactId>      <version>4.1.6.Final</version>    </dependency>    <!--log-->    <dependency>      <groupId>org.slf4j</groupId>      <artifactId>slf4j-api</artifactId>      <version>1.7.21</version>    </dependency>    <dependency>      <groupId>ch.qos.logback</groupId>      <artifactId>logback-core</artifactId>      <version>1.1.7</version>    </dependency>    <dependency>      <groupId>ch.qos.logback</groupId>      <artifactId>logback-classic</artifactId>      <version>1.1.7</version>    </dependency>    <!--test-->    <dependency>      <groupId>junit</groupId>      <artifactId>junit</artifactId>      <version>4.12</version>      <scope>test</scope>    </dependency>  </dependencies></project>

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

  • groupId、artifactId、version:依赖的基本坐标,对于任何一个依赖来说,基本坐标是最重要的,Maven根据坐标才能找到需要的依赖。
  • type:依赖的类型,对应于项目坐标中定义的packaging。大部分情况下,该元素不必声明,其默认值为jar。
  • scope:依赖的范围,上例中JUnit的依赖范围为test。
  • optional:标识依赖是否可选。
  • exclusions:用来排除传递性依赖。

大部分依赖声明只包含基本坐标,然而在一些特殊情况下,其他元素也至关重要。

3、依赖范围

Maven在编译项目主代码的时候需要使用一套classpath。在上例中,编译项目主代码时需要用到logback-core,该文件以依赖的方式被引入到classpath中。其次,Maven在编译和执行测试的时候会使用另外一套classpath。上例中的JUnit就是一个很好的例子,该文件也以依赖的方式引入到测试使用的classpath中,不同的是这里的依赖范围是test。最后,实际运行Maven项目的时候,又会使用一套classpath,上例中的logback-core需要在该classpath中,而JUnit则不需要。

依赖范围就是用来控制依赖与这三种classpath(编译classpath、测试classpath、运行classpath)的关系,Maven有如下几种依赖范围:
compile:编译依赖范围。如果没有指定,就会默认使用该依赖范围。使用此依赖范围的Maven依赖,对于编译、测试、运行三种classpath都有效。典型的例子就是logback-core,在编译、测试、运行时 都需要使用该依赖。

  • test:测试依赖范围。使用此依赖范围的Maven依赖,只对于测试classpath,在编译主代码或者运行项目的时候将无法使用此依赖。典型的例子就是JUnit,它只有在编译测试代码及运行测试用例的时候才需要。

  • provided:已提供依赖范围。使用此依赖范围的Maven依赖,对于测试和测试classpath有效,但在运行时无效。最典型的例子就是servlet-api,编译和测试项目的时候都需要该依赖,但在运行项目时,由于容器已经提供,就不需要Maven重复地引入一遍。

  • runtime:运行时依赖范围。使用此依赖范围的Maven依赖,对于测试和运行classpath有效,但在编译主代码时无效。典型的例子就是JDBC驱动实现,项目主代码的编译只需要JDK提供的JDBC接口,只有在执行测试或者运行项目的时候才需要实现上述接口的具体JDBC驱动。

  • system:系统依赖范围。

  • import:导入依赖范围,仅在Maven2.0.9及以上版本。

4、依赖传递

Maven的传递性依赖机制
假如Maven项目A有一个compile范围的spring-jdbc依赖,而spring-jdbc有一个compile范围的spring-tx,那么spring-tx就会成为项目A的compile范围依赖。spring-tx是项目A的一个传递性依赖。


5、依赖调解

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

例如,项目A有这样的依赖关系:A->B->C->X(1.0)、A->D->X(2.0),X是A的传递性依赖,但是两条依赖路径上有两个版本的X,那么哪个X会被Maven解析使用呢?两个版本都被解析显然是不对的,因为那会造成依赖重复,因此必须选择一个。

Maven依赖调解(Dependency Mediation)的第一原则是:路径最近者优先。该例中X(1.0)的路径长度为3,而X(2.0)的路径为2,因此X(2.0)会被解析使用。


依赖调解第一原则不能解决所有问题,比如这样的依赖关系:A->B->Y(1.0)、A->C->Y(2.0),Y(1.0)和Y(2.0)的依赖路径长度是一样的,都为2。那么到底谁会被解析使用呢?在Maven 2.0.8及之前的版本中,这是不确定的,但是从Maven 2.0.9开始,为了尽可能避免构建的不确定性,Maven定义了依赖调解的第二原则:第一声明者优先。在依赖路径长度相等的前提下,在POM文件中依赖声明的顺序决定了谁会被解析使用,顺序最靠前的那个依赖优先。该例中,如果B的声明在C之前,那么Y(1.0)就会被解析使用。


6、可选依赖

假设有这样一个依赖关系,项目A依赖于项目B,项目B依赖于项目X和Y,B对于X和Y的依赖都是可选依赖:A->B,B->X(可选),B->Y(可选)。根据传递性依赖的定义,如果所有这3个依赖的范围都是compile,那么X、Y就是A的compile范围传递性依赖。然而,由于这里X、Y是可选依赖,依赖将不会得以传递。换句话说X、Y将不会对A有任何影响。

为什么要使用可选依赖这一特性呢?可能项目B实现了两个特性,其中的特性1依赖于X,特性2依赖于Y,而且这两个特性是互斥的,用户不可能同时使用这两个特性。比如B是一个orm框架,它支持多种数据库,包括MySQL、Oracle、PostgreSQL等,在构件这个工具包的时候,需要这两种数据库的驱动程序,但在使用这个工具包的时候,只会依赖一种数据库。


7、最佳实践

在了解了Maven依赖管理的主要功能和原理之后,最重要的是亲自实践一下啦,毕竟实践出真知嘛!

1.排除依赖

传递性依赖会给项目隐式地引入很多依赖,这极大地简化了项目依赖的管理,但是有些时候这种特性也会带来问题。例如,dubbo依赖servlet-api 2.5,而当前项目使用servlet-api 3.0,示例如下:

<project>    <dependencies>      <dependency>        <groupId>com.alibaba</groupId>        <artifactId>dubbo</artifactId>        <version>2.5.3</version>        <exclusions>          <exclusion>            <groupId>javax.servlet</groupId>            <artifactId>servlet-api</artifactId>          </exclusion>          <exclusion>            <groupId>org.slf4j</groupId>            <artifactId>slf4j-api</artifactId>          </exclusion>          <exclusion>            <groupId>log4j</groupId>            <artifactId>log4j</artifactId>          </exclusion>        </exclusions>      </dependency>      <dependency>        <groupId>javax.servlet</groupId>        <artifactId>javax.servlet-api</artifactId>        <version>3.1.0</version>      </dependency>      <!--log-->      <dependency>        <groupId>org.slf4j</groupId>        <artifactId>slf4j-api</artifactId>        <version>1.7.21</version>      </dependency>      <dependency>        <groupId>ch.qos.logback</groupId>        <artifactId>logback-core</artifactId>        <version>1.1.7</version>      </dependency>      <dependency>        <groupId>ch.qos.logback</groupId>        <artifactId>logback-classic</artifactId>        <version>1.1.7</version>      </dependency>      <!--test-->      <dependency>        <groupId>junit</groupId>        <artifactId>junit</artifactId>        <version>4.12</version>        <scope>test</scope>      </dependency>    </dependencies></project>

2.归类依赖

平时开发中,我们经常需要使用Spring Framework的依赖,它们是来自同一项目的不同模块,因此,所有这些依赖的版本号都是相同的,而且可以预见,如果将来需要升级Spring Framework,这些依赖的版本会一起升级。

<project><properties>    <java.version>1.7</java.version>    <springframework.version>4.3.2.RELEASE</springframework.version>    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>  </properties>    <dependencies>        <!-- Spring -->        <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>        <dependency>          <groupId>org.springframework</groupId>          <artifactId>spring-test</artifactId>          <version>${springframework.version}</version>          <scope>test</scope>        </dependency>    </dependencies></project>
0 0
原创粉丝点击