Welcome 微信登录

首页 / 软件开发 / JAVA / Java编程的动态性,第6部分: 利用Javassist进行面向方面的更改

Java编程的动态性,第6部分: 利用Javassist进行面向方面的更改2011-04-09 IBM Dennis M. SosnoskiJava 顾问 Dennis Sosnoski 在他的关于 Javassist 框架的三期文章中将精华部分留在 了最后。这次他展现了 Javassist 对搜索-替换的支持是如何使对 Java 字节码的编辑变得 像文本编辑器的“替换所有(Replace All )”命令一样容易的。想报告所有写入特定字段 的内容或者对方法调用中参数的更改中的补丁吗?Javassist 使这变得很容易,Dennis 向您 展示了其做法。

本系列的 第 4 部分和 第 5 部分讨论了如何用 Javassist 对二进制类进行局部更改。 这次您将学习以一种更强大的方式使用该框架,从而充分利用 Javassist 对在字节码中查找 所有特定方法或者字段的支持。对于 Javassist 功能而言,这个功能至少与它以类似源代码 的方式指定字节码的能力同样重要。对选择替换操作的支持也有助于使 Javasssist 成为一 个在标准 Java 代码中增加面向方面的编程功能的绝好工具。

第 5 部分介绍了 Javassist 是如何让您拦截类加载过程的 ―― 甚至在二进制类表示正 在被加载的时候对它们进行更改。这篇文章中讨论的系统字节码转换可以用于静态类文件转 换,也可以用于运行时拦截,但是在运行时使用尤其有用。

处理字节码修改

Javassist 提供了两种不同的系统字节码修改的处理方法。第一种技术是使用 javassist.CodeConverter 类,使用起来要稍微简单一些,但是可以完成的任务有很多限制 。第二种技术使用 javassist.ExprEditor 类的自定义子类,它稍微复杂一些,但是所增加 的灵活性足以抵销所付出的努力。在本文中我将分析这两种方法的例子。

代码转换

系统字节码修改的第一种 Javassist 技术使用 javassist.CodeConverter 类。要利用这 种技术,只需要创建 CodeConverter 类的一个实例并用一个或者多个转换操作配置它。每一 个转换都是用识别转换类型的方法调用来配置的。转换类型可分为三类:方法调用转换、字 段访问转换和新对象转换。

清单 1 给出了使用方法调用转换的一个例子。在这个例子中,转换只是增加了一个方法 正在被调用的通知。在代码中,首先得到将要使用的 javassist.ClassPool 实例,将它配置 为与一个翻译器一同工作 (正如在前面 第 5 部分 所看到的)。然后,通过 ClassPool 访 问两个方法定义。第一个方法定义针对的是要监视的“set”类型的方法(类和方法名来自命 令行参数),第二个方法定义针对的是 reportSet() 方法 ,它位于TranslateConvert 类中 ,并会报告对第一个方法的调用。

有了方法信息后,就可以用 CodeConverterinsertBeforeMethod() 配置一个转换,以在 每次调用这个 set 方法之前增加一个对报告方法的调用。然后所要做的就是将这个转换器应 用到一个或者多个类上。在清单 1 的代码中,我是通过调用类对象的 instrument() 方法, 在 ConverterTranslator 内部类的 onWrite() 方法中完成这项工作的。这将自动对从 ClassPool 实例中加载的每一个类应用这个转换。

清单 1. 使用 CodeConverter

public class TranslateConvert
{
public static void main(String[] args) {
if (args.length >= 3) {
try {

// set up class loader with translator
ConverterTranslator xlat =
new ConverterTranslator();
ClassPool pool = ClassPool.getDefault(xlat);
CodeConverter convert = new CodeConverter();
CtMethod smeth = pool.get(args[0]).
getDeclaredMethod(args[1]);
CtMethod pmeth = pool.get("TranslateConvert").
getDeclaredMethod("reportSet");
convert.insertBeforeMethod(smeth, pmeth);
xlat.setConverter(convert);
Loader loader = new Loader(pool);

// invoke "main" method of application class
String[] pargs = new String[args.length-3];
System.arraycopy(args, 3, pargs, 0, pargs.length);
loader.run(args[2], pargs);

} catch ...
}

} else {
System.out.println("Usage: TranslateConvert " +
"clas-name set-name main-class args...");
}
}

public static void reportSet(Bean target, String value) {
System.out.println("Call to set value " + value);
}

public static class ConverterTranslator implements Translator
{
private CodeConverter m_converter;

private void setConverter(CodeConverter convert) {
m_converter = convert;
}

public void start(ClassPool pool) {}

public void onWrite(ClassPool pool, String cname)
throws NotFoundException, CannotCompileException {
CtClass clas = pool.get(cname);
clas.instrument(m_converter);
}
}
}