Maven04-坐标和依赖

  • Maven的一大功能就是管理项目依赖
    • 为了能自动化地解析任何一个Java构件,Maven就必须将它们唯一标识,这就依赖管理的底层基础--坐标。
  • 示例:有一个项目mavenproject,该项目有三个不同模块分别是mavenproject-A、mavenproject-B、mavenproject-C,并且模块A依赖模块B,模块B依赖模块C,即项目A --> 项目B --> 项目C。
    • 定义项目A的POM文件:
<?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/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <!--  定义项目坐标  -->
    <groupId>com.mai.example</groupId>
    <artifactId>mavenproject-A</artifactId>
    <version>1.0-SNAPSHOT</version>
    <name>my-maven-project-A</name>
    <!--  定义maven属性  -->
    <properties>
        <heng.ha.version>1.1.0</heng.ha.version>
    </properties>
    <!--  定义依赖列表  -->
    <dependencies>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-core</artifactId>
            <version>2.5.6</version>
        </dependency>
        <!--  定义依赖——项目B 1.1.0  -->
        <dependency>
            <groupId>com.mai.example</groupId>
            <artifactId>mavenproject-B</artifactId>
            <!--  使用maven属性  -->
            <version>${heng.ha.version}</version>
            <exclusions>
                <!--  排除传递性依赖——项目C  -->
                <exclusion>
                    <groupId>com.mai.example</groupId>
                    <artifactId>mavenproject-C</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <!--  显示定义依赖——项目C 1.1.0  -->
        <dependency>
            <groupId>com.mai.example</groupId>
            <artifactId>mavenproject-C</artifactId>
            <!--  使用maven属性  -->
            <version>${heng.ha.version}</version>
        </dependency>
        <!--  显示定义项目B中的可选依赖  -->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.10</version>
        </dependency>
    </dependencies>
</project>
View Code
    • 定义项目B的POM文件:
<?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/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <!--  定义项目坐标  -->
    <groupId>com.mai.example</groupId>
    <artifactId>mavenproject-B</artifactId>
    <version>1.0-SNAPSHOT</version>
    <name>my-maven-project-B</name>
    <!--  定义依赖列表  -->
    <dependencies>
        <!--  定义依赖——项目C 1.0-SNAPSHOT  -->
        <dependency>
            <groupId>com.mai.example</groupId>
            <artifactId>mavenproject-C</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.10</version>
            <!--  标记为可选依赖  -->
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>postgresql</groupId>
            <artifactId>postgresql</artifactId>
            <version>8.4-701.jdbc4</version>
            <!--  标记为可选依赖  -->
            <optional>true</optional>
        </dependency>
    </dependencies>
</project>
View Code
    • 定义项目C的POM文件:
<?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/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <!--  定义项目坐标  -->
    <groupId>com.mai.example</groupId>
    <artifactId>mavenproject-C</artifactId>
    <version>1.0-SNAPSHOT</version>
    <name>my-maven-project-C</name>
</project>
View Code

