MAVEN 坐标与依赖

1,032 阅读7分钟

一、前言

我们知道,maven通过依赖的坐标来查找依赖,并通过一定的规则来自动管理依赖。本篇文章将通过对坐标和依赖的讲解,来告诉我们maven管理依赖的原理与过程,并介绍如何通过一些工具来实现依赖的优化。

二、坐标

我们通常会通过下面的方式来引入一个依赖

<dependency>
    <groupId>xxx.xx.xx</groupId>
    <artifactId>xx</artifactId>
	<version>1.1</artifactId>
</dependency>

groupId,artifactId,version就是一个依赖的坐标,但它只是maven查找依赖所需要必要坐标,其实坐标还包括classfier和extension。下面我们来详细介绍一下每一个坐标元素的含义:

  • groupId:即组Id,它代表一个组织结果,命名类似于域名或java中package 的命令方式
  • artifactId:它的定位是组织内一个项目的名称,groupId+artifactId保证了项目的唯一性
  • version:version即一个项目的迭代版本,更加详细版本介绍可以参考:https://juejin.cn/post/6844903513487753230
  • classfier:classfier并不常见,它代表了一个项目的构件。通常我们只会将代码打包成一个jar或war等,比如xx-xxx-1.1.jar,这只是项目默认的主构件;我们还可以同时打包xx-xxx-1.1-sources.jar, xx-xxx-1.1-javadoc.jar, sources 和 javadoc就是classfier,它代表项目的副构件。如何集成打包参考:http://maven.apache.org/plugins/maven-assembly-plugin/
  • extension/packaging:即扩展名,也就是我们的打包方式。默认情况下会打包成jar,也可以打包成war和pom 等 上面五种元素确定了依赖的唯一地址,除groupId外决定了包的名称,(如何查找依赖将在接下文章中给出) 包名规则: artifactId-version-classfier.extension

三、依赖调解

maven依赖调解有两条非常明确的规则: 路径最短优先 优先定义优先 举个例子如下:

root
+--a:1
   +--c:1
+--b:1
   +--c:2
      +--d:1
   +--a:2

pathLength(a:1)=1,pathLength(a:2)=2,因此a:2就会被自动exclude掉,maven不会将该版本的jar包下载至本地。 c:1和c:2的路径长度均为1,但由于c:1定义在前,会被自动引入,c:2就会被exclude掉。 注:这里大家可能会有一些疑问,c:2引入的d:1也会被exclude掉吗?答案是肯定的。这种简单粗暴的的规则,极大简化了maven管理依赖的成本。

四、依赖范围

maven有三类classpath(通过classpath查找加载相应资源),编译期classpath,测试期classpath及运行期classpath。maven 在编译,测试,运行期间所需要的资源是有所不同的,所以在管理maven 依赖时,也会按照对应的scope进行不同行为的管理。

maven有以下几种scope: compile:通常情况下,我们并不会手动设置scope的范围,默认的就是compile,此scope下的依赖对三类classpath均有效。

  • test:test 指仅对测试有效,对编译和运行无效
  • provided: 指在运行时,该依赖不必显示引入,已有容器或其它提供
  • runtime:指运行时引进,对编译期和测试期有效
  • system:类似于provied,一般指在maven 仓库中找不到,但可以由jdk或者是jvm提供,这种类型的dependency引入时,需要指定路径,如下所示:

    <dependency>
      <groupId>sun.jdk</groupId>
      <artifactId>tools</artifactId>
      <version>1.5.0</version>
      <scope>system</scope>
      <systemPath>${java.home}/../lib/tools.jar</systemPath>
    </dependency>
    <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:仅应用于dependencyManagement 中引入pom
classpath(编译) classpath(测试) classpath(运行) 示例
compile Y Y Y spring-core
test Y JUnit
provided Y Y servlet-api
runtime Y Y JDBC
system Y Y

大家看到这里可能会有疑惑,scope如何影响依赖传递。 一个简单的计算方式:scope(上层依赖)&scope(间接依赖),即直接依赖和间接依赖的交集 比如A(runtime)→B(test) 则该B依赖只会对测试期有效

五、 dependencyManagement

dependencyManagement 是maven 2.0.6加入的标签,它的出现的原因在于推动pom文件的良好管理。主要作用可以总结为两个方面:

  • 版本管理:指的是我们可以通过parent pom或者bom实现maven版本的统一管理,无论从公司角度,组织角度及项目的多模块的角度,这都将是一个很好的管理方式。在maven官方文档中有这么一句话:

