Javaassist技术

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和泛型:由于泛型是运行时擦除的,因此可以直接去除类型变量),不支持注解修改

(2)不支持数组的初始化,如String[]{"1","2"},除非只有数组的维度为1
(3)不支持内部类和匿名类
(4)不支持continue和break 表达式。
(5)方法重载支持不好。例如
class A {} 
class B extends A {} 
class C extends B {} 
 
class X { 
    void foo(A a) { .. }  
    void foo(B b) { .. }  
}
如果调用  x.foo(new C()),可能会调用foo(A) 。
 
(6)推荐开发者用#分隔一个class name和static method或者 static field。例如:
javassist.CtClass.intType.getName()推荐用javassist.CtClass#intType.getName()

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

猜你喜欢

转载自jamie-wang.iteye.com/blog/2243605