maven系列(四)坐标与依赖

坐标和依赖

坐标详解

Maven坐标为各种构件(artifact)引入了秩序,每一个artifact都必须明确定义自己的坐标,而一组Maven坐标是由以下元素定义的:

    <groupId>com.ssozh.mirana</groupId>
    <artifactId>mirana-parent</artifactId>
    <packaging>pom</packaging>
    <version>1.0.0-SNAPSHOT</version>
  • groupId:定义当前maven项目隶属于的实际项目。
    • maven的项目和实际项目不一定是一对一关系,比如SpringFramework这一实际项目,对应的maven项目会有很多包括spring-core、spring-context等等。
    • groupId不应该对应项目隶属的组织和公司,因为一个组织下面会有很多实际项目,如果groupId只定义到组织级别。那么artifactId会非常难以定义
    • groupId的表示方式与java包名的表示方式类似,通过与域名反向一一对应。上面的com.ssozh是我,mirana是我定义的实际项目。该groupId应该遇mirana.ssozh.com对应。
  • artifactId:该元素定义实际项目中的一个maven项目。
    • 推荐的做法是使用实际项目名称作为artifactId的前缀,比如上面的离职中artifactId是mirana-parent。这样做的好处是方便寻找实际构建。
    • 默认情况下,maven生成的构建,其文件名会议artifactId作为开头,如mirana-parent-1.0.0-SNAPSHOT.jar。这样就可以方便从一个lib文件夹中找到某个项目的一组构建。考虑有4个项目,每个项目都有core模块,如果没有前缀就会看到很多core-1.2.jar这样的文件。
  • version:该元素定义maven项目当前所处的版本,如上例中的版本是快照版本。=>13章会详解。
  • packaging:定义maven项目的打包方式。默认使用jar
  • classifier:该元素用来帮助定义构建输出的一些附属构件。
    • 附属构件与主构件对应,比如javadoc和sources就是一些附属构件,其包含了java文档和源代码。
    • 注意:不能直接定义项目的classifier,因为附属构件倍速项目直接默认生成的,而是有附加的插件帮助完成的。

最后,项目构建的文件名是与坐标相对应的,规则为arifactId-version[-classifier].packaging

项目(略)

【因为】本项目不打算直接完成书中的项目,因此这个地方也是先看一遍,等自己的东西做好了,再过来补充这个部分。计划包括:

  1. OSS:可能不应该这么分,因为OSS无非是一种第三方调用而已。
  2. common:log4j
  3. admin:包含controller层的项目。

项目pom

项目的主代码

项目的测试代码

构建项目

使用mvn clean install构建项目,Maven会根据POM配置自动下载锁所需要的依赖构件,执行编译、测试、打包等工作。最后将项目生成的构件 安装到本地仓库中。这时,该模块就能供其他Maven项目使用了。

依赖的配置

其实一个依赖声明可以包含如下一些元素:

<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元素,以声明一个或者多个项目依赖。每个依赖可以包含的元素有:

  • GAV:坐标。
  • type:依赖的类型,对应于项目坐标定义的packaging。大部分情况下该元素省略,默认jar
  • scope:坐标的范围,见下文。
  • optional:标记依赖是否可选,见下文
  • exclusions:用来排除传递性依赖,见下文。

依赖范围(可感知问题)

JUnit以依赖的范围是test,测试范围用元素scope表示。本节将详细讲解什么是测试范围,以及各种测试范围的效果和用途。

首先需要知道,Maven在编译项目主代码的时候需要使用一套classpath。比如编译项目主代码的时候需要用到spring-core,该文件以依赖的方式被引入到classpath中。
其次,maven在编译和执行测试的时候会使用另外一套classpath。比如JUnit也会议依赖的方式引入到测试使用的classpath中,不同的是这里的依赖范围是test。

最后,实际运行maven项目的时候,又会使用一套classpath,上例中的spring-core需要在该classpath中,而JUnit则不需要。

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

  • compile:【默认】编译依赖范围。使用此依赖范围的maven依赖,对于编译、测试、运行三种classpath都有效。

  • test:测试依赖范围。使用此依赖范围的maven依赖,只对测试classpath有效,在编译主代码或者运行醒目的使用时将无法使用此类依赖。

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

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

  • system:系统以来范围。该依赖于三种classpath的关系,和provided依赖范围完全能一致,但是使用system范围的依赖时必须通过systemPath元素显式地指定依赖文件的路径。由于此类依赖不是通过maven仓库解析的,而且常常与本机系统绑定,可能造成构建的不可移植,另外systemPath元素可以引用环境变量,如:

    <dependency>
    	<GAV></GAV>
        <scope>system</scope>
        <systemPath>${java.home}/lib/rt.jar</systemPath>
    </dependency>
    
  • import:导入依赖范围。该依赖范围不会对三种classpath产生实际的影响,该范围依赖只在dependencyManagement元素下才有效果,使用该范围的依赖通常指向一个pom,作用是将目标pom中的dendencyManagement配置导入并合并到当前POM的dependencyManagement元素中。

    • 注意,上述代码中的依赖的type为pom,import范围依赖由于其特殊性,一般都指向打包类型的pom的模块。如果有多个项目,它们使用的依赖版本都是一致的,则就可以定义一个dependencyManagement专门管理依赖的POM,然后在各个项目中导入这些依赖管理配置。

传递性依赖

何为传递性依赖

【问题】:

