Java 9教程,面向那些必须使用遗留代码的人

晚上好,同事们。 恰好在一个月前,我们从曼宁(Manning)获得了一份Modern Java的翻译合同,这将成为明年我们最著名的新产品之一。 Java中的“现代”和“传统”问题是如此严重,以至于对这种书的需求已经非常成熟。 Wayne Citrin在一篇文章中简要介绍了灾难的规模以及如何解决Java 9中的问题,我们今天将其翻译为您。

每隔几年,随着Java新版本的发布,JavaOne的演讲者开始津津乐道新的语言构造和API,并赞扬它们的优点。 同时,热心的开发人员也渴望引入新功能。 这种情况远非现实-完全没有考虑到大多数程序员都在忙于支持和完成现有应用程序 ,也没有从头开始编写新应用程序。

大多数应用程序-尤其是商业应用程序-必须与不支持所有这些新的超级duper功能的Java的早期版本向后兼容。 最后,大多数客户和最终用户,尤其是大型企业领域的用户和最终用户,都对Java平台的根本升级持谨慎态度,他们宁愿等到它变得更强大。

因此,开发人员一旦尝试新的机会,就会面临很多问题。 您会在代码中使用默认的接口方法吗? 也许-如果您很幸运并且您的应用程序不需要与Java 7或更低版​​本进行交互。 是否要使用java.util.concurrent.ThreadLocalRandom类在多线程应用程序中生成伪随机数? 如果您的应用程序应同时在Java 6、7、8或9上运行,它将无法正常工作。

随着新版本的发布,支持遗留代码的开发人员感到孩子被迫盯着糕点店的橱窗。 他们不允许进入内部,所以他们的命运是失望和沮丧。

那么,Java 9的新发行版中是否有涉及遗留代码支持的程序员? 有什么可以使他们的生活更轻松? 幸运的是。

在遗留代码的支持下必须要做的就是Java 9的外观

当然,您可以将新平台的功能推入旧版应用程序中,在这些旧版应用程序中必须遵守向后兼容性。 特别是,总是有机会利用新的API。 但是,结果可能有点难看。

