原始 MediaWiki 页面

我知道编辑这个网站吗?

Java助手

自迁移出 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 代码片段修改方法。有两种略有不同的方法可以做到这一点:CodeConverterExprEditor(这取决于使用哪一种,CodeConverter更容易使用,但ExprEditor更强大。

包装现有的代码块

Javassist 代码的一个常见原因是处理以前未处理的异常,或者使代码的某些部分成为有条件的。作为示例,我们在此演示如何保护假设的 OpenAccesstoString() 方法免受 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())(类 IJLogOutputStreamFiji Updaterfiji.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);
...