@levinzhang
2019-04-27T23:51:08.000000Z
字数 6629
阅读 528
by
Java目前的变化节奏可能令人望而生畏,从目前主流应用程序所运行的Java 8升级到Java 12看上去肯定会很困难。在本文中,我们将会看一下升级所带来的收益、升级过程中潜在的问题以及一些升级的小技巧。
一般来讲,企业级应用不愿意升级到最新版本的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 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。
Java 9的一个变化就是内部API(大多数都是以sun.misc.*
开头的类)隐藏了起来,无法继续使用了。
JDK中有一个名为jdeps的工具,你可以使用-jdkinternals
标记来运行它,这样的话能够检查一组类是否用到了它不该使用的内容。举例来说,如果我在项目的输出中运行如下的命令:
> $JAVA_HOME\bin\jdeps -jdkinternals .
我会得到如下的输出:
. -> /Library/Java/JavaVirtualMachines/jdk1.8.0_201.jdk/Contents/Home/jre/lib/rt.jar
com.mechanitis.demo.sense.twitter.connector.TwitterOAuth (.)
-> sun.misc.BASE64Encoder JDK internal API (rt.jar)
-> sun.misc.Unsafe JDK internal API (rt.jar)
Warning: JDK internal APIs are unsupported and private to JDK implementation that are subject to be removed or changed incompatibly and could break your application.
Please modify your code to eliminate dependency on any JDK internal APIs. For the most recent update on JDK internal API replacements, please check: https://wiki.openjdk.java.net/display/JDK8/Java+Dependency+Analysis+Tool
JDK Internal API Suggested Replacement
---------------- ---------------------
sun.misc.BASE64Encoder Use java.util.Base64 @since 1.8
sun.misc.Unsafe See http://openjdk.java.net/jeps/260
这个工具不仅能够识别出使用了内部API的类,还提供了该使用什么替代方案的建议。
如果你使用Maven或Gradle的话,也需要进行升级。
Gradle 5.0引入了对Java 11的支持,所以我们至少要使用5.0版本。当前的最新版本是5.4.1,它提供了对Java 12的支持。
我们至少要使用3.5.0(最新的版本是3.6.1),需要确保Maven编译器插件至少是3.8版本:
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.0</version>
<configuration>
<release>11</release> <!-- or 12 -->
</configuration>
</plugin>
关于迁移至Java 9或更高版本你所听说的一些问题,都是库和框架需要面对的(而且可能已经被它们所修复了)。例如,很多框架在幕后都会使用反射和内部API。为了保证你的应用能够继续正常运行,你需要确保所有的依赖项都是最新的。很多库都进行了更新,以便于支持Java 9和更高版本,社区正在努力确保这个过程能够持续下去。
有些依赖项可能需要替换。举例来说,很多库都已经使用Byte Buddy来实现代码生成和代理,因为它可以与所有现代版本的Java兼容。在研究迁移至Java 11的时候,你必须要深入理解你的依赖关系,并搞清楚它们是否已经进行了更新以支持Java 8以后的版本。
非JDK核心的API已经被移除掉了,这包括Java EE和Corba模块以及JavaFX。这个问题解决起来通常很简单,只需要在你的依赖管理中添加正确的库就可以了。例如,在Gradle或Maven中添加对JAXB的依赖。JavaFX稍微复杂一些,但是在OpenJFX站点上有非常棒的文档。
要使用最新版本的Java,你并不需要重新编译,这就是语言开发人员如此努力保持向后兼容性的原因之一。你可以无需任何代码变更就可以在持续集成环境(举例来说)中使用新的JDK来运行你的应用。
在前面的步骤中,我们依然可以使用Java 8编译应用。只有在完成了这些步骤之后,你才应该考虑针对Java 11或12进行编译。请记住,如果不想使用新特性的话,我们可以使用较低的语言版本编译应用,这样的话,我们就能够继续回滚到旧版本上。举例来说,在Maven中:
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.0</version>
<configuration>
<release>8</release>
</configuration>
</plugin>
在Gradle中:
sourceCompatibility = JavaVersion.VERSION_1_8
如果所有的事情都运行正常,所有测试都能运行通过而且所有的功能都有很好的性能表现,甚至在生产环境安全运行一段时间之后,我们才能考虑使用新的语言特性。
另外,还需要记住,尽管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技术。