Java编程的动态性,第7部分: 用BCEL设计字节码

Apache Byte Code Engineering Library (BCEL)可以深入 Java 类的字节码。可以用它 转换现有的类表示或者构建新的类,因为 BCEL 在单独的 JVM 指令级别上进行操作,所以可 以让您对代码有最强大的控制。不过,这种能力的代价是复杂性。在本文中,Java 顾问 Dennis Sosnoski 介绍了 BCEL 的基本内容,并引导读者完成一个示例 BCEL 应用程序,这 样您就可以自己决定是否值得以这种复杂性来换取这种能力。

在本系列的最后三篇文章中,我展示了如何用 Javassist 框架操作类。这次我将用一种很 不同的方法操纵字节码——使用 Apache Byte Code Engineering Library (BCEL)。与 Javassist 所支持的源代码接口不同,BCEL 在实际的 JVM 指令层次上进行操作。在希望对 程序执行的每一步进行控制时,底层方法使 BCEL 很有用,但是当两者都可以胜任时,它也 使 BCEL 的使用比 Javassist 要复杂得多。

我将首先讨论 BCEL 基本体系结构,然后本文的大部分内容将讨论用 BCEL 重新构建我的 第一个 Javassist 类操作的例子。最后简要介绍 BCEL 包中提供的一些工具和开发人员用 BCEL 构建的一些应用程序。

BCEL 类访问

BCEL 使您能够同样具备 Javassist 提供的分析、编辑和创建 Java 二进制类的所有基本 能力。BCEL 的一个明显区别是每项内容都设计为在 JVM 汇编语言的级别、而不是 Javassist 所提供的源代码接口上工作。除了表面上的差别,还有一些更深层的区别,包括 在 BCEL 中组件的两个不同层次结构的使用——一个用于检查现有的代码,另一个用于创建 新代码。我假定读者已经通过本系列前面的文章熟悉了 Javassist(请参阅侧栏 不要错过本 系列的其余部分)。因此我将主要介绍在开始使用 BCEL 时,可能会让您感到迷惑的那些不 同之处。

与 Javassist 一样, BCEL 在类分析方面的功能基本上与 Java 平台通过 Relfection API 直接提供的功能是重复的。这种重复对于类操作工具箱来说是必要的,因为一般不希望 在所要操作的类被修改 之前就装载它们。

BCEL 在 org.apache.bcel 包中提供了一些基本常量定义,但是除了这些定义,所有分析 相关的代码都在 org.apache.bcel.classfile 包中。这个包中的起点是 JavaClass 类。这 个类在用 BCEL 访问类信息时起的作用与使用常规 Java 反射时, java.lang.Class 的作用 一样。 JavaClass 定义了得到这个类的字段和方法信息,以及关于父类和接口的结构信息的 方法。 与 java.lang.Class 不同,JavaClass 还提供了对类的内部信息的访问,包括常量 池和属性,以及作为字节流的完整二进制类表示。

JavaClass 实例通常是通过解析实际的二进制类创建的。BCEL 提供了 org.apache.bcel.Repository 类用于处理解析。在默认情况下,BCEL 解析并缓冲在 JVM 类 路径中找到的类表示,从 org.apache.bcel.util.Repository 实例中得到实际的二进制类表 示(注意包名的不同)。 org.apache.bcel.util.Repository 实际上是二进制类表示的源代 码的接口。在默认源代码中使用类路径的地方,可以用查询类文件的其他路径或者其他访问 类信息的方法替换。

改变类

除了对类组件的反射形式的访问, org.apache.bcel.classfile.JavaClass 还提供了改 变类的方法。可以用这些方法将任何组件设置为新值。不过一般不直接使用它们,因为包中 的其他类不以任何合理的方式支持构建新版本的组件。相反,在 org.apache.bcel.generic 包中有完全单独的一组类,它提供了 org.apache.bcel.classfile 类所表示的同一组件的可 编辑版本。