1、什么是坐标

  • Maven定义了这样一组规则:任何一个构件都可以使用Maven坐标唯一标识,Maven坐标的元素包括groupId、artifactId、version、packaging、classifier。
  • 现在,只要提供正确的坐标元素,Maven就能找到对应的构件。比如说,当需要使用Java5平台上TestNG的5.8版本时,就告诉Maven:"groupId=org.testng;artifactId=testng;version=5.8;classifier=jdk15",Maven就会从仓库中寻找相应的构件。
  • Maven的世界中有数量非常巨大的构件,也就是平时使用的jar、war等文件。在Maven为这些构件引入坐标概念之前,我们无法使用任何一种方式来唯一标识这些构件。因此,当需要用到Spring Framework依赖的时候,会去Spring Framework网站寻找,当需要用到log4j依赖的时候,又会去Apache网站寻找。又因为各个项目的网站风格迥异,大量的时间花费在了搜索、浏览网页等工作上面。
  • Maven内置了一个中央仓库的地址(https://repo1.maven.org/maven2),该中央仓库包含了世界上大部分流行的开源项目构件,Maven会在需要的时候去那里下载。
  • 先看一下nexus-indexer构件的坐标定义:
    • 其坐标分别为groupId:org.sonatype.nexus,artifactId:nexus-indexer,version:2.0.0,packaging:jar,没有classifier。
<!--  nexus-indexer是一个对Maven仓库编纂索引并提供搜索功能的类库,它是Nexus项目的一个子模块。  -->
<groupId>org.sonatype.nexus</groupId>
<artifactId>nexus-indexer</artifactId>
<version>2.0.0</version>
<packaging>jar</packaging>
  • 下面详细解释一下各个坐标元素:
    • groupId(必须):定义当前Maven项目隶属的实际项目。
      • 首先,Maven项目和实际项目不一定是一对一的关系。比如Spring Framework这一实际项目,其对应的Maven项目会有很多,如spring-core、spring-context等。这是由于Maven中模块的概念,因此,一个实际项目往往会被划分成很多模块。
      • 其次,groupId不应该对应项目隶属的组织或公司。原因很简单,一个组织下会有很多实际项目,如果groupId只定义到组织级别,而后面我们会看到,artifactId只能定义Maven项目(模块),那么实际项目这个层将难以定义。
      • 最后,groupId的表示方式与Java包名的表示方式类似,通常与域名反向一一对应。示例中,groupId为org.sonatype.nexus,org.sonatype表示Sonatype公司建立的一个非盈利性组织,nexus表示Nexus这一实际项目,该groupId与域名nexus.sonatype.org对应。
    • artifactId(必须):定义实际项目中的一个Maven项目(模块)。
      • 推荐的做法是使用实际项目名称作为artifactId的前缀。比如示例中的artifactId是nexus-indexer,使用了实际项目名nexus作为前缀,这样做的好处是方便寻找实际构件。
      • 在默认情况下,Maven生成的构件,其文件名会以artifactId作为开头,如nexus-indexer-2.0.0.jar,使用实际项目名称作为前缀之后,就能方便从一个lib文件夹中找到某个项目的一组构件。考虑有5个项目,每个项目都有一个core模块,如果没有前缀,我们会看到很多core-1.2.jar这样的文件,加上实际项目名前缀之后,便能很容易区分foo-core1.2.jar、bar-core-1.2.jar
    • version(必须):定义Maven项目当前所处的版本,如示例中nexus-indexer的版本是2.0.0。
      • 需要注意的是,Maven定义了一套完成的版本规范,以及快照(SNAPSHOT)的概念。
    • packaging(可选):定义Maven项目的打包方式。
      • 首先,打包方式通常与所生成构件的文件扩展名对应,如示例中packaging为jar,最终的文件名为nexus-indexer-2.0.0.jar,而使用war打包方式的Maven项目,最终生成的构件会有一个.war文件,不过这不是绝对的。
      • 其次,打包方式会影响到构建的生命周期,比如jar打包和war打包会使用不同的命令。
      • 最后,当不定义packaging的时候,Maven会使用默认值jar
    • classifier:(可选):用来帮助定义构建输出的一些附属构件。
      • 附属构件与主构件对应,如示例中的主构件是nexus-indexer-2.0.0.jar,该项目可能还会通过使用一些插件生成如nexus-indexer-2.0.0-javadoc.jar、nexus-indexer-2.0.0-sources.jar这样一些附属构件,其包含了Java文档和源代码。这时候,这两个附属构件的classifier就是javadoc和sources。这样,附属构件也就拥有了自己唯一的坐标。还有一个关于classifier的典型例子是TestNG,TestNG的主构件是基于Java 1.4平台的,而它又提供了一个classifier为jdk5的附属构件。
      • 注意,不能直接定义项目的classifier,因为附属构件不是项目直接默认生成的,而是由附加的插件帮助生成。
  • 项目构件的文件名是与坐标相对应的,一般的规则为artifactId-version[-classifier].packaging,[-classifier]表示可选。比如示例nexus-indexer的主构件为nexus-indexer-2.0.0.jar,附属构件有nexus-indexer-2.0.0-javadoc.jar。这里还要强调的一点是,packaging并非一定与构件扩展名对应,比如packaging为maven-plugin的构件扩展名为jar。

2、依赖的配置

  • 一个依赖声明可以包含如下元素:
<project>
    ...

    <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和Idversion:依赖的基本坐标,对于任何一个依赖来说,基本坐标是最重要的,Maven根据坐标才能找到需要的依赖。
    • type:依赖的类型,对应于项目坐标定义的packaging。大部分情况下,该元素不必声明,其默认值为jar。
    • scope:依赖的范围。
    • optional:标记依赖是否可选。
    • exclusions:用来排除传递性依赖。
  • 大部分依赖声明只包含基本坐标,然而在一些特殊情况下其他元素至关重要。

2.1、依赖范围

  • 依赖范围是控制依赖与三种不同的classpath(编译classpath、测试classpath、运行classpath)的关系:
    • 首先,Maven在编译项目主代码的时候需要使用一套classpath,在示例中,编译项目主代码的时候需要用到spring-core,该文件以依赖的方式被引入到classpath中。
    • 其次,Maven在编译和执行测试的时候会使用另外一套classpath。示例中的JUnit就是一个很好的例子,该文件也以依赖的方式引入到测试使用的classpath中,不同的是这里的依赖范围是test。
    • 最后,实际运行Maven项目的时候,又会使用一套classpath,示例中的spring-core需要在该classpath中,而JUnit则不需要。
  • Maven有五种依赖范围
    • compile:编译依赖范围。如果没有指定,就会默认使用该依赖范围。
      • 使用该依赖范围的Maven依赖,对于编译、测试、运行三种classpath都有效。如示例中的spring-core,在编译、测试和运行的时候都需要使用该依赖。
    • test:测试依赖范围。
      • 使用该依赖范围的Maven依赖,只对于测试classpath有效,在编译主代码或者运行项目的使用时将无法使用此类依赖。典型的例子是JUnit,它只有在编译测试代码及运行测试的时候才需要。
    • provided:已提供依赖范围。
      • 使用该依赖范围的Maven依赖,对于编译和测试classpath有效,但在运行时无效。典型的例子是servlet-api,编译和测试项目的时候需要该依赖,但在运行项目的时候,由于容器已经提供,就不需要Maven重复地引入一遍。
    • runtime:运行时依赖范围。
      • 使用该依赖范围的Maven依赖,对于测试和运行classpath有效,但在编译主代码时无效。典型的例子是JDBC驱动实现,项目主代码的编译只需要JDK提供的JDBC接口,只有在执行测试或者运行项目的时候才需要实现上述接口的具体JDBC驱动。
    • system:系统依赖范围。
      • 该依赖与三种classpath的关系,和provided依赖范围完全一致。但是,使用system范围的依赖时必须通过systemPath元素显式地指定依赖文件的路径(systemPath元素可以引用环境变量)。由于此类依赖不是通过Maven仓库解析的,而且往往与本机系统绑定,可能造成构建的不可移植,因此应该谨慎使用
    • import(Maven2.0.9及以上):导入依赖范围。
      • 该依赖范围不会对三种classpath产生实际的影响(和dependencyManagement相关)。
        <dependency>
            <groupId>javax.sql</groupId>
            <artifactId>jdbc-stdext</artifactId>
            <version>2.0</version>
            <scope>system</scope>
            <!--  systemPath元素可以引用环境变量  -->
            <systemPath>${java.home}/lib/rt.jar</systemPath>
        </dependency>
  • 除import外的各种依赖范围与三种classpath的关系如表所示。

2.2、依赖传递

  • 传递性依赖:当引入一个依赖时,该依赖自身也可能依赖其他构件,这时maven会自动引入这些间接依赖。即Maven会自动解析各个直接依赖的POM,将那些必要的间接依赖,以传递性依赖的形式引入到当前的项目中。
  • 依赖范围不仅可以控制依赖与三种classpath的关系,还对传递性依赖产生影响
    • 假设A依赖B,B依赖C,我们可以说A依赖B是第一直接依赖,B依赖C是第二直接依赖,A依赖C是传递性依赖(间接依赖)。
    • 第一直接依赖的范围和第二直接依赖的范围决定了传递性依赖的范围。如表所示,最左边一列表示第一直接依赖范围,最上面一行表示第二直接依赖范围,中间的交叉单元格则表示传递性依赖范围。

  • 仔细观察一下表,可以发现这样的规律:
    • 当第二直接依赖的范围是compile的时候,传递性依赖的范围与第一直接依赖的范围一致。
    • 当第二直接依赖的范围是test的时候,依赖不会得以传递。
    • 当第二直接依赖的范围是provided的时候,只传递第一直接依赖范围也为provided的依赖,且传递性依赖的范围同样为provided。
    • 当第二直接依赖的范围是runtime的时候,传递性依赖的范围与第一直接依赖的范围一致,但compile例外,此时传递性依赖的范围为runtime。

2.3、依赖调解

  • Maven依赖调解(Dependency Mediation)有两个原则:
    • 第一原则是路径最近者优先
    • 第二原则是第一声明者优先(从Maven2.0.9开始)。
  • 项目A有这样的依赖关系:A --> B --> C --> X(1.0),A --> D --> X(2.0)。(使用第一原则)
    • X是A的传递性依赖,但是两条依赖路径上有两个版本的X,那么哪个X会被Maven解析使用呢?两个版本都被解析显然是不对的,因为那会造成依赖重复,因此必须选择一个。该例中X(1.0)的路径长度为3,而X(2.0)的路径长度为2,因此X(2.0)会被解析使用。
  • 依赖调解第一原则不能解决所有问题,比如项目A有这样的依赖关系:A --> B --> Y(1.0),A --> C --> Y(2.0)。(使用第二原则)
    • Y是A的传递性依赖,且Y(1.0)和Y(2.0)的依赖路径长度都为2。那么到底谁会被解析使用呢?在Maven2.0.8及之前的版本中,这是不确定的,但是从Maven2.0.9开始,为了尽可能避免构建的不确定性,Maven定义了依赖调解的第二原则。在依赖路径长度相等的前提下,在POM中依赖声明的顺序决定了谁会被解析使用,顺序最靠前的那个依赖优胜。该例中,如果B的依赖声明在C之前,那么Y(1.0)就会被解析使用。

2.4、可选依赖

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

  • 为什么要使用可选依赖这一特性呢?例如项目B实现了两个特性,其中的特性一依赖X,特性二依赖Y,而且这两个特性是互斥的,用户不可能同时使用两个特性,比如B是一个持久层隔离工具包,它支持多种数据库,包括MySQL、PostgreSQL等,在构建这个工具包的时候,需要这两种数据库的驱动程序,但在使用这个工具包的时候,只会依赖一种数据库。项目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/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    
    <groupId>com.mai.example</groupId>
    <artifactId>mavenproject-B</artifactId>
    <version>1.0-SNAPSHOT</version>
    <name>my-maven-project-B</name>
    
    <dependencies>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.10</version>
            <!--  标记为可选依赖  -->
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>postgresql</groupId>
            <artifactId>postgresql</artifactId>
            <version>8.4-701.jdbc4</version>
            <!--  标记为可选依赖  -->
            <optional>true</optional>
        </dependency>
    </dependencies>
</project>
  • 将依赖标记为可选依赖后,该依赖只会对当前项目B产生影响,当其他项目依赖于B的时候,该依赖不会被传递。因此,当项目A依赖于项目B的时候,如果项目A实际要使用基于MySQL数据库,那么就要在项目A中显式地声明mysql-connector-java这一依赖。
<?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/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.mai.example</groupId>
    <artifactId>mavenproject-A</artifactId>
    <version>1.0-SNAPSHOT</version>
    <name>my-maven-project-A</name>

    <dependencies>
        <!--  定义依赖--项目B 1.1.0  -->
        <dependency>
            <groupId>com.mai.example</groupId>
            <artifactId>mavenproject-B</artifactId>
            <version>1.1.0</version>
        </dependency>
        <!--  显示定义项目B中的可选依赖  -->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.10</version>
        </dependency>
    </dependencies>
</project>
  • 最后,关于可选依赖需要说明的一点是,在理想的情况下,是不应该使用可选依赖的
    • 使用可选依赖的原因是某一个项目实现了多个特性,在面向对象设计中,有个单一职责性原则,意指一个类应该只有一项职责,而不是糅合太多的功能。这个原则在规划Maven项目的时候也同样适用。
    • 推荐做法是为MySQL和PostgreSQL分别创建一个Maven项目,基于同样的groupId分配不同的artifactId,如com.mai.example:mavenprojectB-mysql和com.mai.example:mavenprojectB-postgresql,在各自的POM中声明对应的JDBC驱动依赖,而且不使用可选依赖,用户则根据需要选择使用mavenprojectB-mysql或者mavenprojectB-postgresql。由于传递性依赖的作用,就不用再声明JDBC驱动依赖。

2.5、最佳实践

1、排除依赖

  • 传递性依赖会给项目隐式地引入很多依赖,这极大地简化了项目依赖的管理,但是有些时候这种特性也会带来问题。
    • 例如1,当前项目有一个第三方依赖,而这个第三方依赖由于某些原因依赖了另外一个类库的SNAPSHOT版本,那么这个SNAPSHOT就会成为当前项目的传递性依赖,而SNAPSHOT的不稳定性会直接影响到当前的项目。这时就需要排除掉该SNAPSHOT,并且在当前项目中声明该类库的某个正式发布的版本。
    • 例如2,你可能也想要替换某个传递性依赖,比如Sun JT AAPI,Hibernate依赖于这个jar,但是由于版权的因素,该类库不在中央仓库中,而Apache Geronimo项目有一个对应的实现。这时你就可以排除Sun JAT API,再声明Geronimo的JTA API实现。
  • 项目A依赖项目B,但是由于一些原因,不想引入传递性依赖C,而是显式地声明C 1.1.0版本的依赖。
    • 可以使用exclusions元素声明排除依赖(exclusions可以包含一个或者多个exelusion子元素,因此可以排除一个或者多个传递性依赖)。
    • 需要注意的是,声明exclusion的时候只需要groupId和artifactId,而不需要version元素,这是因为只需要groupId和artifactId就能唯一定位依赖图中的某个依赖。换句话说,Maven解析后的依赖中,不可能出现groupId和artifactId相同,但是version不同的两个依赖。
<?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/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.mai.example</groupId>
    <artifactId>mavenproject-A</artifactId>
    <version>1.0-SNAPSHOT</version>
    <name>my-maven-project-A</name>

    <dependencies>
        <!--  定义依赖--项目B 1.1.0  -->
        <dependency>
            <groupId>com.mai.example</groupId>
            <artifactId>mavenproject-B</artifactId>
            <version>1.1.0</version>
            <exclusions>
                <!--  排除传递性依赖--项目C  -->
                <exclusion>
                    <groupId>com.mai.example</groupId>
                    <artifactId>mavenproject-C</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <!--  显示定义依赖--项目C 1.1.0  -->
        <dependency>
            <groupId>com.mai.example</groupId>
            <artifactId>mavenproject-C</artifactId>
            <version>1.1.0</version>
        </dependency>
    </dependencies>
</project>

2、归类依赖

  • 使用maven属性归类依赖,就将某些变化一致的东西定成一个变量,使其变化一致。
  • 这里简单用到了Maven属性,首先使用properties元素定义Maven属性,该例中定义了一个heng.ha.version子元素,其值为1.1.0。有了这个属性定义之后,Maven运行的时候会将POM中的所有的${heng.ha.version}替换成实际值1.1.0。也就是说,可以使用美元符号和大括弧环绕的方式引用Maven属性。
<?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/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.mai.example</groupId>
    <artifactId>mavenproject-A</artifactId>
    <version>1.0-SNAPSHOT</version>
    <name>my-maven-project-A</name>

    <!--  定义maven属性  -->
    <properties>
        <heng.ha.version>1.1.0</heng.ha.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>com.mai.example</groupId>
            <artifactId>mavenproject-B</artifactId>
            <!--  使用maven属性  -->
            <version>${heng.ha.version}</version>
            <exclusions>
                <exclusion>
                    <groupId>com.mai.example</groupId>
                    <artifactId>mavenproject-C</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>com.mai.example</groupId>
            <artifactId>mavenproject-C</artifactId>
            <!--  使用maven属性  -->
            <version>${heng.ha.version}</version>
        </dependency>
    </dependencies>
</project>

3、优化依赖

  • Maven会自动解析项目的直接依赖和传递性依赖,并且根据规则正确判断每个依赖的范围,对于一些依赖冲突进行调节,以确保任何一个构件只有唯一的版本在依赖中存在。最后得到的依赖被称为已解析依赖(Resolved Dependency)。查看当前项日的已解析依赖
    • mvn dependency:list
  • 在此基础上,还能进一步了解已解析依赖的信息。将直接在当前项目POM声明的依赖定义为顶层依赖,而这些顶层依赖的依赖则定义为第二层依赖,以此类推,有第三、第四层依赖。当这些依赖经Maven解析后,就会构成一个依赖树,通过这棵依赖树就能很清楚地看到某个依赖是通过哪条传递路径引入的。查看当前项目的依赖树
    • mvn dependency:tree
  • mvn dependency:analyze
    • Used undeclared dependencies:指项目中使用到的,但是没有显式声明的依赖。这种依赖意味着潜在的风险,当前项目直接在使用它们,但他们是通过直接依赖传递进来的,当升级直接依赖的时候,相关传递性依赖的版本也可能发生变化,这种变化不易察觉,但是有可能导致当前项目出错。例如由于接口的改变,当前项目中的相关代码无法编译。这种隐藏的、潜在的威胁一旦出现,就往往需要耗费大量的时间来查明真相。因此,显式声明任何项目中直接用到的依赖。
    • Unused declared dependencies:指项目中未使用的,但显式声明的依赖。需要注意的是,对于这样一类依赖不应该简单地直接删除其声明,而是应该仔细分析。由于dependency:analyze只会分析编译主代码和测试代码需要用到的依赖,一些执行测试和运行时需要的依赖它就发现不了。当然,有时候确实能通过该信息找到一些没用的依赖,但一定要小心测试。
#                                                                                                                             #
posted @ 2023-10-02 20:51  麦恒  阅读(13)  评论(0编辑  收藏  举报