maven依赖

maven依赖

配置maven依赖

先来看一个maven的配置模板

    <dependencies>
        <dependency>
            <groupId></groupId>
            <artifactId></artifactId>
            <version></version>
            <exclusions>
                <exclusion>
                    <artifactId></artifactId>
                    <groupId></groupId>
                </exclusion>
            </exclusions>
            <optional></optional>
            <type></type>
        </dependency>
    </dependencies
  • maven的依赖是放在pom文件根元素下的dependencies中的,
  • groupId,artifactId,version,三个元素是依赖的坐标,maven就是根据这三个元素找到具体的依赖
  • optional为true时代表可选依赖
  • type对应pom定义时的packaging元素,默认是jar 一般无需更改
  • exclusions >exclusion 排除依赖。

如果想看明白上面的各项配置,那么首先需要理解依赖的范围,和依赖的传递,依赖传递性,依赖调解。

依赖的范围

首先需要知道,maven在编译项目主代码的时候需要使用一套classpath。在测试项目代码时需要用到一套classpath最后在实际运行的时候还需要一套classpath,依赖范围就是用来控制依赖与这三种classspath的关系
maven有如下接种依赖范围

  • compile: 编译依赖范围。如果没有指定,就会默认使用该依赖范围。使用此依赖范围的maven依赖,对于编译,测试,运行,三种classpath都有效,典型的例子就是spring-core在编译,测试和运行的时候都需要使用该依赖。
  • test:测试依赖范围。使用此依赖范围的maven依赖对于测试classpath有效,在编译主代码或者运行项目的使用时将无法使用次依赖,典型的例子就是junit。它只有在编译测试代码及运行测试的时候才需要。
  • provided:已提供依赖范围。使用此依赖范围的maven依赖,对于编译和测试classpath有效,但在运行时无效。典型的例子就是servlet-api,编译和测试项目的时候需要该依赖,但在运行的时候,由于容器已经提供,就不需要maven重复引入一遍
  • runtime:运行时依赖范围。使用此依赖范围的maven依赖,对于测试和运行class-path有效,但在编译主代码时无效,典型的例子就是JDBC驱动实现,项目主代码的编译只需要jdk提供的JDBK接口,只有在执行测试或者运行项目的时候才需要实现上述接口的具体JDBC驱动。
  • system:系统以来范围,该依赖范围与三种classpath的关系,和provided依赖范围完全一致,但是system范围的依赖必须通过systemPath元素显示地指定依赖文件的路径。由于此依赖不是通过mavenc仓库解析的,而且往往与本机系统绑定,可能造成构建的不可移植,因此应该谨慎使用systemPath元素可以引用环境变量如
<dependency>
    <groupId></groupId>
    <aritfactId></artifactId>
    <version></version>
    <scope>system</scope>
    <systemPath>${java.home}/lib/rt.jar</systemPath>
</dependency>

各种依赖范围与classpath的关系

依赖范围(scope)对于编译classpath有效对于测试classpath有效对于运行classpath有效例子
compileYYYspring-core
test-Y-junit
providedYY-servlet-api
runtime-YYjdbc驱动实现
systemYY-本地的,maven仓库之外的类库文件

依赖的传递性

在没有构建工具的时代,我们要引入一个jar包的同时,还要引入其依赖的jar包,我们需要做的工作很多,首先要去各大网站上找相关的主jar包,然后再挑选需要的依赖jar包,这个工作相当繁琐。有了依赖管理工具,我们需要做的就一件事情,配置项目所需的主jar包的依赖,然后就可以了。那么这是怎么做到的呢?
这要借助于maven依赖的传递性
加入项目A有一个compile范围的依赖B,B又有一个compile范围的依赖C,那么C就会成为项目A的compile范围的依赖,C是项目A的一个传递依赖。
有了传递依赖的机制,在使用B的时候就不用考虑它依赖了什么,也不用担心引入多余的依赖,maven会解析各个直接依赖的POM,将那些必要的间接依赖,以传递性以来的形式引入到当前的项目中。

传递性依赖和依赖范围

依赖范围不仅可以控制与三种classpath的关系,还对传递性依赖产生影响。假设A依赖于B,B依赖与C,那么我们说A对于B是第一直接依赖,B对于C是第二直接依赖,A对于C是传递性依赖。第一直接依赖的范围和第二直接依赖的范围决定了传递性依赖的范围。看下面的表格

第一直接依赖\第二直接依赖compiletestprovidedruntime
compilecompile--runtime
testtest--test
providedprovided-providedprovided
runtimeruntime--runtime

