From: http://qa.taobao.com/?p=6266

最近因为工作的需要,要对字节码进行操作,于是利用了一些业务时间研究一下JAVA中如何来操作字节码(ByteCode). 这篇文章,将介绍与操作字节码有关的基本知识和操作字节码的方法及Demo。

为节省篇幅,这里就不介绍JAVA字节码,大家可以自己了解下。

谈到操作字节码,不能不谈到AOP(Aspect Oriented Programming),下面来简单介绍一下:

AOP简介

AOP是OOP的延续,是Aspect Oriented Programming的缩写,意思是面向方面编程。AOP实际是GoF设计模式的延续,设计模式孜孜不倦追求的是调用者和被调用者之间的解耦,AOP可以说也是这种目标的一种实现。

AOP的一个典型应用就是J2EE。J2EE应用系统只有部署在J2EE容器中才能运行,那么为什么划分为J2EE容器和J2EE应用系统? 通过对J2EE容器运行机制的分析,可以发现:实际上J2EE容器分离了一般应用系统的一些通用功能,例如事务机制、安全机制以及对象池或线程池等性能优化机制。

这些功能机制是每个应用系统几乎都需要的,因此可以从具体应用系统中分离出来,形成一个通用的框架平台,而且,这些功能机制的设计开发有一定难度,同时运行的稳定性和快速性都非常重要,必须经过长时间调试和运行经验积累而成,因此,形成了专门的J2EE容器服务器产品,如Tomcat JBoss。

简单了解AOP后,再来了解一下AOP底层技术: 

AOP (Aspect Oriented Programming)底层技术比较  

AOP 底层技术功能性能面向接口编程编程难度
直接改写 class 文件完全控制类无明显性能代价不要求高,要求对 class 文件结构和 Java 字节码有深刻了解
JDK Instrument完全控制类无论是否改写,每个类装入时都要执行hook程序不要求高,要求对 class 文件结构和 Java 字节码有深刻了解
JDK Proxy只能改写 method反射引入性能代价要求
操作字节码的framework (如ASM)几乎能完全控制类无明显性能代价不要求中,能操纵需要改写部分的 Java 字节码

 从上面的图表中分析可以看到,对于一般的操作字节码要求(实际上是能够满足笔者100%的要求),综合考虑功能,性能,可用性,易用性,使用字节码框架来操作字节码是最佳的选择。

下面来了解一下都有哪些开源操作字节码的框架:

l  Javassist

l  cglib

l  SERP

l  Package gnu.bytecode

l  Cojen

l  Jdec

l  BCEL

l  ObjectWeb ASM

l  JClassLib

l  Trove Class File API

l  Jiapi

l  Classfile Reader & Writer

l  JBET

l  Retroweaver

l  Jen

l  Soot

这里重点介绍一下ASM,因为下面将使用ASM框架进行字节码修改。

ASM这个Java 字节码操控框架能被用来动态生成类或者增强既有类的功能。ASM 可以直接产生二进制 class 文件,也可以在类被加载入 Java 虚拟机之前动态改变类行为。Java class 被存储在严格格式定义的 .class 文件里,这些类文件拥有足够的元数据来解析类中的所有元素:类名称、方法、属性以及 Java 字节码(指令)。ASM 从类文件中读入信息后,能够改变类行为,分析类信息,甚至能够根据用户要求生成新类。下图对当前接触常用的操作字节码框架进行了一个比较

Bytecode manipulation framework  

名字Size性能备注
ASM33k60% 
BCEL350k700% 
SERP150k1100% 

ASM的几个特性:

1. JAVA Based.

ASM是基于JAVA的,即用JAVA实现的。

2. Visitor模式.

对于 ASM 来说,Java class 被描述为一棵树;使用 “Visitor” 模式遍历整个二进制结构。

3. 复杂性低. 易学易用.

ASM 提供了更为现代的编程模型,降低了操作字节码的复杂性,使用事件驱动的处理方式使得用户只需要关注于对其编程有意义的部分,而不必了解 Java 类文件格式的所有细节:ASM 框架提供了默认的 “response taker”处理这一切。

4. 较高的性能

对字节码进行操作的同时尽量减小的性能的损失(性能的损失是不可避免)。

这里来介绍一下ASM组成及顺序图:

ASM Infrastructure

 

l  Core package提供了一个读写、修改Java bytecode的API,并且为其它的package定义了依据。这个package对于生成Java bytecode、实现大多数的bytecode变换而言意义重大。

l  Tree package提供了Java bytecode的内存表示法。

l  Analysis package提供了基本的数据流分析和类型检查算法,它们将用于在tree oackage中存储Java方法bytecode。

l  Commons package(包含在ASM2.0中)提供了一些常用的bytecode转换和用于简化bytecode生成的适配器。

l  Util package包含了一些帮助类和简单的bytecode验证器,它们将有助于开发或者测试。

l  XML package提供了一个用于在bytecode和XML之间进行转换的适配器,和一些允许使用XSLT定义bytecode转换的兼容SAX的适配器。

顺序图:

 

Demo

这里我们来实现这样一个功能:在不能改变原代码功能的前提下,对于一个特定类的特定方法有没有被测试过,以HelloTaobao类中方法helloHeyun为例。

类HelloTaobao:

public class HelloTaobao

{

    public void helloHeyun()

    {  

    System.out.println(“Hello, This is Heyun’s investigation about code coverage!”);  

    }

}

主方法类:

public class Main

{

    public static void main(String[] args)

    {

       HelloTaobao ht = new HelloTaobao();

       ht.heyunHeyun();

    }

}

到这里,我们运行一下程序,会在Console输出字符串:“Hello, This is Heyun’s investigation about code coverage!”

下面我们来操作一下字节码文件HelloTaobao.class:

1. 想操作字节码的某一方法,需要继承ASM中的ClassAdapter和MethodAdapter

2. 定义类Generator来读入字节码文件HellTaobao,改造字节码文件,生成改造后的同名字节码文件

。。。。。。。。。。。。。。。。。。。。。