核心要点
从Java 8之后,Java引入了很多有用的新语言特性,以及新工具和性能改善(尤其是垃圾收集相关的优化)。
在选择升级时,我们所面临的选择是升级到最新的Java(12)并准备每六个月升级一次,还是升级到最新的LTS(11)版本,这样能够给自己三年的时间再去考虑下一次升级;
不要忽略编译器警告。在现代Java领域中,废弃的功能要被更严肃地对待,Java 10和Java 11都删除了API;
Java 9所带来的一个变化就是内部API(大多数是sun.misc.*打头的包中的类)被隐藏了,导致无法使用了。在Java 11中,非核心JDK的API也被删除了。这些变化可能会影响到你的应用,但是有明确的迁移路径来避免这些问题;
在度过了第一次升级的艰难时期之后,我们至少可以每六个月在最新版本的Java上测试一下我们的应用,比如在CI环境中。
一般来讲,企业级应用不愿意升级到最新版本的 Java,除非该版本已经得到了充分的证明。Java 9 在 2017 年 9 月发布之后,我们每六个月就会有一个新版本的Java发布,这一切变得更具挑战性。所以,尽管 Java 9 发布还不到两年的时间,最新的版本已经是 Java 12 了。
这种变化节奏可能会令人望而生畏,而且从目前主流应用程序所运行的Java 8升级到 Java 12 可能会有很多工作要做。在本文中,我们将会看一下:
升级所带来的收益;
升级过程中可能会出现的问题;
关于升级的一些技巧。
为何要升级?
在深入研究如何升级的细节之前,我们应该认真考虑一下为何要升级。
特性
作为开发人员,我们最关心的是语言的每次升级所带来的新特性和 API。从 Java 8 之后,我们已经有了很多的新特性,并且还有改变我们工作方式的新工具。在这里我挑选了一些开发人员认为最新版本的 Java 让他们的工作更容易的新特性。
局部变量类型推断(Local-variable type inference) (即var
)是语法糖的一个绝佳示例,它有助于减少样板式代码。它并不是针对所有可读性问题的解决方案,但是在正确的地方使用它能够帮助代码的读者关注业务逻辑(它要做什么)而不是样板式的代码(它是如何做的)。
针对集合的便利的工厂方法(Convenience Factory Methods for Collections) 能够大幅度简化集合的创建,比如 List、Map 和 Set。这些工厂方法还能创建不可修改的集合,让它们使用起来更加安全。
收集至不可变的集合,这个特性能够针对 Streams 操作使用新的 Collector,它会将结果放到一个不可变的集合中。
Predicate::not提供了一个很简便的方式来否定断言 lambdas 表达式或方法引用,同样能够减少我们的样板式代码。
Optional上的新方法能够让我们在使用 Optional 来替换繁琐的 if 语句时,有了更多的函数式编程可选方案。
JShell是一个REPL,它能够让我们运行 Java 编写的代码行,甚至脚本。它是一种体验新特性的好办法,我们可以在本地开发环境中使用它,避免在生产环境的应用代码中采用新版本的 Java。
HttpClient构建到了 JDK 中。几乎所有人都在以某种形式使用 HTTP,要么通过 Web 应用,要么通过 REST 服务或其他方式。内置的客户端移除了对外部的依赖,同时支持 HTTP 1.1 和 2 的同步和异步编程模型。
JAR文件的多发布版本(Multi release jar files),这是一个工具库,开发人员可以使用它来支持需要最新版本的 Java,同时还要支持使用旧版本的场景。
JLink是一个非常棒的工具,由Java模块系统(Java Module System)提供,它能够让我们只打包和部署 JDK 中我们真正需要的部分。
性能
通常来讲,新版本都要比之前的版本表现更好。“更好”会有多种表现形式,但是在最近的释放版本中,我们看到了启动时间的改善、内存消耗的减少,以及使用特定的 CPU 指令从而让代码使用更少的 CPU 周期。Java 9、10、11 和 12 都对垃圾收集功能进行了重大的变更和改进,包括将默认的垃圾收集器改为G1、对 G1 的改进以及三个实验性的垃圾收集器(Java 11 中的Epsilon和ZGC,Java 12 中的Shenandoah)。三个收集器看起来可能有些过分了,但是每个收集器都针对不同的使用场景进行了优化,所以现在你可以从中选择一个最适合你的应用程序的现代垃圾收集器。
成本的削减
Java 最近版本的改善可能会为你带来成本的削减。尤其是像 JLink 这样的工具能够减少部署制件的大小,再加上内存使用方面的改善,这都会降低云计算的成本。
另外还有一项潜在的收益,那就是使用现代版本的语言会吸引更多的开发人员,因为很多开发人员都希望有机会学习新的东西并更新自己的技能。使用现代版本的 Java 有利于开发人员的留任和招聘,最终会影响团队的运行成本。
升级的注意事项
从 Java 8 以来,很多事情都发生了变化,而且不仅局限于语言级别的特性。甲骨文公司开始发布两个不同的构建版本,它们有着不同的许可证(其中一个是商业构建版本,在生产环境使用的话需要付费,另外一个是开源的 OpenJDK 构建版本),并且更改了他们的升级和支持模型。这在社区引发了很多的争论,但是最终在选择使用哪个JDK方面,我们会有更多的选择。我们必须要考虑采用什么方案以及要从 JDK 中得到什么。
该选择哪个版本?
鉴于最新的版本已经是 Java 12 了,在从 Java 8 进行升级时,我们似乎有很大的可选余地。实际上,这个选择要简单得多。现在,我们每六个月就会有一个发布版本,每个新的发布版本都会取代之前的版本。唯一例外的是每三年会有一个长期支持(Long Term Support,LTS)的发布版本。这样的话,就允许组织选择最合适的升级路径,要么每六个月就升级到最新的版本,要么采用传统方式每三年进行一次大的升级,也就是升级到 LTS 版本。Java 8 是 LTS 版本,但是甲骨文在今年一月已经停止了在商业应用中对它的(免费)更新。Java 11 是目前的 LTS 版本,尽管甲骨文目前还没有为其提供免费的商业更新就发布了 Java 12,但是有很多组织都提供了大量的 JDK 构建版本,这些组织将会提供至少三年的更新。
你所面临的选择就是,要么升级到最新版本的 Java(12)并且为每六个月一次的更新做好准备,要么升级到最新的 LTS 版本(11),这样的话,你会有三年的时间考虑下一次的升级。我们建议更大的组织采用的策略是从 LTS 版本升级到下一个 LTS 版本,而创业型的小型组织可以每六个月升级一次。更快速且可预测的发布节奏所带来的好处是它能够同时支持这两种方案。
使用哪个构建版本?
我们不能仅仅认为甲骨文没有为 LTS 版本提供免费更新,就认为无法在生产环境使用 LTS 版本并获取免费更新。现在,有很多的组织和厂商都在提供OpenJDK(JDK 的参考实现,甲骨文的商用 JDK 甚至都是基于它构建的)的构建版本。我在这里不会对它们一一列举,请参阅这个列表,它涵盖了免费 OpenJDK 构建版本的提供商、免费公开更新可以维护到什么时间以及商业支持的详细信息。
看上去,这比以往要复杂得多。当然,还有很多其他的因素要考虑,这是更多选择所带来的副作用。Java Champions 正在就许可证、支持、更新以及不同方案的话题准备一个更加完整的讨论,在QCon London 2019上也有关于该话题的问答环节。
Java 9 会破坏一切吗?
很多开发人员在从 Java 8 进行升级时,最主要的顾虑在于 Java 9 所带来的重大变化,他们害怕这些变更会破坏掉应用程序。其中一项变更就是对内部API的封装,这意味着 JDK 中一些过去可用的方法无法使用了。在 Java 9 发布的时候,这一点,再加上对tools.jar和rt.jar的移除导致很多人感到非常担心,但事后证明,相对于应用开发人员,它们对库、框架和语言开发人员所带来的问题更严重。
如果你的应用没有做什么不应该做的事情(比如使用内部 API 或者已废弃的方法),迁移至 Java 9 或更高版本并不像看上去那么恐怖。社区面临的很多问题实际上已经通过我们所使用的构建工具和库解决掉了。
升级指南
每个升级过程都是与要迁移的应用相关的。但是,有一些基本的良好实践能够帮助我们简化过程。我们按照要处理的顺序将它们列到了这里,你会发现最初的一些步骤根本就不需要升级版本的 JDK。
处理编译告警
警告的出现都是有原因的,如果你遇到警告的话,它们通常预示着一些将来会消失掉的特性。如果你在 Java 8 JDK 上使用 Java 8 的话,那么可能会看到废弃提醒的警告,或者某些不该使用的特性的警告(参见图 1),在尝试升级到更新版本的 JDK 之前,请解决这些警告。
图 1:来自 JDK 8 的编译器告警示例
注意,在现代 Java 中,废弃的功能要被更严肃地对待,Java 10和Java 11都删除了 API。
检查对内部 API 的使用
Java 9 的一个变化就是内部 API(大多数都是以sun.misc.*
开头的类)隐藏了起来,无法继续使用了。
JDK 中有一个名为jdeps的工具,你可以使用-jdkinternals
标记来运行它,这样的话能够检查一组类是否用到了它不该使用的内容。举例来说,如果我在项目的输出中运行如下的命令:
我会得到如下的输出:
这个工具不仅能够识别出使用了内部 API 的类,还提供了该使用什么替代方案的建议。
升级构建工具
如果你使用 Maven 或 Gradle 的话,也需要进行升级。
Gradle
Gradle 5.0 引入了对 Java 11 的支持,所以我们至少要使用 5.0 版本。当前的最新版本是5.4.1,它提供了对 Java 12 的支持。
Maven
我们至少要使用 3.5.0(最新的版本是3.6.1),需要确保 Maven 编译器插件至少是 3.8 版本:
更新依赖
关于迁移至 Java 9 或更高版本你所听说的一些问题,都是库和框架需要面对的(而且可能已经被它们所修复了)。例如,很多框架在幕后都会使用反射和内部 API。为了保证你的应用能够继续正常运行,你需要确保所有的依赖项都是最新的。很多库都进行了更新,以便于支持 Java 9 和更高版本,社区正在努力确保这个过程能够持续下去。
有些依赖项可能需要替换。举例来说,很多库都已经使用Byte Buddy来实现代码生成和代理,因为它可以与所有现代版本的 Java 兼容。在研究迁移至 Java 11 的时候,你必须要深入理解你的依赖关系,并搞清楚它们是否已经进行了更新以支持 Java 8 以后的版本。
添加对已移除功能的依赖
非 JDK 核心的 API 已经被移除掉了,这包括Java EE和Corba模块以及JavaFX。这个问题解决起来通常很简单,只需要在你的依赖管理中添加正确的库就可以了。例如,在 Gradle 或 Maven 中添加对JAXB的依赖。JavaFX 稍微复杂一些,但是在OpenJFX站点上有非常棒的文档。
使用新 JDK 运行应用
要使用最新版本的 Java,你并不需要重新编译,这就是语言开发人员如此努力保持向后兼容性的原因之一。你可以无需任何代码变更就可以在持续集成环境(举例来说)中使用新的 JDK 来运行你的应用。
使用新的 JDK 进行编译
在前面的步骤中,我们依然可以使用 Java 8 编译应用。只有在完成了这些步骤之后,你才应该考虑针对 Java 11 或 12 进行编译。请记住,如果不想使用新特性的话,我们可以使用较低的语言版本编译应用,这样的话,我们就能够继续回滚到旧版本上。举例来说,在 Maven 中:
在 Gradle 中:
开始使用新特性
如果所有的事情都运行正常,所有测试都能运行通过而且所有的功能都有很好的性能表现,甚至在生产环境安全运行一段时间之后,我们才能考虑使用新的语言特性。
另外,还需要记住,尽管 Java 9 版本完全是关于 Java 模块系统的,但是应用程序并不一定必须要使用它,即便我们已经迁移到了支持该功能的版本上的时候依然如此。不过,如果你对采用模块系统感兴趣的话,我们在InfoQ上有一个使用指南。
总结
Java 8 之后,发生了很多的变化:每六个月就会有一个发布版本;许可证、更新和支持方面都发生了变化;JDK 的来源可能也改变了。除此之外,当然还有新的语言特性,包括 Java 9 所带来的重大变化。但是,现在 Java 11 取代 Java 8,成为了最新的 LTS 版本,主要的库、框架和构建工具都采用了最新版本的 Java,所以这是将应用升级至 Java 11 或 12 好时机。
在度过了第一次升级的艰难时期之后,我们至少可以每六个月在最新版本的 Java 上测试一下我们的应用,比如在 CI 环境中。理想情况下,你甚至可以跟随六个月的发布节奏,这样每六个月都能使用最新版本的 Java 并且能够在第一时间使用新的特性。
关于作者
Trisha Gee 是 Java Champion,她曾经为多个行业中各个规模的公司开发 Java 应用,包括金融、制造业、软件行业和非盈利组织。她在 Java 高性能系统方面有着丰富的经验,热衷于提升开发人员的生产力,并参与了开源项目的开发。她是 JetBrains 的 Java 开发人员倡导者,这使得她能够掌握最前沿的 Java 技术。
查看英文原文:Upgrading from Java 8 to Java 12
评论 2 条评论