从表格中我们可以看出,当第二直接依赖是compile范围的时候,传递性依赖和第一直接依赖的范围一样,当第二直接依赖为test时依赖不传递,当第二直接依赖为provided时只有当第一直接依赖为provided是传递性依赖为provided,其它范围不传递,当第二直接依赖为runtime时除了第一直接依赖为compile的情况是传递依赖范围为runtime其它情况传递性依赖和第一直接依赖一样。

依赖的调解

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。那么到底谁会被解析使用呢?在maven2.0.8及以前的版本中,这是不确定的,但是重maven2.9.0开始,为了尽可能避免构建的不确定性,maven定义了依赖调解的第二原则:第一声明优先。在依赖路径长度相等的前提下,载POM中依赖声明的顺序决定了谁会被解析使用,顺序最靠前的那个依赖优胜。在该例中如果B的依赖声明在C之前那么有Y(1.0)就会被解析使用。

可选依赖

假设有这样依赖关系,项目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,而这两个特性又是互斥的。用户不可能同时使用这两个特性。

         <dependency>
            <groupId>com.lm.demo.optional</groupId>
            <artifactId>optoinal-A</artifactId>
            <version>1.0</version>
            <optional>true</optional>
        </dependency><dependency>
            <groupId>com.lm.demo.optional</groupId>
            <artifactId>optoinal-B</artifactId>
            <version>1.0</version>
            <optional>true</optional>
        </dependency>

在上面XML代码片段中,使用optional元素来表示optional-A和optional-B两个依赖为可选依赖,它们支队当前项目产生影响,当其他项目依赖当前项目的时候这两个依赖不会被传递。
最后关旭可选依赖需要说明一点,在理想情况下是不应该使用可选依赖的,我们再前面看到可选依赖的原因是某一个项目实现了多个特性,在面向对象设计中有个单一职责性原则,意指一个类应该只有一项职责,而不是糅合太多功能。这个原则在规划maven项目的时候也同样适用。

最佳实践

  • 排除依赖
  • 归类依赖
  • 优化依赖

排除依赖

传递性依赖会给项目隐式地引入很多依赖,这极大地简化了项目依赖的管理,但有些时候这种特性也会带来问题。例如当前项目有一个第三方依赖,而这个第三方依赖由于某种原因依赖了另外一个类库的SNAPSHOT版本,那么这个SNAPSHOT就会成为当前项目的传递性依赖。而SNAPSHOT的不稳定性会直接影响到当前的项目。这时就需要排除掉该SNAPSHOT,并且在当前项目中声明该类库的某个正式发布版本。还有一些情况,你可能也想要替换某个传递性依赖,比如SunJTA API,Hibernate依赖与这个JAR,但是由于版权音速,该类库不再中央仓库中,而Apache Geronimo项目中有一个对应的实现。这时你就可以排除Sun JAT API,再声明Geronimo的JTA实现

        <dependency>
            <groupId>com.lm.demo.exclusion</groupId>
            <artifactId>exclusion-b</artifactId>
            <version>1.0.0</version>
            <exclusions>
                <exclusion>
                    <groupId>com.lm.edmo.exclusion</groupId>
                    <artifactId>excusion-c</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
         <dependency>
            <groupId>com.lm.edmo.exclusio</groupId>
            <artifactId>exclusion-c</artifactId>
            <version>1.1.0</version>
        </dependency>

上面的代码项目依赖于exclusion-b,但由于一些原因不想引入传递性依赖exclusion-c而是自己显示地声明对于项目exclusion-c 1.1.0版本的依赖,exclusions可以包含一个或者多个exlusion元素因此可以排除一个或者多个传递性依赖。而需要注意的是声明exclusion的时候只需要groupId和artifactId而不需要version原因在请看依赖调解。

归类依赖

归类依赖就是定义一个常量,然后在一类依赖中统一使用该常量,这样可以方便修改,没什么好说的直接上例子吧。

     <properties>
        <springframework.version>2.5.6</springframework.version>
    </properties>

    <dependencies>
        <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>

优化依赖

优化依赖主要用到几个命令
1. mvn dependency:list
2. mvn dependency:tree
3. mvn denpendency:analyse
命令1可以查看引入的依赖列表,包含传递性依赖。
命令2可以查看依赖树,追踪到依赖是由谁引进来的
命令3可以分析依赖的使用情况,例如用了未显式引入的依赖(Used undeclared dependencies),某些显式引入的依赖在项目中没有使用(Unuserd declared dependencies)。

其中需要注意的是Used undeclared dependencies ,这种依赖是通过直接依赖传递进来的,当升级直接依赖的时候先关传递性依赖的版本可能发生变化,这种变化不易察觉,但是有可能导致当前项目出错。例如由于接口的改变,当前项目中的相关代码无法通过编译。这种隐藏的,潜在威胁一旦出现,就往往需要耗费大量的时间来查明真相。因此显示声明任何项目中直接用到的依赖。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值