JEP 223: New Version-String Scheme | 新的版本字符串方案
摘要
定义一种版本字符串方案,可以轻松区分主要版本、次要版本和安全更新版本,并将其应用于 JDK。
目标
- 便于人类理解,易于程序解析。
- 符合当前行业实践,特别是语义化版本。
- 可以被现有的打包系统和平台部署机制采用,包括RPM、dpkg、IPS和Java 网络启动协议(JNLP)。
- 消除当前将两种类型的信息编码在版本字符串的一个元素中的做法,即次要发布号和安全级别。这种做法很难解读,并导致跳过许多版本号。
- 提供一个用于版本字符串解析、验证和比较的简单 API。
非目标
- 不改变此 JEP 所针对的版本之前任何发行版使用的版本字符串格式。
动机
哪个版本包含最新的所有安全修复:JDK 7 Update 55 还是 JDK 7 Update 60?
看起来 JDK 7 Update 60 比 Update 55 迟五个版本,所以它肯定包含更多的安全修复,对吗?
很遗憾,这个结论是错误的:这两个版本都包含完全相同的安全修复。要理解这个答案,首先需要了解 JDK Update 版本的当前编号方案。包含超出安全修复的次要发布为 20 的倍数。基于前一个次要发布的安全发布为奇数,递增 5,如果需要保持奇数更新号则递增 6。要确定一个次要发布是否比以前的发布更安全,最终需要查看发布说明或源代码。
"JDK 7 Update 60"、"1.7.0_60" 和 "JDK 7u60" 之间有什么区别?
它们只是相同版本的不同名称。这些差异使得很难识别和验证等效的发布版本。简单的逐点比较解析标记的序列是不够的;而是需要一个相当复杂的算法。小写字母'u'的使用不是行业标准,也不是语言中立的。
现在是时候采用一个更简单、更直观的版本号方案了。
描述
版本号
版本号 $VNUM
是由连续的元素组成,由句点字符(U+002E)分隔。一个元素可以是零,也可以是没有前导零的无符号整数数字。版本号的最后一个元素不能为零。格式为:
[1-9][0-9]*((\.0)*\.[1-9][0-9]*)*
序列的长度可以是任意的,但是前三个元素被分配了特定的含义,如下所示:
$MAJOR.$MINOR.$SECURITY
$MAJOR
--- 主要版本号,递增表示包含 Java SE 平台规范新版本中指定的重大新功能的主要发行版,例如 Java SE 8 的 JSR 337。在主要发布中可能删除功能,但至少提前一个主要发布提供预先通知,并且可以进行不兼容的更改。JDK 8 的$MAJOR
版本号为8
,JDK 9 的$MAJOR
版本号为9
。当递增$MAJOR
时,后续所有元素都将被移除。$MINOR
--- 次要版本号,递增表示次要更新发布,可以包含兼容的错误修复、通过相关 Platform 规范的维护发布规定的标准 API 的修订,以及超出该规范范围的实现功能,如新的 JDK 特定 API、额外的服务提供者、新的垃圾收集器和针对新硬件架构的移植。$SECURITY
--- 安全级别,递增表示包含关键修复的安全更新发布,包括改进安全性所必需的修复。当递增$MINOR
时,$SECURITY
不会重置为零。因此,对于给定的$MAJOR
值,较高的$SECURITY
值始终表示更安全的发布,而与$MINOR
的值无关。
版本号的第四个及以后的元素可由 JDK 代码库的下游消费者自由使用。这样的消费者可以使用第四个元素来标识补丁发布,其中包含少量关键的非安全修复,以及对应安全发布中的安全修复。
版本号不包括尾随的零元素;即,如果 $SECURITY
的值为零,则省略 $SECURITY
,如果 $MINOR
和 $SECURITY
的值均为零,则省略 $MINOR
。
版本号序列是按照数字、逐点方式与另一个这样的序列进行比较的;例如,9.9.1
小于 9.10.3
。如果一个序列比另一个序列更短,则认为较短序列的缺失元素小于较长序列的相应元素;例如,9.1.2
小于 9.1.2.1
。
版本字符串
版本字符串 $VSTR
由上述版本号 $VNUM
组成,后面可以选择性地跟有预发布和构建信息,格式如下:
$VNUM(-$PRE)?\+$BUILD(-$OPT)?
$VNUM-$PRE(-$OPT)?
$VNUM(+-$OPT)?
$PRE
匹配([a-zA-Z0-9]+)
--- 预发布标识符。通常用于表示正在积极开发和可能不稳定的早期访问版本的ea
,或者表示内部开发人员构建的internal
。在比较两个版本字符串时,具有预发布标识符的字符串始终小于没有该标识符但具有相等的
$VNUM
的字符串。当预发布标识符仅由数字组成时,它们按数字进行比较;否则按字典顺序比较。数字标识符被认为小于非数字标识符。$BUILD
匹配(0|[1-9][0-9]*)
--- 构建号,每个发布的构建递增一次。当$VNUM
的任何部分递增时,$BUILD
将重置为 1。当比较具有相等的
$VNUM
和$PRE
组成部分的两个版本字符串时,没有$BUILD
组成部分的字符串始终小于具有$BUILD
组成部分的字符串;否则,将按数字比较$BUILD
号码。$OPT
匹配([-a-zA-Z0-9\.]+)
--- 如果需要,可以提供附加构建信息。在内部构建的情况下,这通常包含构建的日期和时间。在比较两个版本字符串时,如果存在
$OPT
的值,则根据所选的比较方法,其重要性可能或可能不重要。
版本号 10-ea
与 $VNUM = "10"
和 $PRE = "ea"
匹配。版本号 10+-ea
与 $VNUM = "10"
和 $OPT = "ea"
匹配。
下表比较了 JDK 9 潜在的版本字符串,使用现有和拟议的格式:
Existing Proposed
Release Type long short long short
------------ -------------------- --------------------
Early Access 1.9.0-ea-b19 9-ea 9-ea+19 9-ea
Major 1.9.0-b100 9 9+100 9
Security #1 1.9.0_5-b20 9u5 9.0.1+20 9.0.1
Security #2 1.9.0_11-b12 9u11 9.0.2+12 9.0.2
Minor #1 1.9.0_20-b62 9u20 9.1.2+62 9.1.2
Security #3 1.9.0_25-b15 9u25 9.1.3+15 9.1.3
Security #4 1.9.0_31-b08 9u31 9.1.4+8 9.1.4
Minor #2 1.9.0_40-b45 9u40 9.2.4+45 9.2.4
为了参考,该表以假设的方式展示了使用新格式的一些 JDK 7 更新和安全发布所使用的版本字符串:
Actual Hypothetical
Release Type long short long short
------------ -------------------- -------------------
Security 2013/04 1.7.0_21-b11 7u21 7.4.10+11 7.4.10
Security 2013/06 1.7.0_25-b15 7u25 7.4.11+15 7.4.11
Minor 2013/09 1.7.0_40-b43 7u40 7.5.11+43 7.5.11
Security 2013/10 1.7.0_45-b18 7u45 7.5.12+18 7.5.12
Security 2014/01 1.7.0_51-b13 7u51 7.5.13+13 7.5.13
Security 2014/04 1.7.0_55-b13 7u55 7.5.14+13 7.5.14
Minor 2014/05 1.7.0_60-b19 7u60 7.6.14+19 7.6.14
Security 2014/07 1.7.0_65-b20 7u65 7.6.15+20 7.6.15
放弃版本号中的初始 1
元素
该建议删除 JDK 版本号中的初始 1
元素。也就是说,建议 JDK 9 的第一个发布将具有版本号 9.0.0
,而不是 1.9.0.0
。
近 20 年来,当前版本号方案的第二个元素已成为 JDK 的事实上的 $MAJOR
版本号。我们在添加重大新功能或进行不兼容更改时递增该元素。
我们可以将当前方案的初始元素视为 $MAJOR
版本号,但是这样 JDK 9 的版本号将为 2.0.0
,即使每个人都已经称之为 “JDK 9”。这对任何人都没有帮助。
如果我们保留初始的 1
,那么 JDK 版本号将继续违反语义化版本控制的原则,新接触 Java 的开发人员将继续对 1.9
和 9
之间的区别感到困惑。
放弃初始的 1
存在一定的风险。有很多比较版本号的方法,有些方法可以正常工作,而有些方法不行。
- 通过解析元素并对其进行数字比较来比较版本号的现有代码将继续有效,因为
9
大于1
;即9.0.0
将被认为比1.8.0
晚。 - 如果当前方案的初始元素具有值
1
,则跳过初始元素的现有代码也将继续工作,因为在新方案中,初始元素将永远不会具有该值。 - 但是,对假设初始元素的值为
1
,因此在比较版本号时始终跳转到第二个元素的现有代码将无法正确工作;例如,此类代码将认为9.0.1
在1.8.0
之前。
个别证据表明,第三类中的现有代码并不常见,但我们欢迎相反的数据。
API
将定义一个简单的 Java API 来解析、验证和比较版本字符串(8072379,8144062):
package java.lang;
import java.util.Optional;
public class Runtime {
public static Version version();
public static class Version
implements Comparable<Version>
{
public static Version parse(String);
public int major();
public int minor();
public int security();
public List<Integer> version();
public Optional<String> pre();
public Optional<Integer> build();
public Optional<String> optional();
public int compareTo(Version o);
public int compareToIgnoreOpt(Version o);
public boolean equals(Object o);
public boolean equalsIgnoreOpt(Object o);
public String toString();
public int hashCode();
}
}
将定义一个等效的 C API,最可能是基于修订后的 jvm_version_info 结构体 。
JDK 中检查和比较 JDK 版本字符串的所有代码将更新为使用这些 API。鼓励那些检查和比较 JDK 版本字符串的开发人员使用这些 API。
系统属性
以下 系统属性 的返回值将受到此 JEP 的修改。一般语法如下:
名称 语法
------------------------------ --------------
java.version $VNUM(\-$PRE)?
java.runtime.version $VSTR
java.vm.version $VSTR
java.specification.version $VNUM
java.vm.specification.version $VNUM
系统属性 java.class.version
不受影响。
下表显示了不同发布类型的现有值和建议值:
系统属性 现有值 建议值
------------------------------- ----------- --------
早期访问
java.version 1.9.0-ea 9-ea
java.runtime.version 1.9.0-ea-b73 9-ea+73
java.vm.version 1.9.0-ea-b73 9-ea+73
java.specification.version 1.9 9
java.vm.specification.version 1.9 9
主要发布 (GA)
java.version 1.9.0 9
java.runtime.version 1.9.0-b100 9+100
java.vm.version 1.9.0-b100 9+100
java.specification.version 1.9 9
java.vm.specification.version 1.9 9
次要发布 (GA)
java.version 1.9.0_20 9.1.2
java.runtime.version 1.9.0_20-b62 9.1.2+62
java.vm.version 1.9.0_20-b62 9.1.2+62
java.specification.version 1.9 9
java.vm.specification.version 1.9 9
安全发布 (GA)
java.version 1.9.0_5 9.0.1
java.runtime.version 1.9.0_5-b20 9.0.1+20
java.vm.version 1.9.0_5-b20 9.0.1+20
java.specification.version 1.9 9
java.vm.specification.version 1.9 9
请注意,所有历史上在这些系统属性中检测到 .
作为版本标识的代码都需要进行检查和可能的修改。例如,对于主要版本,System.getProperty("java.version").indexof('.')
将返回 -1
。
启动器
在 OpenJDK 的 java
启动器实现中,当报告版本信息时会使用系统属性,例如 java -version
、java -fullversion
和 java -showversion
。
启动器输出仍然依赖于以下系统属性:
$ java -version
openjdk version \"${java.version}\"
${java.runtime.name} (build ${java.runtime.version})
${java.vm.name} (build ${java.vm.version}, ${java.vm.info})
$ java -showversion < ... >
openjdk version \"${java.version}\"
${java.runtime.name} (build ${java.runtime.version})
${java.vm.name} (build ${java.vm.version}, ${java.vm.info})
[ ... ]
$ java -fullversion
openjdk full version \"${java.runtime.version}\"
以下是我本地执行的结果:
bashC:\Users\佳佳>java -version openjdk version "17.0.3" 2022-04-19 LTS OpenJDK Runtime Environment Corretto-17.0.3.6.1 (build 17.0.3+6-LTS) OpenJDK 64-Bit Server VM Corretto-17.0.3.6.1 (build 17.0.3+6-LTS, mixed mode, sharing) C:\Users\佳佳>java -fullversion openjdk full version "17.0.3+6-LTS" C:\Users\佳佳>java -showversion openjdk version "17.0.3" 2022-04-19 LTS OpenJDK Runtime Environment Corretto-17.0.3.6.1 (build 17.0.3+6-LTS) OpenJDK 64-Bit Server VM Corretto-17.0.3.6.1 (build 17.0.3+6-LTS, mixed mode, sharing)
有关实现细节,可以在源码中找到。
@since
JavaDoc 标记
@since
JavaDoc 标记的值将继续与系统属性 java.specification.version
保持一致;因此,新的 JDK 9 API 将使用 @since 9
表示。
Mercurial 更改集标签
Mercurial 标签用于标识促销的更改集。工具如 Code Tool 的 jcheck
,用于验证推送到 JDK 发布存储库的所有更改集,将被增强以支持使用新版本方案的标签。
Mercurial 标签的一般语法是 jdk-$VNUM+$BUILD
。下表显示了不同发布类型的建议值:
发布类型 建议值
----------- -----------
主要发布 jdk-9+100
次要发布 jdk-9.1.2+27
安全发布 jdk-9.0.1+3
某些工具可能需要同时支持现有和建议的标签格式。
测试
更改版本字符串的语法和语义将需要对所有组件区域进行广泛测试。与 JDK 版本字符串无关的现有测试应继续通过。