自迁移出 MediaWiki 以来,本页内容尚未经过审查。如果您愿意帮忙,请查看帮助指南!
Javassist 是一个用于生成、修改和检查字节码(即 Java 机器语言)的 Java 库。
简单的例子
假设您想知道哪些调用路径通向给定类(例如org.soft.micro.Narf)中的给定方法(例如foo())。
// get the class pool
ClassPool pool = ClassPool.getDefault();
// access the class without loading it just yet
CtClass clazz = pool.get("org.soft.micro.Narf");
// get the method; you can find the signature (2nd parameter) like this:
// fiji --javap -s org.soft.micro.Narf | grep -A1 foo
CtMethod method = clazz.getMethod("foo", "()V");
// now let's output a whole lot of stuff whenever this method is entered
method.insertBefore("new Exception(\"Here I am!\").printStackTrace();");
当然,您不仅限于输出堆栈跟踪。您还可以生成这些跟踪的字符串并将它们放入映射中,可能会计算出现次数。
其他有用的操作包括检查传递给方法的参数,您可以通过特殊名称 $1、$2、… 访问这些参数。
进一步阅读:Javassist 的 online tutorial。
代码编辑
虽然 Javassist 允许直接编辑字节码,但它最强大的功能是它允许您使用 Java 代码片段修改方法。有两种略有不同的方法可以做到这一点:CodeConverter和ExprEditor(这取决于使用哪一种,CodeConverter更容易使用,但ExprEditor更强大。
包装现有的代码块
Javassist 代码的一个常见原因是处理以前未处理的异常,或者使代码的某些部分成为有条件的。作为示例,我们在此演示如何保护假设的 OpenAccess’ toString() 方法免受 NullPointerException 的影响:
// get the class pool
ClassPool pool = ClassPool.getDefault();
// access the class without loading it just yet
CtClass clazz = pool.get("OpenAccess");
CtMethod method = clazz.getMethod("toString", "()Ljava/lang/String;");
// now let's handle NullPointerExceptions
method.setBody("try {"
+ " $_ = $proceed($$);"
+ "} catch (java.lang.NullPointerException e) {"
+ " return \"(null)\";"
+ "}");
请注意,特殊的 $_ = $proceed($$) 是 Javassist 特定的语法,引用原始代码。详情请参阅the Javassist tutorial。
调试
Javassist 有非常有用的反汇编和内省工具。例如,您可以使用如下代码检查类的内容:
// get the class pool
ClassPool pool = ClassPool.getDefault();
// access the class without loading it just yet
CtClass clazz = pool.get("org.soft.micro.Narf");
// output the contents
ClassFilePrinter.print(clazz.getClassFile());
或者你可以反汇编这样的方法:
// get the class pool
ClassPool pool = ClassPool.getDefault();
// access the class without loading it just yet
CtClass clazz = pool.get("org.soft.micro.Narf");
// get the method
CtMethod method = clazz.getMethod("foo", "()V");
// disassemble the method
InstructionPrinter.print(method, System.err);
如果您绝对不希望输出到 System.err,您可以将其替换为 new PrintStream(new IJLogOutputStream())(类 IJLogOutputStream 在 Fiji Updater 的 fiji.updater.util 类中定义,供您重用)。
验证错误
出于安全原因,HotSpot 不允许执行任何格式错误的字节码。为了验证字节码是否合理,代码在执行之前会被“验证”。
不幸的是,上述验证者的错误消息并不完全是一流的。
如果您想避免被错误消息误导(VerifyError中提到的方法很可能是,而不是,而不是</u>),而且想知道错误发生在字节码中的哪个位置,那么最简单的方法可能是使用BCEL(您可以直接从Maven repository获得),Apache的字节代码工程库。为此,您必须先写出 .class 文件:
CtClass clazz = ...;
...;
File directory = ...;
File file = new File(directory, clazz.getName().replace('.', '/') + ".class");
file.getParentFile().makeDirectories();
DataOutputStream out = new DataOutputStream(new FileOutputStream(file));
clazz.getClassFile().write(out);
out.close();
然后你可以使用验证器:
fiji --classpath=/path/to/bcel.jar:directory/ \
--main-class=org.apache.bcel.verifier.Verifier \
my.class.Name
(旁注:Apache 调用验证器 JustIce,但该名称并未反映在程序名称中,仅反映在其输出中。)
有关此问题的更多信息,请参阅here。
旁注:ASM 组件(包含在 JRuby 中,而 JRuby 又包含在 Fiji 中)也有一个验证器。您可以使用类似于以下的命令行调用来启动它:
fiji --classpath /path/to/classes \
--main-class jruby.objectweb.asm.util.CheckClassAdapter \
my.class.Name
如果您使用 Fiji 的 JavassistHelper class,则可以使用 verify() 方法,该方法除了移交给 ASM 组件的验证程序外什么也不做。例子:
import fiji.JavassistHelper;
import javassist.ClassPool;
...
ClassPool.getDefault().addClassPath("/path/to/my.jar");
JavassistHelper helper = new JavassistHelper();
// hack away
helper.verify(hacker.get("the.class.in.question"), System.err);
...