java怎么升级(为什么以及如何升级至 Java 16 或 17)

在 2021 年 4 月 27 日的 InfoQ 直播中,我探讨了为什么应该考虑升级到Java 16或Java 17(一旦发布),并就如何完成升级提供了一些实用的建议。 直播的内容基于我个人的 GitHub 库JavaUpgrades...

在 2021 年 4 月 27 日的 InfoQ 直播中,我探讨了为什么应该考虑升级到Java 16或Java 17(一旦发布),并就如何完成升级提供了一些实用的建议。

直播的内容基于我个人的 GitHub 库JavaUpgrades,其中有文档和示例介绍了升级到 Java 16 或 Java 17 时常见的难题和异常。其中也有具体的解决方案,你可以用在自己的应用程序中。示例要用 Docker 运行,是用 Maven 构建的,但是你当然也可以设置自己的 Gradle 构建。

本文以及那次直播都是为了让用户可以轻松升级到 Java 16 或 Java 17。大部分常见的升级任务都讨论到了,所以你可以更容易地解决它们,并专注于克服应用程序所特有的挑战。

为什么要升级?

Java 的每个新版本,尤其是大版本,都会解决安全漏洞,提升性能,**新特性。** Java 版本最新有助于**应用程序的健康,也有助于组织留住现有的**人员,并有可能吸引来新员工,因为**人员一般更希望使用比较新的技术。

升级有时会被视为一项挑战

人们认为,升级到 Java 的新版本需要很大的工作量。这是因为代码库需要变更,还需要在所有构建和运行应用程序的服务器中安装 Java 的最新版本。幸运的是,有些**使用了 Docker,团队可以让它们自己升级这些内容。

许多人将 Java 9 模块系统(即 Jigsaw)视为一项重大的挑战。然而,Java 9 并不需要你显式地使用模块系统。事实上,大多数运行在 Java 9 以及更高版本上的应用程序并没有在代码库中配置 Java 模块。

评估任何升级所需的工作量都是一项挑战。那取决于多种因素,如依赖项数量及其现状。举例来说,如果你使用的是 Spring Boot,那么升级 Spring Boot 可能已经解决大部分升级问题。遗憾的是,由于存在不确定性,大部分**人员会将升级工作量评估为许多天、周甚或是月。如此一来,考虑成本、时间或其他优先事项,组织或管理层就会推迟升级。我以前见过人们对将 Java 8 应用程序升级到 Java 11 的工作量评估从数周到数月不等。不过,我曾在几天内完成了一次类似的升级。这一部分是因为我之前的**,不过,这也得益于我没有多想就开始了升级过程。周五下午升级 Java 就很**,看看会发生什么。我最近将一个 Java 11 应用程序升级到了 Java 16,我唯一需要完成的任务就是升级一个Lombok依赖项。

升级可能很困难,评估所需的时间似乎是不可能的,但通常,实际的升级过程不会花那么多时间。在许多应用程序升级中,我都见过同样的问题。我希望帮助团队快速解决重复出现的问题,让他们可以集中精力克服应用程序独有的挑战。

Java 的发版节奏

过去,Java每两年发布一个新版本。然而,从 Java 9 发布之后,新版本发布变成了每 6 个月一次,**支持版本(LTS)每 3 年一次。大多数非**支持版本都通过小版本升级提供大约 6 个月的支持,直到下一个版本发布。另一方面,LTS 版本几年内都会收到小版本升级,至少到下个 LTS 版本发布。实际提供支持的时间可能会更长,这取决于 OpenJDK 的供应商(Adoptium、Azul、Corretto 等)。举例来说,Azul 对于非 LTS 版本提供的支持时间就比较长。

你可能会问自己,“我应该总是升级到最新版本,还是应该停留在一个 LTS 版本上?”保证应用程序使用的是 LTS 版本意味着你可以利用小版本升级带来的各种改进,尤其是与安全相关的那些。另一方面,在使用最新的非 LTS 版本时,你应该每隔 6 个月就升级到一个新的非 LTS 版本,否则就无法利用小版本升级了。

然而,每 6 个月升一次级是一项不小的挑战,因为在升级应用程序之前,你可能不得不等待你所使用的框架完成升级。但是,你应该也不会等待太长时间,因为非 LTS 版本的小版本很快就会不再发布了。在我们**,我们目前决定停留在 LTS 版本上,因为我们觉得自己没有时间每 6 个月升级一次,这样一个时间窗口太小。不过也不绝对,如果团队真得需要,或者一个非 LTS 版本带来了有趣的 Java 新特性,那么我们也可能改变决定。

升级到什么版本?

