(V)Maven依赖

作者: SonyaBaby | 来源:发表于2018-06-11 17:29 被阅读0次

    依赖详解

    <project>
    ...
     <dependencies>
       <dependency>
         <groupId></groupId>
         <artifactId></artifactId>
         <version></version>
         <type></type>
         <scope></scope>
         <optional></optional>
         <exclusions>
             <exclusion><exclusion>
             ...
         </exclusions>
       </dependency>
       ...
     <dependencies>
    </project>
    

    groupId、artifactId、version依赖的基本坐标,对任何一个依赖来说坐标是最重要的,Maven根据坐标才能找到所需要的依赖


    type是依赖的类型,对应项目坐标定义的packaging。大部分情况下该元素不必声明,默认为jar


    scope依赖范围:
    Maven在编译项目主代码的时候要用一套编译classpath,这个时候域范围为compile;在执行测试的时候会使用另外一套测试classpath,域范围为test;实际运行Maven项目的时候,又会使用一套运行classpath

    • compile:编译依赖范围。不指定默认使用该依赖范围,对编译、测试、运行三种classpath都有效。典型例子就是spring-core,在编译、测试、运行时都需要用到。
    • test :测试依赖范围。只对测试classpath有效。在编译主项目代码和运行项目时无法使用该类依赖。典型例子就是JUnit,只在编译测试代码和运行测试的时候才需要。
    • provided:以提供依赖范围。对于编译和测试classpath有效。典型例子:servlet-api,编译和测试项目时需要引入该依赖,运行项目时,容器已经提供,就不需要Maven重复引入一遍
    • runtime:运行时依赖。对于测试和运行classpath有效。典型例子:JDBC驱动实现,项目主代码的编译只需要JDK提供的JDBC接口,只有在执行测试或运行项目时才需要实现上述接口的具体JDBC驱动。
    • system:系统依赖范围。和provided依赖范围完全一致。但是使用system依赖范围时必须要通过systemPath元素显式指定依赖文件的路径。此类依赖不是通过Maven仓库解析的,往往与本机系统绑定,可能造成构建的不可移植。systemPath元素可以引用环境变量,eg:
    <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:导入依赖范围。该依赖范围不会对三种classpath产生实际的影响(阅读至8.3.3节回来补充)

    optional依赖是否可选。当前有四个项目的依赖关系A→B(项目A依赖于项目B),B→X(可选),B→Y(可选)。根据传递性依赖定义,若这三个依赖范围都是compile,那么X,Y也是A的compile范围传递性依赖。这里由于XY都是可选依赖,依赖就不会传递。即XY对A不会有任何影响。

    • 为什么需要可选依赖?项目B实现了两个特性,特性一依赖于X,特性二依赖于Y,而且这两个特性是互斥的,用户不会同时使用这两个特性。比如B时一个持久层隔离工具包,支持多种数据库(MySQL,Oracle等),在构建这个工具包时,需要这两种数据库的驱动程序,但在使用这个工具包时,只会依赖一种数据库。


      可选依赖.png
    • 不推荐使用可选依赖。
      使用可选依赖的原因时一个项目实现了多个特性。在面向对象设计中,一个单一职责性原型,即一个类应该只有一项职责,而不是糅合太多的功能,在Maven项目中同样适用。我们看个例子。
    <project>
        <modelVersion>4.0.0</modelVersion>
        <groupId>com.play.myMaven</groupId>
        <artifactId>project-b</artifactId>
        <version>1.0-SNAPSHOT</version>
    
        <dependencies>
            <dependency>
              <groupId>mysql</groupId>
              <artifactId>mysql-connector-java</artifactId>
              <version>5.1.34</version>
              <optional>true</optional>
            </dependency>
            <dependency>
              <groupId>com.oracle</groupId>
              <artifactId>ojdbc14</artifactId>
              <version>10.2.0.1.0</version>
              <optional>true</optional>
            </dependency>
        </dependencies>
    </project>
    

    在这个例子中,它们只会对当前项目B产生影响,当其他项目依赖于B的时候,这两个依赖不会被传递。当项目A依赖于项目B时,如果是基于MySQL数据库,那么在项目A中需要显式的声明mysql-connector-java这一依赖:

    <project>
        <modelVersion>4.0.0</modelVersion>
        <groupId>com.play.myMaven</groupId>
        <artifactId>project-a</artifactId>
        <version>1.0-SNAPSHOT</version>
    
        <dependencies>
            <dependency>
              <groupId>com.play.myMaven</groupId>
              <artifactId>project-b</artifactId>
              <version>1.0-SNAPSHOT</version>
            </dependency>
            <dependency>
              <groupId>mysql</groupId>
              <artifactId>mysql-connector-java</artifactId>
              <version>5.1.34</version>
            </dependency>
        </dependencies>
    </project>
    

    更好的做法是为MySQL/Oracle分别创建一个Maven项目,基于相同的groupId,分配不同的artifactId,在各自的POM中声明对应的JDBC驱动依赖,不使用可选依赖,用户根据需要选择使用对应artifactId。


    exclusions用来排除传递性依赖。传递性依赖会隐式地引入很多依赖。对于一些隐式的不稳定版本依赖和需要替换的传递性依赖,都需要手动排除。例如

    <project>
        <modelVersion>4.0.0</modelVersion>
        <groupId>com.play.myMaven</groupId>
        <artifactId>project-a</artifactId>
        <version>1.0-SNAPSHOT</version>
    
        <dependencies>
            <dependency>
              <groupId>com.play.myMaven</groupId>
              <artifactId>project-b</artifactId>
              <version>1.0-SNAPSHOT</version>
              <exclusions>
                <exclusion>
                    <groupId>com.play.myMaven</groupId>
                    <artifactId>project-c</artifactId>
                </exclusion>
              </exclusions>
            </dependency>
            <dependency>
              <groupId>com.play.myMaven</groupId>
              <artifactId>project-c</artifactId>
              <version>1.10</version>
            </dependency>
        </dependencies>
    </project>
    
    排除依赖.png

    项目A对项目C版本1.10的传递依赖,同时排除了B的传递依赖项目C


    依赖调解
    例如:项目A有这样的依赖关系:A→B→C→X(1,0)、A→D→X(2,0),X是A的传递性依赖,但是两条依赖路径上有两个版本的X,哪个X会被Maven解析?
    Maven依赖调解的第一原则:路径最近者优先。X(2,0)路径为2,X(1,0)路径为3,X(2,0)被解析。
    但如果是:A→B→Y(1,0)、A→C→Y(2,0),Y(1,0)和Y(2,0)依赖长度相同时,怎么解析呢?
    Maven依赖调解的第一原则:第一声明者优先。在POM中依赖声明的顺序决定了谁会被先解析使用。


    归类依赖
    例如很多关于Spring Framework的依赖,为了避免重复书写同一项目的不同模块的版本,同时可以方便查看个构件版本,我们使用Maven属性归类依赖。

    <project>
        <modelVersion>4.0.0</modelVersion>
        <groupId>com.play.myMaven</groupId>
        <artifactId>project-a</artifactId>
        <version>1.0-SNAPSHOT</version>
    
        <properties>
          <springframework.version>5.0.5.RELEASE</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-context</artifactId>
              <version>${springframework.version}</version>
            </dependency>
            <dependency>
              <groupId>org.springframework</groupId>
              <artifactId>spring-context-support</artifactId>
              <version>${springframework.version}</version>
            </dependency>
        </dependencies>
    </project>
    

    通过在properties中定义一个xxx.version子元素,用${xxx.version}来引用这一属性值。


    优化依赖
    对依赖进行优化:去除多余的依赖,显式地声明某些必要依赖。
    Maven可以自动解析所有项目的直接依赖和间接依赖,并根据规则正确判断每个依赖的范围,对于一些依赖冲突,也能进行调解,以确保任何一个构件只有唯一的版本在依赖中存在。在这些之后,最后得到的那些依赖被称为已解析依赖。查看当前项目的已解析依赖,可以运行mvn dependency:list

    [INFO] The following files have been resolved:
    [INFO]    org.springframework:spring-web:jar:4.3.7.RELEASE:compile
    [INFO]    org.springframework.boot:spring-boot-test:jar:1.5.2.RELEASE:test
    [INFO]    com.fasterxml.jackson.core:jackson-annotations:jar:2.8.0:compile
    [INFO]    org.hamcrest:hamcrest-library:jar:1.3:test
    [INFO]    org.mockito:mockito-core:jar:1.10.19:test
    [INFO]    org.springframework:spring-test:jar:4.3.7.RELEASE:test
    [INFO]    org.slf4j:jul-to-slf4j:jar:1.7.24:compile
    [INFO]    org.assertj:assertj-core:jar:2.6.0:test
    [INFO]    org.springframework:spring-expression:jar:4.3.7.RELEASE:compile
    [INFO]    com.icegreen:greenmail:jar:1.3.1b:test
    [INFO]    org.skyscreamer:jsonassert:jar:1.4.0:test
    ...
    [INFO]    org.springframework.boot:spring-boot:jar:1.5.2.RELEASE:compile
    [INFO]    net.minidev:accessors-smart:jar:1.1:test
    [INFO]    org.springframework:spring-context-support:jar:4.3.7.RELEASE:compile
    [INFO]    com.fasterxml:classmate:jar:1.3.3:compile
    [INFO]    ch.qos.logback:logback-classic:jar:1.1.11:compile
    [INFO]
    [INFO] ------------------------------------------------------------------------
    [INFO] BUILD SUCCESS
    [INFO] ------------------------------------------------------------------------
    

    想要看的更清晰,可以以依赖树的方式,看到某个依赖是通过哪条路径引入的。运行:mvn dependency:tree

    [INFO] Scanning for projects...
    [INFO]
    [INFO] ------------------------------------------------------------------------
    [INFO] Building parent 1.0-SNAPSHOT
    [INFO] ------------------------------------------------------------------------
    [INFO]
    [INFO] --- maven-dependency-plugin:2.10:tree (default-cli) @ hello-maven ---
    [INFO] com.play.myMaven:hello-maven:jar:1.0-SNAPSHOT
    [INFO] +- org.springframework:spring-core:jar:5.0.5.RELEASE:compile
    [INFO] |  \- org.springframework:spring-jcl:jar:5.0.5.RELEASE:compile
    [INFO] +- org.springframework:spring-context:jar:5.0.5.RELEASE:compile
    [INFO] |  +- org.springframework:spring-aop:jar:4.3.7.RELEASE:compile
    [INFO] |  +- org.springframework:spring-beans:jar:4.3.7.RELEASE:compile
    [INFO] |  \- org.springframework:spring-expression:jar:4.3.7.RELEASE:compile
    [INFO] +- org.springframework:spring-context-support:jar:5.0.5.RELEASE:compile
    [INFO] +- mysql:mysql-connector-java:jar:5.1.34:compile
    [INFO] +- com.oracle:ojdbc14:jar:10.2.0.1.0:compile
    [INFO] +- junit:junit:jar:4.11:test
    [INFO] |  \- org.hamcrest:hamcrest-core:jar:1.3:test
    [INFO] +- javax.mail:mail:jar:1.4.1:compile
    [INFO] |  \- javax.activation:activation:jar:1.1:compile
    [INFO] +- com.icegreen:greenmail:jar:1.3.1b:test
    [INFO] |  \- org.slf4j:slf4j-api:jar:1.7.24:compile
    ...
    [INFO] ------------------------------------------------------------------------
    [INFO] BUILD SUCCESS
    [INFO] ------------------------------------------------------------------------
    

    除此,我们还可以通过mvn dependency:analyze分析当前项目的依赖

    [INFO] --- maven-dependency-plugin:2.10:analyze (default-cli) @ hello-maven ---
    [WARNING] Used undeclared dependencies found:
    [WARNING]    org.springframework.boot:spring-boot:jar:1.5.2.RELEASE:compile
    [WARNING]    org.springframework:spring-beans:jar:4.3.7.RELEASE:compile
    [WARNING]    org.springframework.boot:spring-boot-autoconfigure:jar:1.5.2.RELEASE:compile
    [WARNING] Unused declared dependencies found:
    [WARNING]    org.springframework.boot:spring-boot-starter-test:jar:1.5.2.RELEASE:test
    [WARNING]    org.springframework.boot:spring-boot-starter-web:jar:1.5.2.RELEASE:compile
    [WARNING]    com.oracle:ojdbc14:jar:10.2.0.1.0:compile
    [WARNING]    mysql:mysql-connector-java:jar:5.1.34:compile
    [WARNING]    org.springframework.boot:spring-boot-configuration-processor:jar:1.5.2.RELEASE:compile
    [WARNING]    org.springframework:spring-core:jar:5.0.5.RELEASE:compile
    [INFO] ------------------------------------------------------------------------
    [INFO] BUILD SUCCESS
    [INFO] ------------------------------------------------------------------------
    
    • Used undeclared dependencies found:被用到但未被声明的依赖。这种依赖存在潜在的风险。当升级直接依赖时,相关传递性依赖的版本也可能发生变化,这种变化不易察觉,有可能导致当前项目出错。例如接口的改变,当前项目的相关代码无法编译。因此,我们需要显式声明任何项目中直接用到的依赖
    • Unused declared dependencies found:未被使用的显式声明的依赖。对于这种依赖,我们不能简简单单的删除声明,因为dependency:analyze只会分析编译主代码和测试代码需要用到的依赖,一些执行测试和运行时需要的依赖是发现不了的。这个操作确实可以找到一些没用的依赖,但是需要小心测试

    注:《Maven实战》学习笔记

    相关文章

      网友评论

        本文标题:(V)Maven依赖

        本文链接:https://www.haomeiwen.com/subject/rhyceftx.html