热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

开发笔记:Maven的坐标和依赖

篇首语:本文由编程笔记#小编为大家整理,主要介绍了Maven的坐标和依赖相关的知识,希望对你有一定的参考价值。何为坐标

篇首语:本文由编程笔记#小编为大家整理,主要介绍了Maven的坐标和依赖相关的知识,希望对你有一定的参考价值。



何为坐标

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

关于坐标(Coordinate),大家最熟悉的定义应该来自于立体几何。在一个立体坐标系中,该立体空间内的任何一个点,都能够用坐标(x,y,z)唯一标识。在实际生活中,我们可以将地址看成一种坐标。省市县等一系列信息同样可以唯一标识城市中的任一居住地址,邮局和快递公司正是基于这样一种坐标进行邮件寄送的。

Maven世界中拥有数量非常巨大的构件,如:jar、war等文件。在Maven为这些构件引入坐标概念之前,我们无法使用任何一种方式来唯一标识所有这些构件。因此,当需要用到Spring Framework依赖的时候,大家会去Spring Framework网站寻找,当需要用到log4j依赖的时候,大家又会去Apache网站寻找。又因为各个项目的网站风格迥异,大量的时间花费在了搜索、浏览网页和下载构件。Maven定义了这样的一组规则:世界上任何一个构件都可以使用Maven坐标唯一标识,Maven坐标的元素包括GroupId、artfactId、version、packaging、classifier,现在只要我们提供正确的坐标,Maven就能够帮助我们找到对应的构件。