一般来说,应用程序由依赖项和你自己的代码(打包后在 JDK 上运行)构成。如果 JDK 中有什么修改,那么依赖项或/和你自己的代码就需要修改。在大多数情况下,这是由 JDK 移除了某项特性导致的。如果你的依赖项使用了一项已经移除的 JDK 特性,那么请**耐心,等待该依赖项的新版本发布。

多 JDK 版本

当升级应用程序时,你可能希望使用 JDK 的不同版本,如最新版本用于实际的升级,老版本用于**应用程序的运行。用于应用程序**的当前 JDK 版本可以通过**变量JAVA_HOME指定,也可以借助包管理工具SDKMAN!或JDKMon。

对于我GitHub库中的示例,我使用 Docker 和不同的 JDK 版本来说明特定的特性如何工作或造成**。你可以试一下相关特性,而不必安装多个 JDK 版本。遗憾的是,使用 Docker 容器的反馈回路有点长。需要首先构建并运行镜像。所以一般来说,我建议你尽可能从 IDE 内升级。但是,在一个干净的、没有个性化设置的 Docker 容器**中试验一些东西或构建应用程序或许是一个不错的注意。

为了说明这一点,我们创建了一个**的Dockerfile 文件,其中包含下面的内容。该示例使用了 Maven JDK 17 镜像,并将你的应用程序代码复制到里面。RUN 命令会运行所有测试,出错了也不会失败。

FROM maven:3.8.1-openjdk-17-slimADD . /yourprojectWORKDIR /yourprojectRUN mvn test --fail-at-end

复制代码

要想构建上述镜像,则运行docker build 命令,并通过-t 指定标签(或名称),通过. 配置上下文,在本例中是当前目录。

docker build -t javaupgrade .

复制代码

**工作

大多数**人员都是从升级本地**开始,然后是构建服务器,最后是各部署**。不过,我有时候会直接在构建服务器上使用新版本的 Java 进行构建,而不是针对这个特定的项目做好所有配置,然后看看会出什么问题。

一次性从 Java 8 升级到 17 也是可以的。不过,如果你遇到任何问题,可能会很难确定这两个 Java 版本间的哪个新特性导致了问题。小步升级,比如从 Java 8 升级到 Java 11,定位问题会比较容易。而且,在你搜索问题原因时,加上 Java 版本也是有帮助的。

我建议在旧版本的 Java 上升级依赖项。那样你可以专注于让依赖项可以正常工作,而不必同时升级 Java。遗憾的是,有时候没法这样做,因为有些依赖项需要更新的 Java 版本。如果是这样,你就别无选择,只能同时升级 Java 和依赖项了。

Maven 和 Gradle 提供了一些插件,可以显示依赖项的新版本。mvn versions:display-dependency-updates 命令会调用Maven版本插件。该插件会列出有新版本可用的依赖项:

[INFO] --- versions-maven-plugin:2.8.1:display-dependency-updates (default-cli) @ mockito_broken ---[INFO] The following dependencies in Dependencies have newer versions:[INFO] org.junit.jupiter:junit-jupiter .................... 5.7.2 -> 5.8.0-M1[INFO] org.mockito:mockito-junit-jupiter ................... 3.11.0 -> 3.11.2

复制代码

在build.gradle 文件中配置好插件后,gradle dependencyUpdates -Drevision=release 命令会调用Gradle版本插件:

plugins { id "com.git**.ben-manes.versions" version "$version" }

复制代码

升级完依赖项后,就可以升级 Java 了。要想把代码改到在新版本的 Java 上运行,最好是在 IDE 中进行,以确保它支持 Java 的最新版本。最后,将构建工具升级到最新版本,并配置 Java 版本:

<plugin> <groupId>org.apache.maven.plugins</groupId> <groupId>org.openjdk.nashorn</groupId> <artifactId>nashorn-core</artifactId> <version>15.2</version></dependency>

复制代码

Java 16

在这个版本中,JDK **者封装了一些 JDK 内部构件。他们不希望应用程序再使用 JDK 的底层 API。这主要影响了 Lombok 这样的工具。所幸,Lombok 几个周内就发布了一个新版本,解决了这个问题。

如果你有任何代码或依赖项仍然使用 JDK 内部构件,那么可以尝试使用 JDK 的高级 API 来解决这个问题。如果不行的话,Maven 还提供了一种变通方法:

<plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>3.8.1</version> <configuration> <fork>true</fork> <compilerArgs> <arg>-J--add-opens=jdk.compiler/com.sun.tools.javac.comp=ALL-UNNAMED</arg> <arg>-J--add-opens=jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED</arg> <arg>-J--add-opens=jdk.compiler/com.sun.tools.javac.main=ALL-UNNAMED</arg> <arg>-J--add-opens=jdk.compiler/com.sun.tools.javac.model=ALL-UNNAMED</arg> <arg>-J--add-opens=jdk.compiler/com.sun.tools.javac.parser=ALL-UNNAMED</arg> <arg>-J--add-opens=jdk.compiler/com.sun.tools.javac.processing=ALL-UNNAMED</arg> <arg>-J--add-opens=jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED</arg> <arg>-J--add-opens=jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED</arg> <arg>-J--add-opens=jdk.compiler/com.sun.tools.javac.jvm=ALL-UNNAMED</arg> </compilerArgs> </configuration></plugin>

复制代码

我曾尝试使用Maven Toolchains通过在pom.xml 文件中指定 JDK 版本来实现 JDK 切换。很遗憾,当使用 Lombok 的旧版本在 Java 16 上运行应用程序时报错了:

[ERROR] Failed to execute goal org.apache.maven.plugins:maven-compiler-plugin:3.8.1:compile (default-compile) on project broken: Compilation failure -> [Help 1]

复制代码

上面就是全部报错信息。我不知道你怎么看,但在我看来,这没什么用,所以我提交了这个问题。如果这个问题修复了,那么使用 Maven Toolchains 切换版本是一种不错的方法。后来,我直接在 Java 16 上运行代码,**了一个更具描述性的错误,其中提到了我之前展示的部分变通方案:

… class lombok.javac.apt.LombokProcessor (in unnamed module @0x21bd20ee) cannot access class com.sun.tools.javac.processing.JavacProcessingEnvironment (in module jdk.compiler) because module jdk.compiler does not export com.sun.tools.javac.processing to unnamed module …

复制代码

Java 17

JDK 维护人员已经就9月份要发布的内容达成了一致。Applet API将被废弃,因为浏览器停止支持 Applet 已经很长时间了。实验性的 AOT 和 JIT 编译器也将被移除。作为实验性编译器的替代方案,你可以使用GraalVM。最大的变化是 JEP-403:强封装的JDK内部构件。Java 选项--illegal-access 已经无效,如果你仍然试图访问一个内部 API,则会抛出如下异常:

java.lang.reflect.InaccessibleObjectException: Unable to make field private final {type} accessible: module java.base does not "opens {module}" to unnamed module {module}

复制代码

大多数时候,这可以通过升级依赖项或使用高级 API 来解决。如果不行的话,你可以使用--add-opens 参数来**对内部 API 的访问。不过,除非不得已不要这样做。注意,有些工具在 Java 17 上还无法运行。例如,Gradle 就无法构建项目,而 Kotlin 不能使用jvmTarget = "17" 。有些框架,如Mockito,在 Java 17 上也有些小问题。enum 字段中的方**导致这个特定的问题。不过,我估计大部分问题都会在 Java 17 发布之前或发布之后短期内**解决。对于任何插件或依赖项,你可能会在构建应用程序时看到这条消息“不支持的类文件主版本 61”。类文件主版本 61 用于 Java 17,60 用于 Java 16。这基本上是说该插件或依赖项不能用于那个 Java 版本。大多数时候,升级到最新版本就可以解决问题。

完工

在解决了所有挑战之后,你终于可以在 Java 17 上运行应用程序了。经过努力,你现在可以使用令人兴奋的 Java 新特性了,如记录和**匹配。

小结

升级 Java 是一项挑战,不过这也要看你的 Java 版本和依赖项有多老,你的**配置有多复杂。本文旨在帮助你解决 Java 升级时最常见的挑战。一般来说,很难评估实际的升级工作要花费多长时间。我觉得,大多数时候,从 Java 11 升级到 Java 17 要比从 Java 8 升级到 Java 11 简单。对于大多数应用程序,从一个 LTS 版本升级到下一个 LTS 版本需要几个小时到几天的时间。大部分时间都花在了构建应用程序上。重要的是先开始,然后逐步更改。这样可以激励自己、团队和管理层继续努力。

你开始升级应用程序了吗?

作者简介:

Johan Janssen 是 Sanoma Learning **门的一名软件架构师。他特别喜欢分享 Java 相关的知识。他在 Devoxx、Oracle Code One、Devnexus 等会议上做过演讲。他通过参与计划**会来协助大会组织,发起并组织了 JVMCON。他得过的奖项有 JavaOne Rock Star 和 Oracle Code One Star。他在数字和印刷媒体上撰写了各种文章。他是 Chocolatey 各种 Java JDK/JRE 包的维护者,每月有大约 10 万次下载。

原文链接:

Why and How to Upgrade to Java 16 or 17

  • 发表于 2022-12-05 19:32
  • 阅读 ( 107 )
  • 分类:互联网

0 条评论

请先 登录 后评论
临风纵欢
临风纵欢

714 篇文章

你可能感兴趣的文章

相关问题