支持Java 8

尽管Java到目前为止已经发布了版本13,但是有许多生产安装都与Java 8一起运行。作为专家,即使是最近几天,我也多次开发Java 8代码,我必须为这不是Java 6而感到高兴。另一方面,作为开放源代码开发人员,我可以自由使用Java 11、12甚至13开发Java代码。 确实如此。

但是,另一方面,我希望使用我的代码。 开发诸如License3j或Java :: Geci之类的工具,这是一种发布与Java 11兼容的字节代码的库,切断了所有可能使用这些库的基于Java 8的应用程序。

我希望这些库可以从Java 8获得。

一种解决方案是在Git存储库中保持两个分支平行,并拥有Java 11+和Java 8版本的代码。 这是我对Java :: Geci 1.2.0版本所做的工作。 这很麻烦,容易出错,并且需要很多工作。 我之所以拥有这些代码,是因为我的儿子也是Java开发人员,他的职业生涯始于他的志愿工作。

(不,我没有向他施加压力。他的英语说和写的都比我好。他会定期审阅这些文章,以解决我的语言破裂问题。如果他对压力有不同的看法,则可以在此处随意添加任何注释,直到结束为止。括号,我不会删除或修改它。注意:)

NOTE:)之间的任何内容都是他的意见。

另一种可能性是使用Jabel

在本文中,我将介绍如何在Java :: Geci项目中使用Jabel。 Jabel的文档很简短,但仍然很完整,对于更简单的项目,它的工作原理确实如此。 例如,对于Licenese3j项目,我实际上只需要向pom.xml添加几行。 对于一年来开发的更复杂的项目,而没有考虑对Java 8兼容性的任何妥协,则要复杂一些。

关于贾贝尔

Jabel是一个开源项目,可从https://github.com/bsideup/jabel获得 。 如果您有Java 9+项目源,则可以将Jabel配置为编译过程的一部分。 它是一种注释处理器,它可以参与编译过程,并且可以使编译器接受各种技巧,从而可以接受Java 9+特性(因为Java 8具有这些特性)。编译器可以工作并生成Java 8,Jabel不会干扰字节码的生成。 ,因此这是真实的,因为它可以新鲜而热情地出现在Java编译器之外。 它仅指示编译器在编译代码时不要迷恋Java 9+功能。

该项目的GitHub页面上很好地说明了它的工作方式以及为什么可以工作。 我在上面写的内容可能甚至不够精确。

移植问题

使用针对Java 8 JVM的Java 9+功能创建Java代码时,我们不仅要关注字节码版本。 使用Java 8 JVM执行的代码将使用Java 8版本的JDK,如果我们碰巧使用了某些在那里不可用的类或方法,则该代码将无法运行。 因此,我们有两个任务:

  • 配置构建以使用Jabel生成Java 8字节码
  • 消除Java 8中不可用的JDK调用。

配置构建

在这里我不会描述如何使用Maven将Jabel配置为构建的一部分。 它记录在网站上,非常简单。

在Java :: Geci的情况下,我想要一些不同的东西。 我想要一个可用于创建Java 8和Java 11目标的Maven项目。 我之所以这样,是因为我希望Java :: Geci像以前一样支持JPMS,并且还为在Java 11或更高版本上运行的那些项目创建最新的字节码(例如,类嵌套而不是桥接方法)。

第一步,我创建了一个名为JVM8的配置文件。 Jabel仅配置为仅在此配置文件处于活动状态时运行。

此配置文件还将发布设置为

 <release>8< /release > 

因此,当编译器第一次看到module-info.java文件时,吓坏了。 幸运的是,我可以排除JVM8概要文件中POM文件中的文件。 我还排除了javax0/geci/log/LoggerJDK9.java ,稍后再讨论。

我还尝试使用Maven自动将版本号配置为具有-JVM8后缀(如果它与JVM8配置文件一起运行),但无法实现。 Maven是一种多功能工具,可以完成许多事情,如果是一个简单的项目,则应该这样做。 对于Java :: Geci,我无法执行此操作,因为Java:Geci是一个多模块项目。

多模块项目相互引用。 至少子模块引用父模块。 子模块的版本可能与父模块的版本不同。 这是合乎逻辑的,因为它们的演变和发展并不一定要结合在一起。 但是,通常是这样。 在像Java :: Geci这样的项目中,它具有七个子模块,每个子模块的版本号与父模块相同,子模块可以从父类继承所有参数,依赖项,编译器选项等,但版本除外。 它不能继承版本,因为它不知道从哪个父版本继承。 这是一个陷阱22。

Java :: Geci开发通过使用Jamal预处理程序来维护八个pom.xml文件来解决此问题。 每当构建配置发生更改时,都必须在pom.xml.jam文件之一或包含的*.jim文件之一中对其进行编辑,然后命令行mvn -f genpom.xml clean将重新生成所有新的pom.xml文件。 这也节省了一些重复的代码,因为预处理的Jamal文件不如相应的XML文件那么冗长。 这样做的代价是必须维护使用的宏。

