当前位置:网站首页 > Java教程 > 正文

java动态编译教程



笔者很久之前就有个想法:参考现有的主流框架的设计,造一个轮子,在基本不改变使用体验的前提下把框架依赖的大量的反射设计去掉,这些反射构筑的组件使用动态编译加载的实例去替代,从而可以得到接近于直接使用原生的性能。于是带着这样的想法,深入学习的动态编译。编写本文的时候使用的是。

下面这个很眼熟的图来源于《深入理解Java虚拟机》前端编译与优化的章节,主要描述编译的过程:

上图看起来只有三步,其实每一步都有大量的步骤,下图尝试相对详细地描述具体的步骤(图比较大难以分割,直接放原图):

实际上,仅仅对于编译这个过程来说,开发者或者使用者不必要完全掌握其中的细节,提供了一个工具包让使用者可以用简易的进行编译(其实在大多数请下,开发者是面向业务功能开发,像编译和打包这些细节一般直接由开发工具、、等工具完成):

具体的使用过程包括:

  • 获取一个实例。
  • 基于文件对象初始化一个编译任务实例。
  • 实例执行结果代表着编译过程的成功与否。

我们熟知的编译器其实就是接口的实现,在中,对应的实现类为。在中不存在接口,具体的编译入口类为。

因为里面的是基于隔离的,所以编译成功之后可以通过自定义的类加载器加载对应的类实例,然后就可以应用反射进行实例化和后续的调用。

动态编译的步骤在上一节已经清楚地说明,这里造一个简单的场景。假设存在一个接口如下:

 

我们可以通过字符串定义一个类:

 

在组装编译任务实例之前,还有几项工作需要完成:

  • 内置的标准实现是面向类源码文件,由于动态编译时候输入的是类源码文件的内容字符串,需要自行实现。
  • 内置的是面向类路径下的源码文件进行加载,这里也需要自行实现。
  • 需要自定义一个实例去加载编译出来的动态类。

自行实现一个,其实可以简单点直接继承,覆盖需要用到的方法即可:

 

如果编译成功之后,直接通过自行添加的方法即可获取目标类编译后的字节码对应的字节数组(二进制内容)。这里的预留了多个构造函数用于兼容原有的编译方式。

只要简单继承即可,关键是要覆盖原来的方法,用于搜索自定义的实例,从而提取对应的字节码字节数组进行装载,为了实现这一点可以添加一个哈希表作为缓存,键-值分别是全类名的别名(形式,而非模式)和目标类对应的实例。

 

是文件的抽象管理器,它用于管理常规的文件,但是不局限于文件,也可以管理其他来源的类文件数据。下面就通过实现一个自定义的用于管理字符串类型的源代码。为了简单起见,可以直接继承已经存在的:

 

注意在这个类中引入了自定义类加载器,目的是为了实现实例的共享以及为文件管理器提供类加载器实例。

前置准备工作完成,我们可以通过去编译这个前面提到的字符串,为了字节码的兼容性更好,编译的时候可以指定稍低的版本例如:

 

输出结果如下:

 

可见通过了字符串的类源码,实现了动态编译、类加载、反射实例化以及最终的方法调用。另外,编译过程的诊断信息可以通过实例获取。为了复用,这里可以把动态编译的过程抽取到一个方法中:

 

既然有的动态编译,为什么还存在这样的字节码增强工具?撇开性能或者效率层面,动态编译存在比较大的局限性,比较明显的一点就是无法完成字节码插桩,换言之就是无法基于原有的类和方法进行修饰或者增强,但是可以做到。再者,提供的和反射的十分相近,如果反射平时用得比较熟练,的上手也就变得比较简单。这里仅仅列举一个增强前面提到的的例子,先引入依赖:

 

编码如下:

 

输出结果如下:

 

这个单词其实是和两个单词拼接在一起,意为助手,是一个字节码增强类库:

  • 可以基于已经存在的类进行字节码增强,例如修改已经存在的方法、变量,甚至是直接在原有的类中添加新的方法等。
  • 可以完全像积木拼接一样,动态拼出一个全新的类。

不像(的学习曲线比较陡峭,属于相对底层的字节码操作类库,当然从性能上来看对字节码增强的效率远高于其他高层次封装的框架)那样需要对字节码编程十分了解,降低了字节码增强功能的入门难度。

现在定义一个接口,用于动态执行一条已知的,很简单,就是查询的系统表里面的用户信息:

 

假设现在只提供一个的驱动包(),暂时不能依赖任何高层次的框架,要动态实现接口,优先整理需要的组件:

  • 需要一个连接管理器去管理的连接。
  • 需要一个执行器用于执行查询。
  • 需要一个结果处理器去提取和转换查询结果。

为了简单起见,笔者在定义这三个组件接口的时候顺便在接口中通过单例进行实现(部分配置完全写死):

 

接着需要动态编译的实现类,它的源文件的字符串内容如下(注意不要在类路径下新建这个类):

 

然后编写一个客户端进行动态编译和执行:

 

最终的输出结果是:

 

然后笔者查看本地安装的中的结果,验证该查询结果是正确的。

这里笔者为了简化整个例子,没有在方法中添加查询参数,可以尝试一下查询的是场景下的编码实现。

动态编译或者更底层的面向字节码层面的编程,其实是一个十分有挑战性但是可以创造无限可能的领域,本文只是简单分析了一下源码编译的过程,并且通过一些简单的例子进行动态编译的模拟,离使用于实际应用中还有不少距离,后面需要花更多的时间去分析一下相关领域的知识。

参考资料:

  • 部分源码
  • 《深入理解Java虚拟机 - 3rd》
  • Javassist

(本文完 c-4-d e-a- 0:23)

技术公众号(《Throwable文摘》),不定期推送笔者原创技术文章(绝不抄袭或者转载):

  • 上一篇: java脚印教程
  • 下一篇: Java量化教程
  • 版权声明


    相关文章:

  • java脚印教程2025-01-23 17:02:06
  • 研究java教程2025-01-23 17:02:06
  • react java 教程2025-01-23 17:02:06
  • 赢:java教程2025-01-23 17:02:06
  • java19.0.1安装教程2025-01-23 17:02:06
  • Java量化教程2025-01-23 17:02:06
  • java反汇编教程2025-01-23 17:02:06
  • java案例教程pdf2025-01-23 17:02:06
  • java睡眠视频教程2025-01-23 17:02:06
  • java前段教程2025-01-23 17:02:06