Maven依赖处理

Maven Dependency Scopes

Maven有6个依赖域。他们分别是Compile,Provided,Runtime,Test,System,Import。这里需要解释一下scope的依赖传递A–>B–>C。当前项目为A,A依赖于B,B依赖于C。知道B在A项目中的scope,那么怎么知道C在A中的scope呢?答案是:
当C是test或者provided时,C直接被丢弃,A不依赖C;
否则A依赖C,C的scope继承于B的scope。

  • Compile

默认就是compile,什么都不配置也就是意味着compile。compile表示被依赖项目需要参与当前项目的编译,当然后续的测试,运行周期也参与其中,是一个比较强的依赖。打包的时候通常需要包含进去。具有传递依赖性,也就是父依赖的scope为compile,本项目中构建的所有流程中都会包括该依赖。

  • Provided

provided意味着打包的时候可以不用包进去,别的设施(Web Container)会提供。事实上该依赖理论上可以参与编译,测试,运行等周期。相当于compile,但是在打包阶段做了exclude的动作。一个典型的例子,web服务器一般在运行时提供了Servlet API。不具有传递依赖性,也就是父依赖的scope为provided,本项目中直接丢掉该依赖。

<dependency>
    <groupId>javax.servlet</groupId>
    <artifactId>servlet-api</artifactId>
    <version>2.5</version>
    <scope>provided</scope>
</dependency>
  • Runtime

runntime表示被依赖项目无需参与项目的编译,不过后期的测试和运行周期需要其参与。与compile相比,跳过编译而已,说实话在终端的项目(非开源,企业内部系统)中,和compile区别不是很大。比较常见的如JSR×××的实现,对应的API jar是compile的,具体实现是runtime的,compile只需要知道接口就足够了。oracle jdbc驱动jar包就是一个很好的例子。另外runntime的依赖通常和optional搭配使用,optional为true。我可以用A实现,也可以用B实现。

# JDBC Driver就是一个很好的例子
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>6.0.6</version>
    <scope>runtime</scope>
</dependency>
  • Test

scope为test表示依赖项目仅仅参与测试相关的工作,包括测试代码的编译,执行。

# JUnit就是一个很好的例子
<dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <version>4.12</version>
    <scope>test</scope>
</dependency>
  • System

从参与度来说,与provided相同,不过被依赖项不会从maven仓库抓,而是从本地文件系统拿,一定需要配合systemPath属性使用。

之前有做个一个图片视频的采集服务,项目是用maven管理的,并用docker部署。需要用到一个外部第三方包。所以我们需要把该本地包使用maven管理起来

<dependency>
            <groupId>thermogroup</groupId>
            <artifactId>infrared</artifactId>
            <version>1.0</version>
            <scope>system</scope>
            <systemPath>${project.basedir}/lib/thermogroupsdk4java.jar</systemPath>
        </dependency>

但是这里有一个问题,因为scope是system,所以使用maven打包的时候是不会把该本地jar包打包进去的。所以在打包docker的时候需要在dockerfile文件中显式的加进去。当时不想这么操作,通过配置maven插件的includeSystemScope参数为true,把本地包也打包进去。

<plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <configuration>
                    <includeSystemScope>true</includeSystemScope>
                </configuration>
            </plugin>
  • Import

scope为import标识这个依赖会被pom文件中的所有声明的依赖替代。Import只能结合dependencyManagement使用,用来做包管理和版本控制。最常见的案例是我们使用springboot,spring cloud的时候,我们使用dependencyManagement做一个大版本管理,后续子项目可以直接通过dependency声明使用的依赖而不需要指定版本(继承dependencyManagement pom文件中声明的依赖版本)。

<dependencyManagement>
        <dependencies>
            <!--支持Spring Boot 2.1.X-->
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-dependencies</artifactId>
                <version>2.1.10.RELEASE</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>Greenwich.RELEASE</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
            <dependency>
                <groupId>com.alibaba.cloud</groupId>
                <artifactId>spring-cloud-alibaba-dependencies</artifactId>
                <version>2.1.0.RELEASE</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>
依赖范围与classpath的关系.png

依赖性传递和依赖范围.png

Maven的Dependency机制

参考文档:https://maven.apache.org/guides/introduction/introduction-to-dependency-mechanism.html

Maven依赖的管理是maven的核心功能,管理单个项目模块通常是简单的,但是对于复杂的项目通常涉及到上百个模块,maven的依赖管理就显得尤其重要。

Transitive Dependencies-依赖的传递性

由于依赖具有传递性,对于一个大一点的项目依赖的拓扑图就会变得越来越大,maven提供了以下几种方式来限制最终选择哪个依赖。

  • Dependency mediation-依赖调节
    当一个项目通过传递性依赖同一个库的不同版本时,会遵循就近原则。比如下面的这个依赖拓扑图,由于D2.0版本的库路径长度(A->B->C->D2.0)大于(A->E->D1.0)的路径长度,所以maven最终会依赖D1.0版本的库。
 A
  ├── B
  │   └── C
  │       └── D 2.0
  └── E
      └── D 1.0

如果你需要显式的依赖D2.0,最佳实践是在自己的项目重声明依赖D2.0,这种它的路径就是(A->D2.0),会优先选择。从maven2.0.9开始,当路径长度一样时,第一声明者优先;通常我们最好显式的声明自己项目中使用的依赖,不要依靠依赖的传递性。

A
  ├── B
  │   └── C
  │       └── D 2.0
  ├── E
  │   └── D 1.0
  │
  └── D 2.0    
  • Dependency management
    参考上述对scope import的解读
  • Dependency scope
    参考上述对scope传递性的解读
  • Excluded dependencies
    显示的排除依赖
<dependency>
      <groupId>group-a</groupId>
      <artifactId>artifact-a</artifactId>
      <version>1.0</version>
      <exclusions>
        <exclusion>
          <groupId>group-c</groupId>
          <artifactId>excluded-artifact</artifactId>
        </exclusion>
      </exclusions>
    </dependency>
  • Optional dependencies
    可选依赖
<!-- declare the dependency to be set as optional -->
    <dependency>
      <groupId>sample.ProjectA</groupId>
      <artifactId>Project-A</artifactId>
      <version>1.0</version>
      <scope>compile</scope>
      <optional>true</optional> <!-- value will be true or false only -->
    </dependency>
可选依赖.png
  • 查看当前项目的已解析依赖
# 平铺
mvn dependency:list
# 层级
mvn dependency:tree
# 依赖分析,Used undeclared dependencies found:项目中使用到了,但是没有声明的依赖;Unused declared dependencies found:未使用到的依赖
mvn dependency:analyze

Maven的Profile的使用

在pom文件中声明profiles,可以设置默认profile.启动的时候以启动的分支为准。

<profiles>
        <profile>
            <id>local</id>
            <properties>
                <env>local</env>
            </properties>
            <activation>
                <activeByDefault>true</activeByDefault>
            </activation>
        </profile>
        <profile>
            <id>test</id>
            <properties>
                <env>test</env>
            </properties>
        </profile>
        <profile>
            <id>prod</id>
            <properties>
                <env>prod</env>
            </properties>
        </profile>
    </profiles>

我们可以在pom文件中通过${env}环境变量可以获取具体哪个分支,比如下面我想让不同的分支打包到harbor不同的镜像仓库,可以使用该环境变量。

 <properties>
        <!--docker插件-->
        <!-- docker私服地址 -->
        <docker.repostory>dev2.out:8082</docker.repostory>
        <!--项目名,需要和Harbor中的项目名称保持一致 -->
        <docker.registry.name>leather.${env}</docker.registry.name>
    </properties>
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容