Java :: Geci有一个version.jim文件,其中包含项目的版本作为宏。 以Java 8发行版为目标时,必须将此文件中的版本更改为xyz-JVM8并且必须执行命令mvn -f genpom.xml clean 。 不幸的是,这是我可能忘记的手动步骤。 创建Java 8目标后,我可能也忘记删除-JVM8后缀。

为了减轻这种人为错误的风险,我开发了一个单元测试,以检查版本号与编译配置文件是否一致。 它标识了读取/javax0/geci/compilation.properties文件的编译配置文件。 这是项目中由Maven过滤的资源文件,包含

 projectVersion=${project.version}  profile=${profile} 

测试运行时,属性将替换为项目中定义的实际值。 project.version是项目版本。 属性profile在两个配置文件(默认和JVM8 )中定义为该配置文件的名称。

如果版本和配置文件不匹配,则测试失败。 遵循Java :: Geci的理念,当测试本身也可以修复错误时,测试不仅命令程序员修复“错误”。 它将修改version.jim文件,使其包含正确的版本。 但是,它不会运行生成Jamal宏的pom文件。

因此,在第二次构建之后,我将通过一些手动编辑工作获得xyz版本以及xyz-JVM8版本的发行文件。

消除Java 8+ JDK调用

简单的通话

乍看之下,这是一项简单的任务。 您不得使用Java 8 JDK中没有的方法。 我们可以使用Java 8进行任何操作,因此这绝对是可能的任务。

例如每个

 " " .repeat(tab) 

必须消除。 为此,我创建了一个包含静态方法的类JVM8Tools 。 例如:

 public static String space( int n){ 
     final StringBuilder sb = new StringBuilder( /*20 spaces*/ "                   " ); 
     while ( sb.length() < n){ 
         sb.append(sb); 
     } 
     return sb.substring( 0 ,n).toString();  } 

被定义在那里,使用这种方法我可以写

 space(tab) 

而不是调用String::repeat方法。 这部分很简单。

模仿

更加困难的是实现getNestHost()方法。 Java 8中没有这样的东西,但是Java :: Geci的工具模块中包含的选择器表达式使您可以使用表达式,例如

 Selector.compile( "nestHost -> (!null & simpleName ~ /^Map/)" ).match(Map.Entry. class ) 

检查是否在Map内声明了Entry类,这很简单。 即使在有人选择这样做的Java 8环境中使用此表达式也是有意义的,但我不想执行截肢手术以从Java :: Geci删除此功能。 它必须被实施。

该实现检查实际的运行时,并且如果该方法在JDK中存在,则它通过反射进行调用。 在其他情况下,它使用类的名称并尝试查找分隔内部类名称和封闭类名称的$字符来模仿功能。 当使用不同的类加载器加载同一类结构的多个实例时,在极少数情况下,这可能导致错误的结果。 我认为像Java :: Geci这样的工具可以使用它,在执行单元测试时几乎不会发生这种情况。

Class#getNestHost反射方式调用Class#getNestHost方法还有一个速度缺陷。 如果有真正的需求,我决定修复它。

记录支持

最后一个问题是日志记录。 Java 9引入了一个日志外观,强烈建议库使用它。 在Java环境中,日志记录是一个长期存在的问题。 问题不在于没有任何东西。 恰恰相反。 太多了 有Apache Commons Logging,Log4j,Logback和JDK内置的Java util日志记录。 一个独立的应用程序可以选择它使用的日志记录框架,但是如果一个库使用一个不同的库,那么即使不是不可能,也很难将不同的日志消息集中到同一流中。

因此,Java 9引入了一个新的Facade,库可以使用它发送日志,应用程序可以将输出通过Facade传递到所需的任何日志框架。 Java :: Geci使用此外观,并通过它为生成器提供日志记录API。 如果是JVM8环境,则不可能。 在这种情况下,Java :: Geci将日志消息传递到标准Java记录器中。 要做到这一点有一个新的界面LoggerJDK由两个类实现LoggerJVM8LoggerJDK9 。 如果目标是Java 8,则后者的源代码将从编译中排除。

实际的记录器尝试通过反射获取javax0.geci.log.LoggerJDK9#factory 。 如果存在,则可以使用Java 9日志记录。 如果不存在,则记录器将返回工厂到javax0.geci.log.LoggerJVM8#factory 。 这样,通过反射仅调用记录器工厂,每个反射器仅发生一次。 日志记录本身已简化,并使用目标日志记录而没有任何反射,因此不会影响速度。

带走

可以在大多数库项目中支持Java 8,而不会做出无法接受的妥协。 我们可以从同一源创建两个不同的二进制文件,这些二进制文件支持两个不同的版本,而支持Java 9及更高版本的版本不会“受苦”旧的字节码。 有一些妥协。 您必须避免调用Java 9+ API,并且在绝对需要的情况下,您可以提供一个后备解决方案,并且可以提供基于反射的运行时检测解决方案。

翻译自: https://www.javacodegeeks.com/2019/11/supporting-java-8.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值