就 像 org.apache.bcel.classfile.JavaClass 是使用 BCEL 分析现有类的起点一样, org.apache.bcel.generic.ClassGen 是创建新类的起点。它还用于修改现有的类——为了处 理这种情况,有一个以 JavaClass 实例为参数的构造函数,并用它初始化 ClassGen 类信息 。修改了类以后,可以通过调用一个返回 JavaClass 的方法从 ClassGen 实例得到可使用的 类表示,它又可以转换为一个二进制类表示。

听起来有些乱?我想是的。事实上,在两个包之间来回转是使用 BCEL 的一个最主要的缺 点。重复的类结构总有些碍手碍脚,所以如果频繁使用 BCEL,那么可能需要编写一个包装器 类,它可以隐藏其中一些不同之处。在本文中,我将主要使用 org.apache.bcel.generic 包 类,并避免使用包装器。不过在您自己进行开发时要记住这一点。

除了 ClassGen , org.apache.bcel.generic 包还定义了管理不同类组件的结构的类。 这些结构类包括用于处理常量池的 ConstantPoolGen 、用于字段和方法的 FieldGen 和 MethodGen 和处理一系列 JVM 指令的 InstructionList 。最后, org.apache.bcel.generic 包还定义了表示每一种类型的 JVM 指令的类。可以直接创建这些 类的实例,或者在某些情况下使用 org.apache.bcel.generic.InstructionFactory helper 类。使用 InstructionFactory 的好处是它处理了许多指令构建的簿记细节(包括根据指令 的需要在常量池中添加项)。在下面一节您将会看到如何使所有这些类协同工作。

用 BCEL 进行类操作

作为使用 BCEl 的一个例子,我将使用 第 4 部分中的一个 Javassist 例子——测量执 行一个方法的时间。我甚至采用了与使用 Javassist 时的相同方式:用一个改过的名字创建 要计时的原方法的一个副本,然后,通过调用改名后的方法,利用包装了时间计算的代码来 替换原方法的主体。

时间: 2024-10-25 10:16:06

Java编程的动态性,第7部分: 用BCEL设计字节码的相关文章

Java编程的动态性,第8部分: 用代码生成取代反射

从本系列前面的文章中,您了解到反射的性能比直接访问要慢许多倍,并了解了用 Javassist 和 Apache Byte Code Engineering Library (BCEL)进行classworking.Java 顾问 Dennis Sosnoski 通过演示如何使用运行时 classworking,来用全速前进的生成代码 取代反射代码,从而结束他的 Java 编程的动态性系列. 既然您已经看到了如何使用 Javassist 和 BCEL 框架来进行 classworking ,我将展

Java编程的动态性,第5部分: 动态转换类

在经过一段时间的休息之后,Dennis Sosnoski 又回来推出了他的 Java 编程的动态性系 列的第 5 部分.您已在前面的文章中看到了如何编写用于转换 Java 类文件以改变代码行为 的程序.在本期中,Dennis将展示如何使用 Javassist 框架,把转换与实际的类加载过程结 合起来,用以进行灵活的"即时"面向方面的特性处理.这种方法允许您决定想要在运行时 改变的内容,并潜地在每次运行程序时做出不同的修改.在整个过程中,您还将更深入地了 解向JVM 中加载类的一般问题.

Java编程的动态性, 第4部分: 用Javassist进行类转换

厌倦了只能按编写好源代码的方式执行的 Java 类了吗?那么打起精神吧,因为您就要发 现如何将编译器编译好的类进行改造的方法了!在本文中,Java 顾问 Dennis Sosnoski 通 过介绍字节码操作库 Javassist 将他的 Java 编程的动态性系列带入高潮,Javassist 是广 泛使用的 JBoss 应用服务器中加入的面向方面的编程功能的基础.您会看到到用 Javassist 转换现有类的基本内容,并且了解到这种用框架源代码处理类的方法的威力和局限性. 讲过了 Java 类格