也许你会奇怪,“Maven是从哪里下载构件呢?”答案很简单,Maven内置了一个中央仓库地址(http://repo1.maven.org/maven2),该中央仓库包含了世界上大部分流行的开源项目构件,Maven会在需要的时候去那里下载。

在我们开发Maven项目的时候,也需要定义适当的坐标,这是Maven强制要求的。在这个基础上,其它的Maven项目才能引用该项目生成的构件。

坐标详解

Maven坐标为各种构件引入了秩序,任何一个构件都必须明确定义自己的坐标,而一组Maven坐标是通过一些元素定义的,它们是GroupId、artfactId、version、packaging、classifier。坐标定义如下:


org.sonatype.nexus
nexus-indexer
2.0.0
jar

  



  • groupId:定义当前Maven项目所属的实际项目。首先,Maven项目和实际项目不一定是一对一的关系。比如SpringFramework这一实际项目,其对应的Maven项目会有很多,如spring-core、spring-context等。这是由于Maven中模块的概念,因此一个实际项目往往会被划分为很多模块。推荐的做法是使用项目隶属的组织或公司的反向域名作为前缀,后跟实际项目名称。如上例:GroupId为org.sonatype.nexus,org.sonatype表示Sonatype公司建立的一个非营利性组织,nexus表示Nexus这一实际项目。

  • artifactId:该元素定义实际项目中的一个Maven项目(模块),推荐的做法是使用实际项目名称作为前缀,比如上例中,artfactId是nexus-indexer,使用了实际项目名nexus作为前缀,这样做的好处是方便寻找实际构件。默认情况下,Maven生成的构件,其文件名会以artfactId作为开头。

  • version:该元素定义Maven项目当前所处的版本,如上例中nexus-indexer的版本是2.0.0。

  • packaging:该元素定义项目的打包方式。首先,打包方式通常于所生成构件的文件扩展名对应,如上例中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文档和源代码。这时候,javadoc和sources就是这两个附属构件的classifier。这样,附属构件也就拥有了自己唯一的坐标。

 

创建Maven项目

现在,我们试着用idea来创建一个Maven项目,选中标有红框的骨架,点击Next

  

然后我们设定项目的groupId和artifactId

  

idea默认会使用自带的Maven,我们要将Maven Home的目录指向我们之前设定好的,另外还要重写配置文件的目录:

  

最后点击Finish,idea会帮助我们构建Maven项目的骨架,我们就不用再去动手生成pom.xml、以及Maven要要求的main和test目录了,这些idea会帮我们设定好。项目构建完毕后,目录结构如下:

删除idea帮我们创建好的App和AppTest文件,然后我们在main\\java\\com\\leolin\\mvnbook和test\\java\\com\\leolin\\mvnbook分别创建Hello.java、HelloTest.java文件。代码如下:

Hello.java


package com.leolin.mvnbook;
public class Hello {
public String sayHello(String name) {
return "Hello " + name;
}
}

  

HelloTest.java 


package com.leolin.mvnbook;
import org.junit.Test;
import static junit.framework.Assert.*;
public class HelloTest {
@Test
public void testHello() {
Hello hello = new Hello();
String result = hello.sayHello("Jack");
assertEquals("Hello Jack", result);
}
}

  

然后,我们在pom.xml文件中添加spring的依赖:



org.springframework
spring-core
5.2.5.RELEASE


  

然后,我们右击项目→Maven→Reimport,便可以看到项目本身新增了Spring依赖:

 

 

 

之后,我们将基于hello项目本身来学习依赖范围。

依赖范围

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

依赖范围就是用来控制依赖与这三种classpath(编译classpath、测试classpath、运行classpath)的关系,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元素显式指定依赖文件的路径。由于此类依赖不是通过Maven仓库解析的,而且往往与本机系统绑定,可能造成构建的不可移植,因此要谨慎使用。systemPath可以引用环境变量,如:



    javax.sql
    jdbc-stdext
    2.0
    system
    ${java.home}/lib/rt.jar



      



  • import:导入依赖范围。该依赖范围不会对三种classpath产生实际的影响。只有在dependencyManagement下才有效果。

传递性依赖

考虑一个基于Spring Framework的项目,如果不使用Maven,那么在项目中就需要手动下载依赖。由于Spring Framework又依赖于其他开源类库,我们又得手动去下载,这是一件非常麻烦的事情。Maven的传递性依赖机制可以很好地解决这一问题,之前我们在hello项目的pom.xml文件中引入了spring-core。现在,让我们再创建一个新项目,并使其依赖hello项目,但我们不再新项目中引入spring-core,看看新项目是否能正常运行。

首先,我们要对原先的hello项目执行mvn clean install,将其放到Maven仓库中。我们创建一个新的hello-friends的Maven项目,并在pom.xml的元素中放入hello依赖:



com.leolin.mvnbook
hello
1.0-SNAPSHOT


  

将hello-friends项目重新Reimport,然后看看hello-friends本身的依赖:

 

 

可以看到在hello-friends的pom.xml文件中,即便我们没有显式地引入spring-core,但Maven依旧帮助我们引入spring-core。有了传递性依赖机制,在使用Spring Framework的时候就不需要考虑它依赖什么了,也不用担心引入多余的依赖。Maven会解析各种直接依赖的POM,将那些必要的间接依赖,以传递性依赖的形式引入到当前项目中。假设A依赖于B,B依赖于C,我们可以说A对于B是第一直接依赖,B对于C是第二直接依赖,A对于C是传递性依赖。

依赖调解

Maven引入的传递性依赖机制,大大简化和方便了依赖声明。但有的时候,传递性依赖也可能会给我们带来麻烦。例如项目A有这样的依赖关系A→B→C→X(1.0)、A→D→X(2.0),X是A的传递性依赖,但两条依赖路径上有两个版本的X,那么哪个版本的X会被Maven引用呢?Maven依赖调解的第一原则是:路径最近者优先。该例中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会使用第二原则,第一声明者优先,在依赖路径长度相等的情况下,在POM中依赖声明的顺序决定了谁会被引用。,如果B的依赖声明在C之前,那么Y(1.0)就会被引用。

可选依赖

假设有这样一个依赖关系,A依赖与B,B依赖于X和Y,B对X和Y的依赖都是可选依赖:A→B、B→X(可选)、B→Y(可选)。由于X和Y都是可选依赖,依赖将不会传递,也就是X和Y不会对A有什么影响。

为什么会有可选依赖呢?可能项目B实现了两种特性,其中一种特性依赖X,另一种依赖于Y,而且这两种特性互斥,用户不能同时使用这两种特性,比如B是一个持久层隔离工具包,它支持多种数据库mysql、PostgrepSQL等,在构建这个工具包的时候,需要这两种数据库的驱动程序,但再使用这个工具包时,只依赖一种数据库。

下面是项目B可选依赖的配置:



4.0
com.leolin.mvnbook
project-b
1.0.0


mysql
mysql-connector-java
5.1.10
true


postgresql
postagresql
8.4-701.jdbc3
true




  

在上述XML代码中,使用元素表示MySQL和PostgrepSQL这两种依赖为可选依赖,它们只会对当前项目B产生影响,当A依赖于B时,如果A使用的是MySQL数据库,那么只需要在A中显示地声明MySQL的依赖,如下:



4.0.0
com.leolin.mvnbook
project-a
1.0.0


com.leolin.mvnbook
project-b
1.0.0


mysql
mysql-connector-java
5.1.10




  

最后,关于可选依赖需要说明的一点是,在理想的情况下,是不应该使用可选依赖的。前面我们可以看到,使用可选依赖的原因是某一个项目实现了多个特性,在面向对象设计中,有个单一职责性原则,意指一个类应该只有一项职责,而不是糅合太多的功能 。这个原则在规划maven项目的时候也同样适用。在上面的例子中,更好的做法是为MySQL和PostgrepSQL分别创建一个maven项目,基于同样的groupId分配不同的artifactId。 

排除依赖

来看一个场景,现在有这样的项目依赖:A→B→C(SNAPSHOT-0.1),但C(SNAPSHOT-0.1)是不稳定版本,存在一些BUG,并影响到项目A的运行,我们想引入C(1.0.0)版本的依赖。可以在pom.xml文件中使用元素exclusion来排除依赖:



4.0.0
com.leolin.maven
project-a
1.0.0
jar


com.leolin.maven
project-b
1.0.0


com.leolin.maven
project-c




com.leolin.maven
project-c
1.1.0




  

代码中使用exclusions元素声明排除依赖,exclusions可以包含一个或者多个exclusion子元素,因此可以排除一个或者多个传递性依赖。需要注意的是,声明exclusion的时候只需要groupId和artifactId,而不需要version元素,这是因为只需要groupId和artifactId就能唯一定位依赖图中的某个依赖。换句话说,maven解析后的依赖中,不可能出现groupId和artifactId项目,但是version不同的两个依赖。

归类依赖

关于Spring Framework的依赖有很多,他们来自同一个项目的不同模块。比如:org.springframework:spring-core:2.5.6、org.springframework:spring-beans:2.5.6、org.springframework:spring-context:2.5.6,它们是来自同一项目的不同模块。可以预见,未来如果要升级Spring Framework,这些依赖版本会一起升级,这意味着我们要修改多处依赖的版本号。为了提高我们的开发效率,Maven为我们提供了常量这一做法:



2.5.6



org.springframework
spring-core
${springframework.version}


org.springframework
spring-beans
${springframework.version}


org.springframework
spring-context
${springframework.version}


org.springframework
spring-context-support
${springframework.version}



  

这里简单使用到了Maven的属性,首先使用properties元素定义了Maven的属性,该例子中定义了一个springframework.version子元素,其值为2.5.6,有了这个属性之后,Maven运行的时候,会将POM中所有${springframework.version}替换成2.5.6。也就是说,可以使用美元符号和大括弧环绕${}的方式来引用Maven的属性,然后将所有Spring Framework依赖的版本值用这一属性引用。



推荐阅读
  • flowable工作流 流程变量_信也科技工作流平台的技术实践
    1背景随着公司业务发展及内部业务流程诉求的增长,目前信息化系统不能够很好满足期望,主要体现如下:目前OA流程引擎无法满足企业特定业务流程需求,且移动端体 ... [详细]
  • Spring源码解密之默认标签的解析方式分析
    本文分析了Spring源码解密中默认标签的解析方式。通过对命名空间的判断,区分默认命名空间和自定义命名空间,并采用不同的解析方式。其中,bean标签的解析最为复杂和重要。 ... [详细]
  • 本文介绍了Java工具类库Hutool,该工具包封装了对文件、流、加密解密、转码、正则、线程、XML等JDK方法的封装,并提供了各种Util工具类。同时,还介绍了Hutool的组件,包括动态代理、布隆过滤、缓存、定时任务等功能。该工具包可以简化Java代码,提高开发效率。 ... [详细]
  • 本文介绍了在SpringBoot中集成thymeleaf前端模版的配置步骤,包括在application.properties配置文件中添加thymeleaf的配置信息,引入thymeleaf的jar包,以及创建PageController并添加index方法。 ... [详细]
  • XML介绍与使用的概述及标签规则
    本文介绍了XML的基本概念和用途,包括XML的可扩展性和标签的自定义特性。同时还详细解释了XML标签的规则,包括标签的尖括号和合法标识符的组成,标签必须成对出现的原则以及特殊标签的使用方法。通过本文的阅读,读者可以对XML的基本知识有一个全面的了解。 ... [详细]
  • 本文介绍了如何使用python从列表中删除所有的零,并将结果以列表形式输出,同时提供了示例格式。 ... [详细]
  • Java在运行已编译完成的类时,是通过java虚拟机来装载和执行的,java虚拟机通过操作系统命令JAVA_HOMEbinjava–option来启 ... [详细]
  • r2dbc配置多数据源
    R2dbc配置多数据源问题根据官网配置r2dbc连接mysql多数据源所遇到的问题pom配置可以参考官网,不过我这样配置会报错我并没有这样配置将以下内容添加到pom.xml文件d ... [详细]
  • 如何实现JDK版本的切换功能,解决开发环境冲突问题
    本文介绍了在开发过程中遇到JDK版本冲突的情况,以及如何通过修改环境变量实现JDK版本的切换功能,解决开发环境冲突的问题。通过合理的切换环境,可以更好地进行项目开发。同时,提醒读者注意不仅限于1.7和1.8版本的转换,还要适应不同项目和个人开发习惯的需求。 ... [详细]
  • Activiti7流程定义开发笔记
    本文介绍了Activiti7流程定义的开发笔记,包括流程定义的概念、使用activiti-explorer和activiti-eclipse-designer进行建模的方式,以及生成流程图的方法。还介绍了流程定义部署的概念和步骤,包括将bpmn和png文件添加部署到activiti数据库中的方法,以及使用ZIP包进行部署的方式。同时还提到了activiti.cfg.xml文件的作用。 ... [详细]
  • 本文介绍了在sqoop1.4.*版本中,如何实现自定义分隔符的方法及步骤。通过修改sqoop生成的java文件,并重新编译,可以满足实际开发中对分隔符的需求。具体步骤包括修改java文件中的一行代码,重新编译所需的hadoop包等。详细步骤和编译方法在本文中都有详细说明。 ... [详细]
  • android listview OnItemClickListener失效原因
    最近在做listview时发现OnItemClickListener失效的问题,经过查找发现是因为button的原因。不仅listitem中存在button会影响OnItemClickListener事件的失效,还会导致单击后listview每个item的背景改变,使得item中的所有有关焦点的事件都失效。本文给出了一个范例来说明这种情况,并提供了解决方法。 ... [详细]
  • 原文地址:https:www.cnblogs.combaoyipSpringBoot_YML.html1.在springboot中,有两种配置文件,一种 ... [详细]
  • 关键词:Golang, Cookie, 跟踪位置, net/http/cookiejar, package main, golang.org/x/net/publicsuffix, io/ioutil, log, net/http, net/http/cookiejar ... [详细]
  • iOS超签签名服务器搭建及其优劣势
    本文介绍了搭建iOS超签签名服务器的原因和优势,包括不掉签、用户可以直接安装不需要信任、体验好等。同时也提到了超签的劣势,即一个证书只能安装100个,成本较高。文章还详细介绍了超签的实现原理,包括用户请求服务器安装mobileconfig文件、服务器调用苹果接口添加udid等步骤。最后,还提到了生成mobileconfig文件和导出AppleWorldwideDeveloperRelationsCertificationAuthority证书的方法。 ... [详细]
author-avatar
mobiledu2502874403
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有