例如,当您的应用程序还需要使用不支持该API的Java的较早版本时,如果要访问新API,则可以应用后期绑定。 假设您要使用Java 8中引入的java.util.stream.LongStream类,并且要使用anyMatch(LongPredicate)方法,但是应用程序必须与Java 7兼容。您可以创建一个帮助器类,如下所示:

 public classLongStreamHelper { private static Class longStreamClass; private static Class longPredicateClass; private static Method anyMatchMethod; static { try { longStreamClass = Class.forName("java.util.stream.LongStream"); longPredicateClass = Class.forName("java.util.function.LongPredicate"); anyMatchMethod = longStreamClass.getMethod("anyMatch", longPredicateClass): } catch (ClassNotFoundException e) { longStreamClass = null; longPredicateClass = null; anyMatchMethod = null } catch (NoSuchMethodException e) { longStreamClass = null; longPredicateClass = null; anyMatchMethod = null; } public static boolean anyMatch(Object theLongStream, Object thePredicate) throws NotImplementedException { if (longStreamClass == null) throw new NotImplementedException(); try { Boolean result = (Boolean) anyMatchMethod.invoke(theLongStream, thePredicate); return result.booleanValue(); } catch (Throwable e) { // lots of potential exceptions to handle. Let's simplify. throw new NotImplementedException(); } } } 

有多种方法可以简化此操作,或者使其变得更通用或更有效-您就明白了。

与在Java 8中一样,可以调用LongStreamHelper.anyMatch(theLongStream, thePredicate) ,而不必像在Java 8中那样调用LongStreamHelper.anyMatch(theLongStream, thePredicate) 。 如果您使用的是Java 8,则可以使用,但是如果使用Java 7,则程序将抛出NotImplementedException

为什么这很丑? 因为如果您需要访问许多API,代码可能会变得过于复杂(实际上,即使是现在,只有一个API,这已经很不方便了)。 另外,由于代码不能直接提及LongStreamLongPredicate ,因此这种做法也不是类型安全的。 最后,由于反射的开销以及其他try-catch块,这种做法的效率要低得多。 因此,尽管可以通过这种方式完成,但它并不太有趣,并且由于粗心而充满错误。

是的,您可以访问新的API,并且您的代码同时保持向后兼容性,但是使用新的语言构造将不会成功。 例如,假设我们需要在代码中使用lambda表达式,这些表达式应保持向后兼容并可以在Java 7中运行。您很不走运。 Java编译器不允许您在目标上方指定源代码的版本。 因此,如果您将源代码的合规性级别设置为1.8(即Java 8),而目标合规性级别是1.7(Java 7),则编译器将不允许您这样做。

多版本JAR文件将为您提供帮助

最近,又出现了一个使用最新Java功能的绝佳机会,同时允许应用程序与不支持此类应用程序的Java的较旧版本一起使用。 在Java 9中,新API和新Java语言结构均提供了此功能:我们正在谈论多版本JAR文件

多版本的JAR文件几乎与旧的JAR文件没有什么不同,但有一个重要的警告:新的JAR文件中出现了一个新的“小生境”,您可以在其中编写使用最新Java 9功能的类。如果使用Java 9,则JVM将找到该“小众”,将使用其中的类,而忽略JAR文件主要部分中同名的类。

但是,在使用Java 8或更低版本的Java时,JVM并不知道此“特定位置”的存在。 她将其忽略,并使用JAR文件主要部分中的类。 随着Java 10的发布,对于使用Java 10最相关功能的类,将出现一个新的类似“小众”。

JEP 238中 ,提供了一个Java开发提案,其中描述了繁琐的JAR文件,其中提供了一个简单示例。 假设我们有一个JAR文件,其中有四个在Java 8或更低版本中运行的类:

 JAR root - A.class - B.class - C.class - D.class 

现在想象一下,在Java 9发布之后,我们重写了类A和B,以便它们可以使用特定于Java 9的新功能。然后Java 10发布了,我们重写了类A,以便它可以使用Java 10的新功能。 ,应用程序仍可以在Java 8上正常运行。新的多版本JAR文件如下所示:

 JAR root - A.class - B.class - C.class - D.class - META-INF Versions - 9 - A.class - B.class - 10 - A.class 

JAR文件不仅获得了新的结​​构,还获得了新的结​​构。 现在,在他的清单中表明该文件是多版本的。

当您在Java 8 JVM中运行此JAR文件时,它会忽略\META-INF\Versions部分,因为它甚至不会怀疑它或寻找它。 仅使用原始的A,B,C和D类。

在Java 9下运行时,将使用\META-INF\Versions\9中的类,而不是原始的类A和B,但将忽略\META-INF\Versions\10

在Java 10下运行时,将同时使用两个分支\META-INF\Versions ; 特别是Java 10的A版本,Java 9的B版本以及默认的C和D版本。

因此,如果您需要在应用程序中使用Java 9的新ProcessBuilder API,但需要确保应用程序在Java 8下仍能继续工作,只需使用ProcessBuilder在JAR文件的\META-INF\Versions\9部分中编写类的新版本即可。 ,并将旧类保留在存档的主体部分(默认情况下使用)。 这是使用Java 9的新功能而不牺牲向后兼容性的最简单方法。

Java 9 JDK具有jar.exe工具的一个版本,该版本支持创建多版本JAR文件。 其他非JDK工具也提供此支持。

Java 9:模块,无处不在的模块

Java 9模块系统 (也称为Project Jigsaw)无疑是Java 9的最大变化。模块化目标之一是增强Java的封装机制,以便开发人员可以指定向其他组件提供哪些API并可以计数, JVM将强制执行封装。 通过模块化,封装比使用针对类或类成员的public/protected/private访问修饰符更强大。

模块化的第二个目标是指出哪些模块需要其他模块才能工作,并且在启动应用程序之前,请确保所有必要的模块均已就位。 从这个意义上讲,模块比传统的类路径机制更强大,因为类路径未事先检查,并且由于缺少必要的类而可能导致错误。 因此,当应用程序有足够的时间工作足够长的时间或启动了许多次之后,就已经可以检测到错误的类路径。
整个模块系统庞大而复杂,对其的详细讨论超出了本文的范围(此处是一个很好的详细说明 )。 在这里,我将关注模块化的那些方面,这些方面可以帮助开发人员获得遗留应用程序的支持。

模块化是一件好事,即使应用程序的其余部分(尚未)模块化,开发人员也应尽可能将新代码分成模块。 幸运的是,这要归功于模块使用规范。

首先,将JAR文件模块化(并变成一个模块),并在JAR文件的根目录中显示module-info.class文件(从module-info.java编译)。 module-info.java包含元数据,尤其是元数据,导出包的模块的名称(即,从外部可见),该模块所需的模块以及一些其他信息。

module-info.class的信息仅在JVM查找时才可见-也就是说,如果它与Java的较旧版本一起使用,则系统会像对待常规JAR文件一样对待模块化的JAR文件(假定代码已编译为与Java的较旧版本一起使用严格来说,这需要花点时间,仍然是Java 9被指定为module-info.class的目标版本,但这是真实的。

因此,您应该能够使用Java 8及以下版本运行模块化的JAR文件,但前提是它们在其他方面也与Java的早期版本兼容。 另请注意,可以保留将module-info.class 放置在多版本JAR文件的版本区域中

在Java 9中,既有类路径又有模块路径。 和模块路径。 类路径照常工作。 如果将模块化的JAR文件放在类路径中,则会像其他任何JAR文件一样被浪费。 也就是说,如果您对JAR文件进行了模块化,并且您的应用程序尚未准备好将其视为模块,则可以将其放在类路径中,它将照常工作。 您的旧代码应该可以非常成功地处理它。

还要注意,类路径中所有JAR文件的集合被视为单个未命名模块的一部分。 这种模块被认为是最通用的,但是,它将所有信息导出到其他模块,并且可以引用任何其他模块。 因此,如果您还没有模块化的Java应用程序,但是有些旧的库也没有模块化(也许永远不会)-您只需将所有这些库放在类路径中,整个系统就可以正常工作。

Java 9具有与类路径一起使用的模块路径。 从此路径使用模块时,JVM可以检查(在编译时和在运行时)是否所有必要的模块都到位,如果缺少任何模块,则报告错误。 作为未命名模块的成员,类路径中的所有JAR文件均可通过模块化路径中列出的模块访问,反之亦然。

将JAR文件从类路径传输到模块路径并不难-并充分利用模块化。 首先,您可以将module-info.class文件添加到JAR文件中,然后将模块化的JAR文件放入模块路径中。 这样新建的模块仍然可以访问类路径JAR中的所有其余JAR文件,因为它们进入了未命名的模块并保持访问状态。

也有可能您不想调制JAR文件,或者该JAR文件不属于您,而是属于其他人,因此您无法自己对其进行调制。 在这种情况下,仍可以将JAR文件放在模块路径中,它将成为自动模块。

即使自动模块没有module-info.class ,也将其视为模块。 该模块与其包含的JAR文件同名,其他模块可以显式请求它。 它会自动导出所有公开可用的API,并读取(即要求)所有其他命名模块以及无名模块。

因此,可以将来自类路径的未模块化JAR文件转换为模块,而无需执行任何操作。 继承的JAR文件会自动转换为模块,它们只是缺少一些信息,这些信息将确定是否所有必要的模块都已安装到位,或者确定缺少的模块。

并非每个未模块化的JAR文件都可以移至模块路径并转换为自动模块。 有一个规则: 包只能是一个命名模块的一部分 。 即,如果一个包位于一个以上的JAR文件中,则只有一个包含该包的JAR文件可以组成自动模块。 其余的可以保留在类路径中并加入未命名的模块。

乍一看,这里描述的机制似乎很复杂,但实际上它很简单。 实际上,在这种情况下,只是您可以将旧的JAR文件保留在类路径中或将它们移至模块路径。 您可以将它们分为模块。 而且,当对旧的JAR文件进行模块化时,可以将它们保留在类路径中或将其移动到模块路径。

在大多数情况下,一切都应该像以前一样简单地工作。 您继承的JAR文件应该在新的模块化系统中扎根。 调制代码的次数越多,需要检查的依赖性信息就越多,并且在开发的早期阶段会检测到缺少的模块和API,这可能会节省很多工作。

Java 9“自己动手”:模块化JDK和Jlink

遗留Java应用程序的问题之一是最终用户可能无法在合适的Java环境中工作。 保证Java应用程序正常运行的一种方法是为应用程序提供运行时。 Java允许您创建可以在应用程序内分发的私有(可再分发)JRE。 是创建私有JRE的方法。 通常,将采用与JDK一起安装的JRE文件层次结构,保存必要的文件,并保存具有应用程序可能需要的功能的可选文件。

这个过程有点麻烦:必须小心地维护安装文件的层次结构,以免丢失单个文件,而不是单个目录。 这本身不会带来任何伤害,但是,您仍然希望摆脱所有多余的内容,因为这些文件会占用空间。 是的,很容易让步并且犯这样的错误。

那么为什么不将这项工作委托给JDK呢?

在Java 9中,您可以创建一个自包含的环境,该环境已添加到应用程序中-在此环境中,应用程序正常工作将具有一切必要的条件。 您不必再担心用户的计算机将具有执行Java的错误环境,也不必担心自己不当构建了私有JRE。

用于创建此类独立可执行映像的关键资源是模块化系统。 现在,您不仅可以模块化自己的代码,还可以模块化Java 9 JDK本身。 现在,Java类库是模块的集合,而JDK工具也由模块组成。 模块系统要求您指定代码中所需的基类模块,并指定必要的JDK元素。

为了将它们整合在一起,Java 9提供了一个名为jlink的特殊新工具。 通过启动jlink,您将获得文件的层次结构-恰好是运行应用程序所需的文件层次结构,更多,更多。 这样的集合将比标准JRE小得多,而且,它将是特定于平台的(即为特定的操作系统和计算机选择)。 因此,如果要为其他平台创建此类可执行映像,则需要在每个需要该映像的特定平台上的安装上下文中运行jlink。

还要注意:如果在没有模块化的应用程序上运行jlink,则该工具根本没有挤压JRE的必要信息,因此jlink除了打包整个JRE之外将一无所有。 即使在这种情况下,它也会为您带来一些方便:jlink会为您打包JRE,因此您不必担心如何正确复制文件层次结构。

使用jlink,可以很容易地打包应用程序以及运行它所需的一切-而且您不必担心会做错事情。 该工具将仅打包应用程序正常运行所需的运行时部分。 也就是说,可以保证旧版Java应用程序可以接收将在其中运行的环境。

新旧会议

, Java- , , . Java 9, , API , ( ) , , Java.

Java 9: -, , , Java.

JAR- Java 9 JAR-, Java . , Java 9, Java 8 – .

Java, , JAR- , . , , « » .

JDK jlink, , . , Java – .

Java, Java 9 , – , , Java.

Source: https://habr.com/ru/post/zh-CN417733/


All Articles