Java编程的动态性,第2部分: 引入反射

在" Java编程的动态性,第1部分,"我为您介绍了Java编程类和类装入.该篇文章介绍 了一些Java二进制类格式的相关信息.这个月我将阐述使用Java反射API来在运行时接入和使 用一些相同信息的基础.为了使已经熟知反射基础的开发人员关注本文,我将在文章中包括 反射性能如何与直接接入相比较. 使用反射不同于常规的Java编程,其中它与 元数据--描述其它数据的数据协作.Java语 言反射接入的特殊类型的原数据是JVM中类和对象的描述.反射使您能够运行时接入广泛的类 信息.它甚至使您

Java编程的动态性,第1部分: 类和类装入

本文是这个新系列文章的第一篇,该系列文章将讨论我称之为 Java 编程的动态性的一系 列主题.这些主题的范围从 Java 二进制类文件格式的基本结构,以及使用反射进行运行时 元数据访问,一直到在运行时修改和构造新类.贯穿整篇文章的公共线索是这样一种思想: 在 Java 平台上编程要比使用直接编译成本机代码的语言更具动态性.如果您理解了这些动 态方面,就可以使用 Java 编程完成那些在任何其它主流编程语言中不能完成的事情. 本文中,我将讨论一些基本概念,它们是这些 Java 平台动态特性的基础.

Java编程的动态性,第6部分: 利用Javassist进行面向方面的更改

Java 顾问 Dennis Sosnoski 在他的关于 Javassist 框架的三期文章中将精华部分留在 了最后.这次他展现了 Javassist 对搜索-替换的支持是如何使对 Java 字节码的编辑变得 像文本编辑器的"替换所有(Replace All )"命令一样容易的.想报告所有写入特定字段 的内容或者对方法调用中参数的更改中的补丁吗?Javassist 使这变得很容易,Dennis 向您 展示了其做法. 本系列的 第 4 部分和 第 5 部分讨论了如何用 Javassis

Java编程的动态性,第3部分: 应用反射

命令行参数处理是一项令人厌烦的零碎工作,不管您过去已经处理过多少次了,它好像总 能重新摆在您的面前.与其一遍又一遍地编写同一块代码的不同变种,为什么不利用反射来 简化参数处理的工作呢?Java 顾问 Dennis Sosnoski 向您展示了如何做到这一点.在本文 中,Dennis 简明扼要地介绍了一个开源库,这个库可以使得命令行参数实际上自己处理自己 . 在 上个月的文章中,我介绍了Java Reflection API,并简要地讲述了它的一些基本功能 .我还仔细研究了反射的性能,并且在文章的

java中的字节码

这段时间一直在看java,看到讲 Reflection,ClassLoader,javassist 的地方,很兴奋,因为以前用.net的时候接触过一点点Reflection,但我再看到ClassLoader的时候,简直觉得非常神奇,可是神奇的东西一般都很难懂,看了很多资料,都不太明白,直到在ibm的网站上看到<java编程的动态性>,很详细的阐述了ClassLoader的作用,接着我看了使用javassist修改字节码的地方,跃跃欲试,动手做了一个小小的例子,朋友说我误入歧途,刚刚接触java

Java Class字节码知识点回顾

把之前的笔记重新整理了一下,发上来供对java Class文件结构的有兴趣的同学参考一下,也算对以前知识的回顾. Java Class文件打破了C或者C++等语言所遵循的传统,用这些传统语言写的程序通常首先被编译,然后被连接成单独的.专门支持特定硬件平台和操作系统的二进制文件.通常情况下,一个平台上的二进制可执行文件不能在其他平台上工作. Java Class文件是可以运行在任何支持Java虚拟机的硬件平台和操作系统上的二进制文件,Class文件中包含了java虚拟机指令集和符号表以及若干其他辅