maven快照版本和发布版本


1. 场景

一个大型的软件应用通常包含多个模块,并且通常的场景是多个团队开发同一应用的不同模块。举个例子,设想一个团队开发应用的前端,项目为 app-ui(app- ui.jar:1.0) ,而另一个团队开发应用的后台,使用的项目是 data-service(data-service.jar:1.0)

现在可能出现的情况是开发 data-service 的团队正在进行快节奏的bug修复或者项目改进,并且他们几乎每隔一天就要发布库到远程仓库。

现在如果 data-service 团队每隔一天上传一个新版本,那么将会出现下面的问题:

  • data-service 团队每次发布更新的代码时都要告知 app-ui 团队。
  • app-ui 团队需要经常地更新他们 pom.xml 文件到最新版本。

为了解决这种情况, 快照(SNAPSHOT) 的概念派上了用场。

2. 什么是快照 (SNAPSHOT)

快照 (SNAPSHOT)** 是一种特殊的版本,指定了某个当前的开发进度的副本。不同于常规的版本,Maven每次构建都会在远程仓库中检查新的快照** 。

现在 data-service 团队会每次发布更新代码的快照到仓库中,比如说 data-service:1.0-SNAPSHOT 来替代旧的快照jar包。

注意:每次更新jar包时,版本好不变,且后缀必须带上-SNAPSHOT。

3. 项目快照(Snapshot) VS 版本(Version)

版本(Version) 的情况下,如果Maven以前下载过指定的版本文件,比如说 data-service:1.0Maven 将不会再从仓库下载新的可用的 1.0 文件。若要下载更新的代码, data-service 的版本需要升到 1.1

快照(Snapshot) 的情况下,每次 app-ui 团队构建他们的项目时, Maven 将自动获取最新的快照( data- service:1.0-SNAPSHOT )。

备注: 版本(Version) 存放在Release发布仓库。 快照(Snapshot) 存放在Snapshot快照仓库。

注意: 版本(Version) 的概念,只要不带有 -SNAPSHOT 的关键字时,都会认为这是一个在 Release发布仓库 的jar包。其中在 Release发布仓库 的jar包命名除了具体的版本号之后还可以带上比如:1.0-Release、1.0-rc1等等的字样。

4. 原理详解

Maven 中的仓库分为两种, Snapshot快照仓库Release发布仓库Snapshot快照仓库 用于保存开发过程中的不稳定版本, Release正式仓库 则是用来保存稳定的发行版本。定义一个组件/模块为 快照版本 ,只需要在 pom.xml 文件中在该模块的版本号后加上 -SNAPSHOT 即可(注意这里必须是大写),如下所示:

<dependency>
    <groupId>com.jsoft.test</groupId>
    <artifactId>testcommon</artifactId>
    <version>0.1-SNAPSHOT</version>
    <packaging>jar</packaging>
</dependency>

Maven 会根据模块的版本号( pom.xml 文件中的 version) 中是否带有 -SNAPSHOT 来判断是 快照版本 还是 正式版本 。如果是 快照版本 ,那么在 mvn deploy 时会自动发布到 快照版本库 中,而使用 快照版本 的模块,在 不更改版本号 的情况下,直接编译打包时, Maven自动从镜像服务器上下载最新的快照版本 。如果是 正式发布版本 ,那么在 mvn deploy 时会自动发布到 正式版本库 中,而使用 正式版本 的模块,在 不更改版本号 的情况下,编译打包时如果本地已经存在该版本的模块则 不会主动去镜像服务器上下载

所以,我们在开发阶段,可以将公用库的版本设置为快照版本,而被依赖组件则引用快照版本进行开发,在公用库的快照版本更新后,我们也不需要修改 pom.xml 文件提示版本号来下载新的版本,直接 Maven 执行相关编译、打包命令即可重新下载最新的快照库了,从而也方便了我们进行开发。

虽然, 快照 的情况下, Maven 在日常工作中会自动获取最新的快照,你也可以在任何 Maven 命令中使用 -U参数 强制 Maven 下载最新的快照构建。命令如下:

mvn clean package -U

5. SNAPSHOT之殇

SNAPSHOT版本的依赖因为存在变更传达的透明性的优势而被赏识,甚至被“溺爱”,有很多团队索性直接使用SNAPSHOT到生产环境中,这样对于变更直接生效,很方便。但是作为技术人员的我们其实应该很严谨地看待变更传达的透明性,变更就意味着风险,透明性更是把风险彻底隐藏了起来,生产环境中存在这样的现象更是心惊胆战。例如:A-->B.1.0.3-SNAPSHOT,B对一个A使用的功能实现进行了调整,直接发布到仓库,A重新构建或许就会失败,更糟糕的是构建成功,运行时异常。这个时候A甚至完全没有代码变更就突然失败了,会带来更多的困惑。

