Retrotranslator:跨越JDK版本。

一般来说,以“做项目”为主的软件公司比较容易更新技术,在下一个项目中换一个技术框架、升级到最新的JDK版本,甚至把Java换成C#、C++来开发程序都是有可能的。但当公司发展壮大,技术有所积累,逐渐成为以“做产品”为主的软件公司后,自主选择技术的权力就会丧失掉,因为之前所积累的代码和技术都是用真金白银换来的,一个稳健的团队也不会随意的改变底层的技术。然而在飞速发展的程序设计领域,新技术总是日新月异、层出不穷,偏偏这些新技术又如鲜花之于蜜蜂一样,对程序员散发着天然的吸引力。
在Java世界里,每一次JDK大版本的发布,都伴随着一场大规模的技术革新,而对Java程序编写习惯改变最大的,无疑是JDK 1.5发布。自动装箱、反省、动态注解、枚举、变长参数、遍历循环(foreach循环)...事实上,在没有这些语法特性的年代,Java程序也照样能写,但是现在看来,上述每一种语法的改进几乎都是“必不可少”的。就如同习惯了24寸液晶显示器的程序员,很难习惯在15寸纯平显示器上编写代码。但假如“不幸”因为要保护现有投资、维持程序结构稳定等,必须使用1.5以前版本的JDK呢?我们没有办法把15寸显示器变成24寸的,但却可以跨越JDK版本之间的沟壑,把JDK 1.5中编写的代码放到JDK 1.4或1.3的环境中去部署使用。为了解决这个问题,一种名为“Java逆向移植”的工具(Java Backporting Tools)应运而生,Retrotranslator是这类工具中较为出色的一个。
Retrotranslator的作用是将JDK 1.5编译出来的Class文件转变为在JDK 1.4huo1.3上部署的版本,他可以很好的支持自动装箱、泛型、动态注解、枚举、变长参数、遍历循环、静态导入这些语法特性,甚至还可以支持JDK 1.5中新增的集合改进、并发包以及对泛型、注解等的反射操作。了解了Retrotranslator这种逆向移植工具可以做什么以后,现在关心的是他是怎么做到的?
要想知道Retrotranslator如何在旧版本JDK中模拟新版本JDK的功能,首先要弄清楚JDK升级中会提供哪些新的功能。JDK每次升级新增的功能大致可以分为以下4类:

  • 在编译器层面做的改进。如自动装箱拆箱,实际上就是编译器在程序中使用到包装对象的地方自动插入了很多Integer.valueOf()、Float.valueOf()之类的代码;变长参数在编译之后就自动转化成了一个数组来完成参数传递;泛型的信息则在编译阶段就已经拆除掉了(但是在元数据中还保留着),相应的地方被编译器自动插入了类型转换代码。
  • 对Java API的代码增强。譬如JDK 1.2试代引入的java.util.Collections等一系列集合类,在JDK 1.5试代引入的java.util.concurrent并发包等。
  • 需要在字节码中进行支持的改动。如JDK 1.7里面新加入的语法特性:动态语言支持,就需要在虚拟机中新增一条invokedynamic字节码指令来实现相关的调用功能。不过字节码指令集一直处于相对比较稳定的状态,这种需要在节码层面直接进行的改动是比较少见的。
  • 虚拟机内部的改进。如JDK 1.5中实现的JSR-133规范重新定义的Java内存模型(Java Memory Model,JMM)、CMS收集器之类的改动,这类改动对于程序员编写代码基本是透明的,但会对程序运行时产生影响。

上述4类新功能中,Retrotranslator只能模拟前两类,对于后面两类直接在虚拟机内部实现的改进,一般所有的逆向移植工具都是无能为力的,至少不能完整的或者在可接受的效率上完成全部模拟,否则虚拟机设计团队也没有必要舍近求远的改动处于JDK底层的虚拟机。
在可以模拟的两类功能中,第二类模拟相对更容易实现一些,如JDK 1.5引入的java.util.concurrent包,实际是由多线程大师Doug Lea开发的一套并发包,在JDK 1.5出现之前就已经存在,所以要在旧的JDK中支持这部分功能,以独立类库的方式便可四号线。Retrotranslator中附带了一个名为“backport-util-concurrent.jar”的类库(由另一个名为“Backport JSR 166”的项目tigong )来代替JDK 1.5的并发包.

至于JDK在编译阶段进行处理的那些改进,Retrotranslator则是使用ASM框架直接对字节码进行处理。由于组成Class文件的字节码指令数量并没有改变,所以无论是JDK 1.3、JDK 1.4还是JDK 1.5,能用字节码表达的语义范围应该是一致的。当然,肯定不可能简单地把Class的文件版本号从49.0改回48.0就能解决问题了,虽然字节码指令的数量没有变化,但是元数据信息和一些语法支持的内容还是要做相应的修改。以枚举为例,在JDK 1.5中增加了enum关键字,但是Class文件常量池的CONSTANT_Class_info类型常量并没有发生任何语义变化,仍然是代表一个类或接口的符号引用,没有加入枚举,也没有增加“CONSTANT_Enum_info”之类的“枚举符号引用”常量。所以使用enum关键字定义常量,虽然从Java语法上看起来与使用class关键字定义类、使用interface关键字定义接口是同一层次的,但实际上这是由Javac编译器做出来的假象,从字节码的角度来看,枚举仅仅是一个继承于java.lang.Enum、自动生成了values()和valueOf()方法的普通Java类而已。
Retrotranslator对枚举所做的主要处理就是把枚举类的父类从“java.lang.Enum”替换为他运行时类库中包含的“net.sf.retrotranslator.runtime.java.lang.Enum_”,然后再在类和字段的访问标志中抹去ACC_ENUM标志位。当然,这只是处理的总体思路,具体的实现要比上面说的复杂得多。可以想想既然两个父类实现都不一样,values()和valueOf()的方法自然需要重写,常量池需要引入大量新的来自父类的符号引用,这些都是实现细节。下图是一个使用JDK 1.5编译的枚举类与被Retrotranslator转换处理后的字节码的对比图。


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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值