When referring to artifacts whose poms have transitive dependencies the project will need to specify versions of those artifacts as managed dependencies. Not doing so will result in a build failure since the artifact may not have a version specified. (This should be considered a best practice in any case as it keeps the versions of artifacts from changing from one build to the next)

maven 依赖的一个最佳实践:所有的transive依赖都要显示地声明版本,防止不同编译行为造成的编译结果不同

  • 依赖聚合:指我们将公共依赖用bom进行管理,包括版本及exclusion,项目在引入依赖时,只需要引入顶层bom及声明所需要加载的依赖即可。 这两个方式不但方便了项目或公司对 依赖版本的统一管理,也有利于项目pom的精简化。 说了 dependencyManagement的好处,这里不得不强调一些使用dependencyManagement的一些注意事项(依赖引入规则),它和依赖引入规则和dependency有很大不同,我也将其总结成三个规则:
  • 优先定义规则
  • 显示声明优先
  • bom 优先与parent

<project>
 <modelVersion>4.0.0</modelVersion>
 <groupId>maven</groupId>
 <artifactId>X</artifactId>
 <packaging>pom</packaging>
 <name>X</name>
 <version>1.0</version>
 <dependencyManagement>
   <dependencies>
     <dependency>
       <groupId>test</groupId>
       <artifactId>a</artifactId>
       <version>1.1</version>
     </dependency>
     <dependency>
       <groupId>test</groupId>
       <artifactId>b</artifactId>
       <version>1.0</version>
       <scope>compile</scope>
     </dependency>
   </dependencies>
 </dependencyManagement>
</project>
<project>
 <modelVersion>4.0.0</modelVersion>
 <groupId>maven</groupId>
 <artifactId>Y</artifactId>
 <packaging>pom</packaging>
 <name>Y</name>
 <version>1.0</version>
 <dependencyManagement>
   <dependencies>
     <dependency>
       <groupId>test</groupId>
       <artifactId>a</artifactId>
       <version>1.2</version>
     </dependency>
     <dependency>
       <groupId>test</groupId>
       <artifactId>c</artifactId>
       <version>1.0</version>
       <scope>compile</scope>
     </dependency>
   </dependencies>
 </dependencyManagement>
</project>
<project>
  <modelVersion>4.0.0</modelVersion>
  <groupId>maven</groupId>
  <artifactId>Z</artifactId>
  <packaging>pom</packaging>
  <name>Z</name>
  <version>1.0</version>
  <dependencyManagement>
    <dependencies>
      <dependency>
        <groupId>maven</groupId>
        <artifactId>X</artifactId>
        <version>1.0</version>
        <type>pom</type>
        <scope>import</scope>
      </dependency>
      <dependency>
        <groupId>maven</groupId>
        <artifactId>Y</artifactId>
        <version>1.0</version>
        <type>pom</type>
        <scope>import</scope>
      </dependency>
    </dependencies>
  </dependencyManagement>
</project>

Z同时引入了X与Y,X与Y同时都引入了依赖a,根据优先定义优先,则会引入X中a也就是1.1版本。 需要注意的是,即便X中再引入其它bom,也会优先使用X中的,而不会采用路径长度最短优先的原则。 我们看一下项目使用:


<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.test</groupId>
  <artifactId>test</artifactId>
  <version>1.0.0</version>
  <packaging>jar</packaging>
  
   
    <dependencies>
      <dependency>
        <groupId>maven</groupId>
        <artifactId>X</artifactId>
        <version>1.0</version>
        <type>pom</type>
        <scope>import</scope>
      </dependency>
      <dependency>
        <groupId>maven</groupId>
        <artifactId>Y</artifactId>
        <version>1.0</version>
        <type>pom</type>
        <scope>import</scope>
      </dependency>
 
      <dependency>
       <groupId>test</groupId>
       <artifactId>a</artifactId>
       <version>1.3</version>
     </dependency>
    </dependencies>
</project>

如果在一个项目中显示定义了a,根据显示定义优先,此pom将不会引入X,Y中定义的a,因此,此时的版本应为1.3 应该注意的是,不仅仅会引入bom中版本,还会使用bom中的其它元素,比如exclusion,scope,type