Javaassist是一个高层的Java字节码处理类库,能运行时动态生成类,修改类。Javaassit能动态生成类的基础源于Java Class的字节码技术:只要遵从规范,Java Class可以来自任何地方。
类似的技术还有:bcel,asm等,他们相对于Javaassit,偏向底层,效率较高,但编码难度更高(需要了解JVM指令)。
Javaassist是Jboss的一个子项目,其特点是简单:不需要了解底层JVM指令,直接用Java代码编写,容易理解,并且现在生成代码效率和以上两种技术相差已经很小。
Javaassist是一个日本人
Shigeru Chiba
开发的。
例子
动态生成一个全新的类// ClassPool包含所有动态生成的类,getDefault()返回默认的ClassPool, ClassPool cp = ClassPool.getDefault(); // 动态生成一个类 CtClass gclazz = cp.makeClass("org.jamee.demo.javaassist.GeneratedClass"); CtMethod gmethod = CtMethod .make("public void sayHello() { System.out.println(\"Hello Javaassist\"); }", gclazz); gclazz.addMethod(gmethod); // 转换成Class Class> gc = gclazz.toClass(); // 将该CtClass从ClassPool中移除, gclazz.detach(); // 调用方法 Object ginst = gc.newInstance(); Method gm = gc.getMethod("sayHello"); gm.invoke(ginst);
输出:
Hello Javaassist
动态修改类
测试目标类
class TestClass { public int compute(int param) { try { Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } return param + 1000; } }
修改类
// ClassPool包含所有动态生成的类,getDefault()返回默认的ClassPool, ClassPool cp = ClassPool.getDefault(); // 该ClassPool默认从java lib,ext,classpath搜索class文件,并生成一个CtClass返回 CtClass clazz = cp.get("org.jamee.demo.javaassist.TestClass"); // 修改compute方法 CtMethod method = clazz.getDeclaredMethod("compute"); // $args,参数($1,第一个参数,$2,$3以此类推) ,$_:返回值 method.insertAfter("System.out.println(\"compute called with param: \" + java.util.Arrays.toString($args) + \", return: \" + $_);"); // 转换成Class,这一步也载入了修改后的Class。注意:必须保证之前这个Class没有载入过,不然会报异常:java.lang.LinkageError,因为JVM不允许一个class多次加载 clazz.toClass(); // 将该CtClass从ClassPool中移除, clazz.detach(); // 这时载入的TestClass已经被修改 TestClass test = new TestClass(); // 调用方法 test.compute(5);
输出:
compute called with param: [5], return: 1005
拦截方法
Javaassist的另外一个用法是拦截方法
ProxyFactory factory = new ProxyFactory(); // 设置父类,ProxyFactory将会动态生成一个类,继承该父类 factory.setSuperclass(TestClass.class); // 设置过滤器,判断哪些方法调用需要被拦截 factory.setFilter(new MethodFilter() { public boolean isHandled(Method m) { if (m.getName().equals("compute")) { return true; } return false; } }); Class> c = factory.createClass(); TestClass object = (TestClass) c.newInstance(); // 设置拦截处理 ((Proxy) object).setHandler(new MethodHandler() { public Object invoke(Object self, Method thisMethod, Method proceed, Object[] args) throws Throwable { long start = System.currentTimeMillis(); try { return proceed.invoke(self, args); } catch (Exception e) { throw e; } finally { long taken = System.currentTimeMillis() - start; System.out.println("call method: " + thisMethod.getName() + " take: " + taken + "ms"); } } }); // 调用方法 System.out.println(object.compute(11));
输出:
call method: compute take: 3002ms
1011
从例子可以看出Javaassist可以修改类,创建类,创建方法,修改方法,拦截方法。实际上通过Javaassist你还可以:定义包,创建字段属性,访问注解(Annotation),定义接口等等。
限制
(1)不支持java5.0的新增语法(enum和泛型:由于泛型是运行时擦除的,因此可以直接去除类型变量),不支持注解修改
(7)不支持变长参数,需要改成数组
(8)插入的同一个方法代码前后的局部变量无法使用
例如:
void foo () {
//insert before
int a= 5;
....
// insert after
c = a // a is not defined
}
解决办法:
a. 创建一个field
b. 添加一个新方法:foo$impl,方法体拷贝旧的foo方法体,而新的foo,调用foo$impl
void foo () {
int a= 5;
foo$impl($$);
c = a
}
void foo$impl() {
...
}
参考
http://jboss-javassist.github.io/javassist/ 官网
http://jboss-javassist.github.io/javassist/tutorial/tutorial.html Jboss网站文档
http://zhxing.iteye.com/blog/1703305 这篇文章几乎是翻译了上面的文章,但翻译的不是很好
http://javatar.iteye.com/blog/814426 性能的分析