最近看了Java 编程的动态性,第 7 部分: 用 BCEL 设计字节码,网址是http://www-128.ibm.com/developerworks/cn/java/j-dyn0414/
其中的示范代码解释的不是很详细,这方面的中文资料又少,只好自己花时间看下去。bcel的类库却是不够友好,api文档也是走马观花的点一下,很多函数没有说明。理解基本靠猜,还好有个示范代码。下面对自己理解的东西做个记录,详细的代码可以到上面的链接下载。
import java.io.FileOutputStream;
import java.io.IOException;
import org.apache.bcel.Constants;
import org.apache.bcel.classfile.ClassParser;
import org.apache.bcel.classfile.JavaClass;
import org.apache.bcel.classfile.Method;
import org.apache.bcel.generic.ClassGen;
import org.apache.bcel.generic.ConstantPoolGen;
import org.apache.bcel.generic.InstructionConstants;
import org.apache.bcel.generic.InstructionFactory;
import org.apache.bcel.generic.InstructionList;
import org.apache.bcel.generic.MethodGen;
import org.apache.bcel.generic.ObjectType;
import org.apache.bcel.generic.PUSH;
import org.apache.bcel.generic.Type;
public class BCELTiming {
/**
* Add timing wrapper to method of class. The method can accept any
* arguments and return any type (including void), but must be a normal
* (non-static, non-initializer) method to be used with this code as
* currently implemented. Handling the other types of methods would not
* involve any fundamental changes to the code.
*
* @param cgen
* generator for class being modified
* @param method
* current method to be enhanced with timing wrapper
*/
private static void addWrapper(ClassGen cgen, Method method) {
// set up the construction tools
InstructionFactory ifact = new InstructionFactory(cgen);
InstructionList ilist = new InstructionList();
ConstantPoolGen pgen = cgen.getConstantPool();
String cname = cgen.getClassName();
MethodGen wrapgen = new MethodGen(method, cname, pgen);
wrapgen.setInstructionList(ilist);
// rename a copy of the original method
MethodGen methgen = new MethodGen(method, cname, pgen);
cgen.removeMethod(method);
String iname = methgen.getName() + "$impl";
methgen.setName(iname);
cgen.addMethod(methgen.getMethod());
//以上是一下初始化的工作
// compute the size of the calling parameters
// operand stack操作数堆栈
Type[] types = methgen.getArgumentTypes(); // 取出参数类型数组
// solt代表本地变量的堆栈偏移量,里头储存了调用methen代表的函数的参数
int slot = methgen.isStatic() ? 0 : 1; // 这种方式与Java如何处理方法调用有关。对于非静态的方法,每次调用的第一个(隐藏的)参数是目标对象的this引用(就是位置0储存的内容)。
for (int i = 0; i < types.length; i++) {
slot += types[i].getSize();// 累计个个参数类型的长度,
}
// 现在solt指向最后一个参数的下一个位置
// save time prior to invocation
// 调用静态的long java.lang.System.currentTimeMillis()方法,调用结束后函数的返回的long类型的值会压入operand stack操作数堆栈
ilist.append(ifact.createInvoke("java.lang.System",
"currentTimeMillis", Type.LONG, Type.NO_ARGS,
Constants.INVOKESTATIC));
ilist.append(InstructionFactory.createStore(Type.LONG, slot));// 将operand stack的top保存到本地变量堆栈的slot位置,operand stack弹出long值
// call the wrapped method
int offset = 0; // 偏移量
short invoke = Constants.INVOKESTATIC; // 预先设置为调用静态函数
if (!methgen.isStatic()) { // 如果不是调用静态函数,将调用的第一个(隐藏的)参数(目标对象的this引用)压入operand stack
ilist.append(InstructionFactory.createLoad(Type.OBJECT, 0));
offset = 1;// 偏移量加1
invoke = Constants.INVOKEVIRTUAL;// 设置为调用非静态函数
}
for (int i = 0; i < types.length; i++) { // 遍历所有参数
Type type = types[i];
ilist.append(InstructionFactory.createLoad(type, offset)); // 按参数类型把参数一个个从本地变量堆栈取出,压入operand stack
offset += type.getSize();
}
Type result = methgen.getReturnType();// 取得要调用函数的返回值类型
ilist.append(ifact.createInvoke(cname, iname, result, types, invoke));// 调用方法名为iname的函数
// store result for return later
if (result != Type.VOID) {
ilist.append(InstructionFactory.createStore(result, slot + 2)); // 将名为iname的函数返回值复制到本地变量堆栈的slot+2的位置上
}
// print time required for method call
// 获取静态对象java.lang.System.out的引用,返回值压入operand stack
ilist.append(ifact.createFieldAccess("java.lang.System", "out",
new ObjectType("java.io.PrintStream"), Constants.GETSTATIC));
ilist.append(InstructionConstants.DUP);// 取operand stack的top,压入operand stack。完成后load_stack的头两个元素是静态对象java.lang.System.out的引用
ilist.append(InstructionConstants.DUP);// 取operand stack的top,压入operand stack。现在有3个java.lang.System.out的引用。供下面3次调用out.print()函数使用
String text = "Call to method " + methgen.getName() + " took ";
ilist.append(new PUSH(pgen, text));// 将text放入pgen(代表常量池),并把其在pgen的引用压入operand stack(供out.print(Sting)调用的参数)
ilist.append(ifact.createInvoke("java.io.PrintStream", "print",
Type.VOID, new Type[] { Type.STRING },
Constants.INVOKEVIRTUAL));// 调用结束,operand stack弹出一个String的引用和一个out的引用(还剩2个out),函数没有返回值
ilist.append(ifact.createInvoke("java.lang.System",
"currentTimeMillis", Type.LONG, Type.NO_ARGS,
Constants.INVOKESTATIC));// 调用java.lang.System.currentTimeMillis()方法,调用结束后函数的返回的long类型的值会压入堆栈operand stack
ilist.append(InstructionFactory.createLoad(Type.LONG, slot));// 从本地变量堆栈的slot位置载入先前储存的long值,压入operand stack
ilist.append(InstructionConstants.LSUB);// 调用long的减法指令,弹出2个long值,并把结果压入operand stack,现在operand stack的top第一个是long,第二个是out的引用
ilist.append(ifact.createInvoke("java.io.PrintStream", "print",
Type.VOID, new Type[] { Type.LONG }, Constants.INVOKEVIRTUAL));// 调用out.print(long)方法
ilist.append(new PUSH(pgen, " ms."));// 将String对象" ms."放入pgen,并把其在pgen的引用压入operand stack(供out.print(Sting)调用的参数)
ilist
.append(ifact.createInvoke("java.io.PrintStream", "println",
Type.VOID, new Type[] { Type.STRING },
Constants.INVOKEVIRTUAL));
// return result from wrapped method call
if (result != Type.VOID) {
ilist.append(InstructionFactory.createLoad(result, slot + 2));// 处理返回值,如果不为空,从本地对象堆栈的slot+2位置读取指定类型的返回值压入operand stack
}
ilist.append(InstructionFactory.createReturn(result)); //调用处理返回值的指令,result为返回值的类型
//下面是一下扫尾工作
// finalize the constructed method
wrapgen.stripAttributes(true);
wrapgen.setMaxStack();
wrapgen.setMaxLocals();
cgen.addMethod(wrapgen.getMethod());
ilist.dispose();
}
public static void main(String[] argv) {
if (argv.length == 2 && argv[0].endsWith(".class")) {
try {
JavaClass jclas = new ClassParser(argv[0]).parse();
ClassGen cgen = new ClassGen(jclas);
Method[] methods = jclas.getMethods();
int index;
for (index = 0; index < methods.length; index++) {
if (methods[index].getName().equals(argv[1])) {
break;
}
}
if (index < methods.length) {
addWrapper(cgen, methods[index]);
FileOutputStream fos = new FileOutputStream(argv[0]);
cgen.getJavaClass().dump(fos);
fos.close();
} else {
System.err.println("Method " + argv[1] + " not found in "
+ argv[0]);
}
} catch (IOException ex) {
ex.printStackTrace(System.err);
}
} else {
System.out.println("Usage: BCELTiming class-file method-name");
}
}
}
相对javassist,bcel确实比较复杂。但是bcel给我的感觉比较自由,有种一切尽在掌握的感觉,这一点比较我喜欢。
虽然自由,但是用bcel写一个类的实现绝对不会是一件让人开心的事情,如果碰巧你的实现又稍微复杂了点(比如实现了xx接口,又增加了一些函数和成员变量)。而且你的实现未必会比javac编译出来的代码效率高(不考虑jvm的动态优化)。
不过,先把你要实现的类的代码写出来,按照代码来写bcel的实现,会降低些实现难度。
分享到:
相关推荐
另一篇资源"学用BCEL设计字节码的记录(java) - 庸人自扰 - C++博客.mht"则可能更侧重于实际操作和应用案例。博客作者分享了他们在使用BCEL过程中的经验,包括常见的问题和解决方法,这对于我们理解和掌握BCEL的使用...
2. **字节码生成**: 使用BCEL,开发者可以创建新的类或方法,或者修改现有的字节码指令。这对于实现动态代理、代码注入、运行时代码生成等场景非常有用。 3. **字节码操作**: BCEL提供了丰富的API,允许开发者插入...
例如,可以先使用jclasslib来理解一个类的字节码,然后利用这些工具进行动态或静态的字节码注入,以实现如性能优化、日志记录、事务管理等功能。 四、字节码操作的应用场景 1. **性能优化**:通过修改字节码,可以...
主要的字节码工具包括ASM、BCEL、CGLIB和Byte Buddy等。 ASM是一个轻量级的字节码库,可以直接生成和修改Java类的字节码。它提供了低级别的API,可以精细地控制字节码生成,适合于实现复杂的AOP逻辑。例如,通过ASM...
6. **示例**:可能提供一些示例代码或演示应用,展示如何使用BCML进行字节码操作。 7. **许可证文件**:包含Apache License 2.0或其他相关许可协议,规定了使用库的条件。 8. **README**和`CHANGES`文件:提供项目...
- **字节码混淆**:在软件发布时,可以使用BCEL对字节码进行混淆,提高代码的安全性。 - **AOP(面向切面编程)**:在运行时插入或修改代码,实现跨切面的逻辑,如日志记录、事务管理等。 - **元编程**:通过BCEL...
4. **字节码插桩**: 在已存在的字节码中插入额外的代码,用于日志记录、性能监控或事务处理等。 5. **JVM语言实现**: BCEL为实现新的JVM目标语言(如Groovy、Scala等)提供了基础工具。 **四、BCEL与Java字节码** ...
1. **字节码解析**:使用字节码解析库,如ASM、BCEL、Javassist等,读取并解析.class文件。这些库提供了API,允许开发者访问和操作字节码。 2. **类结构分析**:通过解析字节码,可以识别出类的公共与私有属性、...
这通常使用ASM、BCEL、Javassist等字节码库完成,这些库提供了方便的方法解析和操作字节码。 3. **插入字节码**:在分析了字节码后,根据需求,我们可以选择在特定的位置插入新的字节码指令。例如,如果我们想要在...
总结起来,"ant-apache-bcel-1.6.3.jar.zip"这个压缩包是Java开发者的重要工具,它整合了Ant的构建能力与BCEL的字节码操作功能,提供了在Java项目中进行复杂构建和代码操作的能力。了解并掌握这两个工具的使用,将对...
1. **字节码注入的原理**:字节码注入通常通过动态代理、ASM、BCEL、Javassist等字节码操作库来实现。这些库允许程序在运行时动态生成或修改字节码,然后通过ClassLoader加载到JVM中,使得修改后的字节码影响目标...
- 开源库如ASM、BCEL、Javassist:提供了API,可以直接操作和生成字节码。 - IntelliJ IDEA和Eclipse等IDE也提供了查看字节码的功能。 掌握Java字节码分析能力对Java开发者来说是一项高级技能,它不仅可以提升你...
例如,添加日志记录、安全验证等功能,这些功能可以通过特定的字节码转换模式来实现。 2. **复杂转换模式的组合**: 通过将简单的字节码转换模式组合起来,可以构建出更为复杂的转换逻辑,以满足不同场景下的需求。 3...
Java安全漫谈目录 人口统计字节码:远程字节码加载: 系统默认的defineClass加载字节码演示: 使用TemplatesImpl加载字节码演示: 使用BCEL加载字节码:反序列化:最简单的Transformer演示: 我简化的 ,更方便大家...
要获取参数名称,我们需要借助于字节码解析库,例如ASM、BCEL或javassist。 1. ASM库:ASM是一个强大的Java字节码操控和分析框架。我们可以使用ASM的ClassReader类读取字节码,并通过MethodVisitor接口访问方法信息...
这个结构包括了一些关键字段,如魔数(magic)用于识别文件类型,版本号(minor_version和major_version)记录了字节码的版本,以及常量池(constant_pool)、访问标志(access_flags)、当前类索引(this_class)、...
Javaassist是一个开源库,主要用在Java编程中,它提供了对字节码操作的强大功能,使得程序员可以在运行时动态修改类或创建新的类。这个"javassist-3.9.0.GA.jar.zip"文件包含了Javaassist库的版本3.9.0 GA,这是一个...
BCEL (Byte Code Engineering Library) 是一个用于处理 Java 字节码的库,BTrace 在注入探针时可能使用了 BCEL 来生成和操作字节码。BCEL 提供了工具来解析、创建、修改和打印 Java 类文件。 7. **使用场景**: -...
JavaAssist库通过提供一系列API,使得开发者无需深入理解复杂的ASM或BCEL等底层字节码操作框架,就能方便地实现对字节码的操作。这包括添加、删除或修改类的方法、字段和构造器,以及插入或替换代码块,甚至可以创建...
在实际开发中,解析Class文件的工具和库有很多,如javap(Java字节码反汇编器)、ASM、BCEL、Javassist等。这些工具可以帮助开发者深入理解字节码,甚至自动生成或修改字节码,实现一些高级功能。 总而言之,Java...