考虑一个基于spring框架的项目,如果不使用maven则需要手动下载相关依赖,这么做会引入很多不必要的依赖。

另外一个做法就是懒加载,下载一个只有spring框架的jar包,到实际使用的时候,再根据出错信息,或者查询相关文档,加入需要的其他依赖。

【解决方法】:mavn的传递性依赖机制。

有了传递性依赖机制,在使用spring的时候就不用考虑它依赖了什么,也不用担心引入多余的依赖。maven会解析各个直接依赖的POM,将那些必要的间接依赖,以传递性依赖的形式传递到当前的项目中。

依赖性依赖和依赖范围

假设A依赖于B,B依赖于C,我们说A对于B是第一直接依赖,B对C是第二个直接依赖,A对C就是传递性依赖。第一直接依赖和第二直接依赖的范围决定了传递性依赖的范围。依赖范围影响传递性依赖:

- compile test provided runtime
compile compile - - runtiom
test test - - test
provided provided - provided provided
runtime runtime - - runtime

第一数列是第一依赖,第一横列是第二依赖。中间的表格是传递范围。

很明显可以发现,当第二依赖的范围是compile的时候,传递性依赖的范围与第一直接依赖范围一致;当第二依赖范围是test的时候,依赖不会得以传递。

依赖调解

maven引入的传递性依赖机制,让我们不用考虑这些直接依赖引入什么传递性依赖。但有时候,当传递性依赖造成问题的时候,我们就需要清除地知道该传递性依赖是从哪条依赖路径引入的。

Maven依赖调节(dependency mediation)的第一原则是:路径最近者优先。

Maven定义了依赖调解的第二原则:第一声明者优先。在路径长度一样情况下,在POM中依赖声明的顺序最靠前的那个依赖优胜。例如:

A->B->Y(1.0)

A->C->Y(2.0)

如果B的依赖声明C之前,那么Y(1.0)就会被解析使用。

可选依赖

【场景】

假设有这样一个依赖关系,项目A依赖于项目B,项目B依赖于项目X与项目Y,但是B对于X和Y的依赖都是可选依赖。如果都是compile依赖,那么根据传递性,X和Y对于A就是compile范围的依赖。然而由于X、Y是可选依赖,依赖将不会得以传递。

【为什么会有可选依赖】

存在一个项目B实现了两个特性,其中的特性一依赖于X,特性二依赖于Y,而且这两个依赖是互斥的,用户不能同时使用这两个特性。比如B是一个持久层隔离工具包,他支持多种数据库,如Myql和postgreSQL等,在构建这个工具包的时候,需要这两个数据可的驱动程序,但在使用的时候,只会依赖一种数据库。因为出现了可选依赖

【使用方法】

<project>
	<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.jdbc3</version>
            <optional>true</optional>        
        </dependency>
    </dependencies>
</project>

上述XML中,使用<optional>表示这两个依赖为可选依赖,他们只会对当前项目B产生影响,当其他项目依赖于B的时候,这两个依赖不会被传递。因此当A依赖于B的时候,如果实际使用基于Mysql数据库,那么再项目A中就需要显示地声明mysql-connector-java这一依赖。

在理想情况下,是不应该使用可选依赖的。在面向对象设计中,有一个单一职责性原则。这个规则在maven项目中也同样使用。

最佳实践

排除依赖

传递性依赖会给项目隐式地加入很多依赖,这极大的简化了项目的管理,但是有些时候这种特性也会带来问题。例如,当前项目有一个第三方依赖,而这个第三方依赖由于某些原因依赖了另外一个类库的SNAPSHOT版本,那么这个SNAPSHOT就会成为当前的传递性依赖,而SNAPSHOT的不稳定性会直接影响到当前的项目。这个时候就需要排除掉该SNAPSHOT,并且在当前项目中声明该类库的某个正式发布的版本。具体用法就是用<exclusions>标签就行了。

归类依赖

对于同样是来自于spring framework的不同模块,比如spring-core,spring-beans等等。所有这些依赖的版本都是相同的,而且可以预见,如果将来要升级spring framework,这些依赖的版本会一起升级,对于这种情况应该使用<properties>元素定义maven属性,定义一个springframework.version子元素,其值为2.5.6。然后通过${springframework.version}替换实际值。

优化依赖

【三个命令】

mvn dependency:list : 显示已解析的依赖和每个依赖的范围。

mvn dependency:tree:显示依赖树

mvn dependency:analyze

  1. used undeclared dependencies指项目中使用到的,但没有显式声明的依赖。【当这个依赖升级了,可能导致当前项目出错】
  2. Unused declared dependencies指的是项目中未使用的,但显示声明的依赖【需要注意的是,由于该命令只会分析编译主代码和测试代码需要用到的依赖】

补充

classpath

maven项目分为src目录,resource目录,test/src目录,test/resource目录:

其中src和resource对应到项目的targetclasses目录,如果在src目录调用classpath,则class的根目录为targetclasses

test/src,test/resource对应到test-classes目录,如果在test/src目录调用classpath,则class的根目录为target est-classes

maven的classpath就是java下面的地址,和resources下面的地址。因为最后都会编译/复制到classes中去的。

其实也就是maven中说的主代码所在的位置

这个地方相关的知识点可以查看:

  1. java.util.Properties
  2. org.springframework.util.ResourceUtils
原文地址:https://www.cnblogs.com/SsoZhNO-1/p/15516450.html