关于从先前的长期支持版本(Java 11 和 Java 8)迁移代码,你需要知道的是什么?
整理 | 王晓曼
出品 | CSDN(ID:CSDNnews)
Spring Framework 6 将采用 Java 17 和 Jakarta EE 9
正如昨天在 SpringOne 上宣布的那样,Spring Framework 6 和Spring Boot 3 计划在 2022 年第四季度实现总体可用性的高端基线:
- Java 17+(来自 Spring Framework 5.3.x 线中的 Java 8-17)
- Jakarta EE 9+(来自Spring框架5.3.x 线中的 Java EE 7-8)
这一前瞻性的基线将为我们的 APl 设计和集成工作带来巨大的好处,在未来的许多年里,它将为您的应用程序代码和框架以及您的应用程序带来光明。然而,这当然是有代价的:Spring Framework 6 和 Spring Boot 3 的应用程序在运行时至少需要 JDK 17,以及 Tomcat 10 /Jetty 11(为了兼容 Jakarta EE 9)。更重要的是,在你的应用程序源代码中可能需要一些更改:例如在 jakarta EE 9 中 javax 到 jakarta 命名空间的更改,无论你在哪里接触 Servlet API、JPA、Bean Validation 等。
虽然一开始这听起来有点咄咄逼人,但请记住,我们谈论的是2022年第四季度的发布:到那时,不仅 JDK 17 将取代 JDK 11 成为下一个长期支持版本,而且它本身也将被 JDK 18 和 JDK 19 作为当时可用的特性版本取代,而 JDK 20 的特性已经接近冻结。
Jakarta EB 9 也一样:我们预计届时 Jakarta EE 10 将会推出,而另一代 Tomcat、Jetty 和 co 将作为运行时选项被支持。保持以上基线作为最小值,这允许在 Spring Framework 6.x 中进一步了解 Java 的发展,Java 17 和 Jakarta EE 9 只是开始。
同时,Spring Framework 5.3.x 和 Spring Boot 2.x 目前仍在积极开发中,今年 11 月将推出 Spring Boot 2.6,然后在 2022 年 5 月推出 Spring Boot 2.7,最新一次 Spring Boot 2.x 特性分支已经到达(还没有确定这是 2.7 还是可能的更高版本),该分支将与 Spring Framework 5.3 一起进入扩展的开源维护阶段。与 Spring Framework 6 和 Spring Boot 3 并行,有好几年的重叠。考虑我们之前对 Spring Framework 3.2.x 和4.3.x 的分支,以了解在实践中将如何实现。
因此,如果你打算继续使用 JDK 8 或 11 几年,或者即使你打算继续使用 JDK17上的 Spring Framework 5.3 基础设施几年,请放心,我们对 Spring Framework 5.3.x 和 Spring Boot 2.x 的扩展维护是可以的,会让你覆盖。
一旦你准备使用下一代应用程序的 Java 生态系统,以自己的节奏随时升级到 Spring 框架 6 和 Spring Boot 3,随着 Tomcat、Jetty 和 co——新基础设施的好处和新建筑成为可用的选项。我们希望在接下来的几年里你们会欣赏这些选择。
注:如果你想知道 JDK 11 作为一个 LTS 的生成,请注意,JDK 11 的商业支持时间框架比 JDK 8 短,JDK 11 的 LTS 将在 2023 年底淘汰。JDK 17 作为下一代 LTS 将提供至少到 2026 年的支持时间框架。我们认为 JDK 8 在生态系统中具有独特的作用;
相比之下,JDK 11 只是一个过渡版本。此外,JDK 17 提供了一组最近积累起来的语言、API 和 JVM 增强功能,使其成为一个更有吸引力的升级。同样重要的是,在同一个 Spring Framework 6.x 中,JDK 23 LTS(2024年)和 JDK 29 LTS(2027年)仍然会支持,最终的支持范围会更广 JDK 17-29。
为什么要升级到 Java 17?
Java 17, Java 语言和运行平台的下一个长期支持(LTS)版本,将于 9 月 14 日正式发布。不幸的是,许多应用程序仍然在旧版本的 Java 上运行,比如以前的 LTS 版本:Java 11 和 Java 8。本文解释了为什么应该升级应用程序,并帮助您实际升级到Java 17。
但首先,你们很多人可能会问:“为什么升级?”
为什么会有人想要升级到最新的 Java 版本?特别是如果您的应用程序在 Java 8、Java 11、Java 14 或您正在使用的任何版本上运行得很好时,这是有理由怀疑的。升级到 Java 17 需要付出努力,尤其是对那些目标是真正利用 JVM 中的新语言特性和功能的人。
没错,根据环境和应用程序的不同,可能需要进行一些升级。开发人员和其他团队成员需要更新他们的本地环境。此外,构建环境和运行环境(如生产环境)也需要升级。
幸运的是,许多项目和团队使用 Docker,它在这方面帮助很大。在我自己的团队中,团队定义了他们自己的持续集成/持续部署(CI/CD)管道,他们用 Docker 映像运行一切。团队可以通过在他们的 Docker 映像中指定该版本升级到最新的 Java 版本——这不会影响其他可能在旧 Java 版本上运行的团队,因为这些团队使用的是旧的 Docker 映像。
在 Kubernetes 上运行的测试和生产环境也是如此。当一个团队想要升级到一个新的 Java 版本时,他们可以自己更改 Docker 映像,然后部署所有内容。(当然,如果您仍然有共享的构建环境,或者其他管理您环境的团队,这个过程可能会有点挑战性。)
应用程序也可能需要做一些更改。我注意到,团队发现有这么多的工作量是很有挑战性的,以致将一个应用程序从 Java 8 升级到 Java 11 需要几周甚至几个月的时间。过高的估计往往会导致公司因为其他优先事项而推迟升级。
我试图升级了一个应用程序,估计需要几周时间,但实际只花了几天时间,主要是因为等待构建的完成。这部分是由于多年的升级经验,但这也是一个刚刚起步的问题,并试图在过程中解决问题。这是一个周五下午的好工作内容;看看您已经完成了多少工作,还有哪些挑战,这样就更容易估算剩下的工作。
然而,即使有多年的经验,在没有关于项目深入信息的情况下,我也无法估计升级需要多长时间。这很大程度上取决于应用程序有多少依赖项。通常,将依赖项升级到最新版本可以解决 Java 升级过程中可能出现的许多问题。
LTS 版本
本文一直引用 Java 8、Java 11 和 Java 17 作为 LTS 版本。这是什么意思?下面是对 Oracle Java SE 支持路线图的引用:
对于 Java SE 8 之后的产品版本,Oracle 将每三年指定一个版本作为长期支持的(LTS)版本。Java SE 11 是一个 LTS 版本。出于 Oracle Premier Support 的目的,非 LTS 版本被认为是最新 LTS 版本的累积实现增强集。
一旦一个新的特性版本可用,任何以前的非 LTS 版本都将被认为是可取代的。例如,Java SE 9 是一个非 LTS 版本,并立即被 Java SE 10 (也是非 LTS )所取代,而 Java SE 10 又立即被 Java SE 11 所取代。然而,Java SE 11 是一个 LTS 版本,因此,即使 Java SE 12 已经发布,Oracle 客户也将收到 Oracle Premier 支持和定期更新的版本。
在 Java 升级期间需要更改什么?
你的应用程序包含你和你的团队编写的代码,它可能还包含依赖项。如果从 JDK 中删除了某些内容,可能会破坏代码、依赖关系,或者两者都破坏。这通常有助于确保这些依赖项是最新的,以解决这些问题。有时,在开始升级过程之前,你可能必须等待框架发布与最新 Java 版本兼容的新版本。
这意味着,作为升级前评估过程的一部分,你对依赖项要有很好的了解。
大多数功能不会一下子从 JDK 中全部删除。首先,功能被标记为弃用。例如,用于 XML 绑定的 Java 体系结构(JAXB)在 Java 9 中被标记为弃用,然后在Java 11 中被删除。如果您不断更新,则会看到被弃用的部分,并且可以在功能被删除前解决这些特性的任何使用问题。但是,如果直接从 Java 8 跳到 Java 17,那么这个特性的删除将会立刻给您造成影响。
要查看 API 的变化,例如,查看在特定的 Java 版本中哪些方法被删除或添加到 String APl 中,可以查看 Marc Hoffmann 和 Cay Horstmann 的 Java 版本年鉴,或者 Foojay 的 Java 版本年鉴。
Multirelease JAR 功能
如果你的应用程序被仍然使用旧 JDK 的客户使用,并且站点的升级不在您的控制范围内,该怎么办?在 Java 9 和 JEP 238 中引入的多版本 JAR 功能可能很有用,因为它允许将多个 Java 版本(包括比 Java 9更老的版本)的代码打包到一个 JAR 文件中。
例如,创建一个 Application 类(清单1)和一个 Student 类(清单2),并将它们放在 src/main/java/com/example 文件夹中。Student 类是一个在 Java 8 上运行的类。
清单1:应用程序类
public class Application {
public static void main(String[] args) {Student student = new Student("James ");System.out.println("Implementation " + student.implementation());System.out.println("Student name James contains a blank: " + student.isBlankName());}
}
清单2:为 Java 8 编写的 Student 类:
public class Student {final private String firstName;public Student(String firstName) {this.firstName = firstName; }boolean isBlankName() {return firstName == null || firstName.trim().isEmpty(); }static String implementation() { return "class"; }}
接下来,创建一个 Student 记录(清单3),它不仅使用记录(在 Java 14 中引入),还使用 String.isBlank() 方法(在 Java 11 中引入),并将其放在文件夹 src/main/java17/com/example 中。
清单3:使用较新 Java 特性的 Student 记录
public record Student(String firstName) {boolean isBlankName() {return firstName.isBlank(); }static String implementation() { return "record"; }}
需要一些配置,但这是取决于你使用的构建工具。可以在我的 GitHub 存储库中找到一个 Maven 示例。该示例构建在 Java 17 上,并创建 JAR 文件。当在 JDK 17 或更新版本上执行 JAR 文件时,将使用 Student 记录。在旧版本上执行 JAR 文件时,将使用 Student 类。
这个特性是非常有用的,例如,如果新的 API 提供更好的性能,因为你可以使用那些有最新 Java 版本的客户的 API 。使用旧 JDK 的客户可以使用相同的 JAR 文件,而不需要提高性能。
请注意,在本示例中,所有的实现,即 Student,都应该具有相同的公共 API,以防止出现运行问题。但是,构建工具不验证公共 API,但一些 IDE 可以。此外,在 JDK 17 中,你可以使用 jar -validate 命令来验证 JRA 文件。
需要注意的是,JDK 某些版本中提供的预览功能。一些较大的特性首先以预览的形式发布,可能会在下一个 JDK 中形成最终的特性。这些预览特性在 Java 的 LTS 和非 LTS 版本中都有,并且,这些特性是通过 enable-preview 标志启用的,默认情况下是关闭的。如果你在产品代码中使用这些预览特性,请注意它们可能会在 JDK 版本之间发生变化,这可能会导致需要进行一些调试或重构。
关于 Java 弃用和特性移除的更多信息
在升级 JDK 之前,请确保您的 IDE、构建工具和依赖项是最新的。Maven 版本插件和 Gradle 版本插件会显示你有哪些依赖项,并列出最新的可用版本。
请注意,这些工具只显示您所使用文件的新版本——但有时文件名称会更改,会产生分叉,或者代码会移动。例如,JAXB 首先是通过 jakarta.xml.bind: jaxb-api,但改为 jakarta.xml.bind: jakarta.xml.bindapi 在它过渡到 Eclipse Foundation 之后。要找到这样的变化,你可以使用 Jonathan Lermitage 的 Maven Old Grouplds Alerter 插件或者它的 Gradle 插件。
JavaFX。从 Java 11 开始,平台不再将 JavaFX 作为规范的一部分,大多数 JDK 构建已经删除了它。你可以使用来自 Gluon 的独立 JavaFX 构建,或者将 OpenJFX 依赖项添加到你的项目中。
字体。曾经 JDK 包含一些字体,但是从 Java 11 开始,它们被删除了。例如,如果你使用 Apache POI(用于 Microsoft Office 兼容文档的 Java API)。你需要字体,操作系统需要提供字体,因为它们不再出现在 JDK 中。但是,在诸如 Alpine Linux 这样的操作系统中,字体必须使用 apt install fontconfigcommand 手动安装,根据您使用的字体,可能需要额外的字体包。
Java 任务控制。对于监视和分析应用程序,这是一个非常有用的工具。我强烈建议你调查一下。Java Mission Control 曾经包含在 JDK 中,但现在它可以作为一个单独的下载,并以新的名称提供:JDK Mission Control。
Java EE。JDK 11 中最大的变化是删除了 Java EE 模块。前面提到的 JAXB 等 Java EE 模块被许多应用程序使用。既然这些模块不再存在于 JDK 中,你应该添加相关的依赖项。表 1 列出了各种模块及其依赖关系。
请注意,JAXB 和 JAX-WS 都需要两个依赖项:一个用于 API,另一个用于实现。另一个变化是命名约定,现在 Java EE 由 Eclipse Foundation 以 Jakarta EE 的名称维护。您的包导入需要反映这种变化,例如 jakarta.xml。绑定。应该使用 * 而不是 javax.xml.bind。
表1:Java EE 模块及其当前替换
CORBA。Java 的 CORBA 模块没有正式的替代,它在 Java 11 中被删除了。然而,Oracle GlassFish Server 包含 CORBA 的实现。
Nashorn。Java 15 删除了 Nashorn JavaScript 引擎。如果您仍然想使用引擎,您可以使用 nashorncore 依赖项。
实验的编译器。Java 17 删除了对 GraalVM 实验性的提前(AOT)和即时(JIT)编译器的支持,这在 JEP 410 的文档中有解释。
注意不支持的主文件
您可能会看到错误:Unsupported class file major version 61。我 在JaCoCo 代码覆盖库和各种其他 Maven 插件中看到过它。消息的主要版本 61指的是 Java 17。在这种情况下,这意味着你使用的框架或工具的版本不支持 Java 17。因此,你应该将框架或工具升级到新版本。(如果你看到一条包含主版本 60 的消息,那么它与 Java 16 有关。)
请注意,一些工具,如 Kotlin 和 Gradle 还不支持 Java 17,至少在我写这篇文章的时候(2021年8月中旬)是这样的。
有时可以解决这个问题,例如,将 Java 16 指定为 Kotlin 的 JVM 目标。不过,我希望 Java 17 支持很快就会加入。
封装 JDK 内部 API
Java 16 和 Java 17 封装了 JDK 内部 API,这影响了各种框架,比如 Lombok。你可能会看到类似module jdk.compiler 没有导出 com.sun.tools 这样的错误,这意味着你的应用程序不再能够访问 JDK 的那一部分。
总的来说,我建议升级所有使用这些内部组件的依赖项,并确保您自己的代码不再使用它们。
如果这实现不了,有一个解决方案仍然使你的应用程序访问内部。例如,如果你需要访问 comp 模块,请使用以下命令:
——add-opens = jdk.compiler/com.sun.tools.javac.comp=ALL-UNNAMED
但是,只能将此解决方案作为最后的手段,最好是临时使用,因为您正在规避 Java 团队添加的重要保护措施。
了解关于 JEP 396 中的 Java 16 和 JEP 403 中的 Java 17 的更多信息。
Java升级资源
建议查看我创建的 JavaUpgrades GitHub 存储库,其中包含一些示例、常见错误和解决方案,可以在升级过程中帮助你。
结论
升级依赖关系并为已删除的 JDK 特性添加依赖关系可以解决许多 Java 升级挑战。我建议采用一种结构化的方法逐步升级:首先,确保代码已编译,然后运行测试,再运行应用程序。
如果你告诉自己、团队和公司你可以在 JDK 17 上编译和测试所有的东西,而不是告诉他们它几乎已经完成了,或者更糟的是,它只完成了 80%,那么迁移的过程就会容易很多。
我个人的经验是,从 JDK 11 升级到 JDK 17 要比从 JDK 8 升级到 JDK 11 容易得多。然而,在这两种情况下,对于重要的应用程序来说都是几个小时到几天的时间不等,这主要是由于等待构建完成。
希望本文能简化你的升级过程。
参考链接:
https://spring.io/blog/2021/09/02/a-java-17-and-jakarta-ee-9-baseline-for-spring-framework-6
https://blogs.oracle.com/javamagazine/migrate-to-java-17