这也是maven经常遭人诟病的一个因素,对于同一份代码,构建结果却不具备确定性,让很多人沮丧。当然这个不完全是因为依赖的问题,也有maven插件的问题,maven之前的版本寻找插件策略的方式也存在不确定性,maven在版本2的时候,会去寻找最新的插件版本(如果没配置的话)来执行构建,经常会找到SNAPSHOT版本的插件,所以依赖了一个不稳定的插件来执行构建,不确定性就大大增加。不过maven在3版本就改变了这个策略,会寻找最新稳定版的插件来执行构建,使得构建具备了确定性,稳定性也好多了。说明maven本身也在SNAPSHOT的问题上狠狠摔了一跤。

归根到底,这些问题的根源就是SNAPSHOT是变化的,是不稳定的,而应用(软件)依赖于变化并且不稳定的SNAPSHOT的依赖会导致自身也在变化和不稳定中,这是稳定性的一个大忌,依赖不稳定的服务或者依赖,上述的maven2的问题就是一个典型反例。

6. 如何选择

那么什么时候使用SNAPSHOT?什么时候使用RELEASE?这个可以从他们各自的特性上来看,SNAPSHOT版本的库是一直在变化的,或者说随时都会变化的,这样虽然可以获取到最新的特性,但是也存在不稳定因素,依赖一个不稳定的模块或者库会让模块自身也变得不稳定,尤其是自身对被依赖模块的变化超出掌控的情况。即使可以掌控被依赖模块的变化,也会带来不稳定的因素,因为每次变更都有引入bug的可能性。如果这么说,那么我们是不是要摒弃SANPSHOT了呢?答案肯定是否定的。

想象下,什么情况下,模块会一直变化或者变化比较剧烈?开发新特性的时候,所以对于团队之间协同开发的时候,模块之间出现依赖,变化会非常剧烈,如模块A依赖模块B,模块A必然需要最方便地获取模块B的特性,在开发期间,方便性比稳定性更重要。可以反证下,假设模块B使用RELEASE版本1.0.0,模块A依赖1.0.0,现在模块A出现了bug,需要修复下,那么A就要提供一个版本号1.0.1,这样所有依赖A模块都需要更新版本号,因为开发期间这种事情是如此多,所以会带来巨变。反观SNAPSHOT方案,如果模块B的版本是1.0.0-SNAPSHOT,模块A完全不需要修改版本号即可获取模块B的新特性。当开发进入预发布阶段,为了生产环境的稳定性,依赖应该是RELEASE版本,因为此时SNAPSHOT版本的模块自动获取新特性的特点恰恰会造成生产环境的不稳定性,生产环境上,稳定性重于一切。

7. 魔幻之手

现在已经很明确了,在开发期间,活跃模块的版本号使用SNAPSHOT,在生产期间,依赖RELEASE版本模块。貌似,我们找到了银弹,不过这个只是理想状态,即所有的模块的版本都在自己的掌控或者间接掌控下,只有这样你才能影响对应模块的版本号。往往是理想很丰满,现实却很骨感,如果你依赖的一个模块只有SNAPSHOT版本,并且该模块也很活跃,最无助的是模块的维护人不理会你的请求,那么是否就没辙了,只能把应用构建在不稳定模块上呢?介绍一款maven插件——versions,这是一个非常强大的版本管理插件,其中有个对依赖版本加锁的特性——lock- snapshots,并且提供了参数可以控制锁定的依赖,就可以实现对特定的SNAPSHOT模块锁定版本,执行的命令如下:mvn versions:lock- snapshots -DincludesList="groupId:artifactId:type:classifier:version",执行这个命令之后,对应的版本号会变化,比如1.0.0-SNAPSHOT会变成1.0.0.20090327.172306-4,即完成了锁定,此时这个SNAPSHOT就变成了固定小版本的稳定版本,不会在变化了,也相当于正式版的功能了。当然以后也可以解锁,详细请看对应文档。


原文链接:https://blog.csdn.net/pange1991/article/details/86360229?depth_1-utm_source=distribute.pc_relevant.none-task-blog-BlogCommendFromBaidu-1&utm_source=distribute.pc_relevant.none-task-blog-BlogCommendFromBaidu-1