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 性能的分析