你知道如何操作JAVA字节码文件吗,这里将介绍与操作Java字节码有关的基本知识和操作Java字节码的方法及Demo,首先我们来看一下AOP的概念,AOP是OOP的延续,是AspectOrientedProgramming的缩写,意思是面向方面编程。
如何操作JAVA字节码文件
本文将介绍与操作Java字节码有关的基本知识和操作Java字节码的方法及Demo,谈到操作Java字节码,不能不谈到AOP(AspectOrientedProgramming),下面来简单介绍一下:
AOP简介
AOP是OOP的延续,是AspectOrientedProgramming的缩写,意思是面向方面编程。AOP实际是GoF设计模式的延续,设计模式孜孜不倦追求的是调用者和被调用者之间的解耦,AOP可以说也是这种目标的一种实现。
AOP的一个典型应用就是J2EE。J2EE应用系统只有部署在J2EE容器中才能运行,那么为什么划分为J2EE容器和J2EE应用系统?通过对 J2EE容器运行机制的分析,可以发现:实际上J2EE容器分离了一般应用系统的一些通用功能,例如事务机制、安全机制以及对象池或线程池等性能优化机 制。
这些功能机制是每个应用系统几乎都需要的,因此可以从具体应用系统中分离出来,形成一个通用的框架平台,而且,这些功能机制的设计 开发有一定难度,同时运行的稳定性和快速性都非常重要,必须经过长时间调试和运行经验积累而成,因此,形成了专门的J2EE容器服务器产品,如 TomcatJBoss。
简单了解AOP后,再来了解一下AOP底层技术:
AOP(AspectOrientedProgramming)底层技术比较
从上面的图表中分析可以看到,对于一般的操作Java字节码要求(实际上是能够满足笔者100%的要求),综合考虑功能,性能,可用性,易用性,使用Java字节码框架来操作Java字节码是最佳的选择。
下面来了解一下都有哪些开源操作JavaJava字节码的框架:
Javassist;
cglib;
SERP;
Packagegnu.bytecode;
Cojen;
Jdec;
BCEL;
ObjectWebASM;
JClassLib;
TroveClassFileAPI;
Jiapi;
ClassfileReader&Writer;
JBET;
Retroweaver;
Jen;
Soot
这里重点介绍一下ASM,因为下面将使用ASM框架进行Java字节码修改。
ASM这个Java字节码操控框架能被用来动态生成类或者增强既有类的功能。ASM可以直接产生二进制class文件,也可以在类被加载入 Java虚拟机之前动态改变类行为。Javaclass被存储在严格格式定义的.class文件里,这些类文件拥有足够的元数据来解析类中的所有元素:类 名称、方法、属性以及Java字节码(指令)。ASM从类文件中读入信息后,能够改变类行为,分析类信息,甚至能够根据用户要求生成新类。下图对当前接触 常用的操作Java字节码框架进行了一个比较:
ASM的几个特性:
1.JAVABased.
ASM是基于JAVA的,即用JAVA实现的。
2.Visitor模式.
对于ASM来说,Javaclass被描述为一棵树;使用“Visitor”模式遍历整个二进制结构。
3.复杂性低.易学易用.
ASM提供了更为现代的编程模型,降低了操作Java字节码的复杂性,使用事件驱动的处理方式使得用户只需要关注于对其编程有意义的部分,而不必了解Java类文件格式的所有细节:ASM框架提供了默认的“responsetaker”处理这一切。
4.较高的性能
对Java字节码进行操作的同时尽量减小的性能的损失(性能的损失是不可避免)。
这里来介绍一下ASM组成及顺序图:
Corepackage提供了一个读写、修改Javabytecode的API,并且为其它的package定义了依据。这个package对于生成Javabytecode、实现大多数的bytecode变换而言意义重大。
Treepackage提供了Javabytecode的内存表示法。
Analysispackage提供了基本的数据流分析和类型检查算法,它们将用于在treeoackage中存储Java方法bytecode。
Commonspackage(包含在ASM2.0中)提供了一些常用的bytecode转换和用于简化bytecode生成的适配器。
Utilpackage包含了一些帮助类和简单的bytecode验证器,它们将有助于开发或者测试。
XMLpackage提供了一个用于在bytecode和XML之间进行转换的适配器,和一些允许使用XSLT定义bytecode转换的兼容SAX的适配器。
顺序图:
***************************************************************************************
Demo
这里我们来实现这样一个功能:在不能改变原代码功能的前提下,对于一个特定类的特定方法有没有被测试过,以HelloTaobao类中方法helloHeyun为例。
类HelloTaobao:
- publicclassHelloTaobao
- {
- publicvoidhelloHeyun()
- {
- System.out.println(“Hello,ThisisHeyun’sinvestigationaboutcodecoverage!”);
- }
- }
主方法类:
- publicclassMain
- {
- publicstaticvoidmain(String[]args)
- {
- HelloTaobaoht=newHelloTaobao();
- ht.heyunHeyun();
- }
- }
到这里,我们运行一下程序,会在Console输出字符串:“Hello,ThisisHeyun’sinvestigationaboutcodecoverage!”。
下面我们来操作一下Java字节码文件HelloTaobao.class:
1.想操作Java字节码的某一方法,需要继承ASM中的ClassAdapter和MethodAdapter
2.定义类Generator来读入Java字节码文件HellTaobao,改造Java字节码文件,生成改造后的同名Java字节码文件HellTaobao,代码如下:
publicclassGenerator
{
publicstaticvoidmain(String[]args)throwsException
{
ClassReadercr=newClassReader(“HellTaobao”);
ClassWritercw=newClassWriter(ClassWriter.COMPUTE_MAXS);
ClassAdapterclassAdapter=newByteCodeClassHandler(cw);
cr.accept(classAdapter,ClassReader.SKIP_DEBUG);
byte[]data=cw.toByteArray();
Filefile=newFile(“HellTaobao.class”);
FileOutputStreamfout=newFileOutputStream(file);
fout.write(data);
fout.close();
}
}
3.ByteCodeClassHandler(自定义)类继承ClassAdapter(fromASM)
4.ByteCodeClassHandler类中重写visitMethod,这个方法里去判断如果Java字节码文件HelloTaobao.class包含方法helloHeyun就调用ByteCodeMethodHandler类
publicclassByteCodeClassHandlerextendsClassAdapter
{
publicByteCodeClassHandler(ClassVisitorcv)
{
super(cv);
}
publicvoidvisit(intversion,intaccess,Stringname,Stringsignature,
StringsuperName,String[]interfaces)
{
super.visit(version,access,name,signature,superName,interfaces);
}
publicvoidvisitSource(Stringsource,Stringdebug)
{
super.visitSource(source,debug);
}
publicvoidvisitEnd()
{
}
@Override
publicMethodVisitorvisitMethod(intaccess,Stringname,Stringdesc,
Stringsignature,String[]exceptions)
{
MethodVisitormv=cv.visitMethod(access,name,desc,signature,
exceptions);
MethodVisitorwrappedMv=mv;
if(mv!=null)
{
//对于”helloHeyun”方法进行改造
if(name.equals(“helloHeyun”))
{
//使用自定义MethodVisitor,改写方法内容
wrappedMv=newByteCodeMethodHandler(mv);
}
}
returnwrappedMv;
}
}
5.ByteCodeMethodHandler(自定义)继承MethodAdapter(fromASM),这里来做改造想要调用的自定义 方法,这里将调用类ControlByteCode(自定义)中的controlByteCodeByHeyun(自定义)方法
publicclassByteCodeMethodHandlerextendsMethodAdapter
{
publicByteCodeMethodHandler(MethodVisitormv)
{
super(mv);
}
publicvoidvisitCode()
{
visitMethodInsn(Opcodes.INVOKESTATIC,“ControlByteCode”,
“controlByteCodeByHeyun”,“()V”);
}
}
6.ControlByteCode类的controlByteCodeByHeyun方法如下
publicclassControlByteCode
{
publicstaticvoidcontrolByteCodeByHeyun()
{
System.out.println(“Thismethodhasalreadybeencovered.”);
//TODOrealsecuritycheck
}
}
7.这样,当运行完Generator类中main方法后,会生成一个和原Java字节码文件同名的文件(可以观察出,会比以前的文件大,当然也可以用MD5来确定是两个不同文件)。
8.此时在运行主方法类Main,会发现在Console打印如下:
Hello,ThisisHeyun’sinvestigationaboutcodecoverage!
Thismethodhasalreadybeencovered.
9.由此,可以看出,在原功能没有变化的前提下,通过改变Java字节码文件,我们实现了CodeCoverage的雏形。实际上,很多CodeCoverage工具(如Cobertura)都是运用此方法来实现Instrument(插装)的。