Java动态编程初探——Javassist

最近需要通过配置生成代码,减少重复编码和维护成本。用到了一些动态的特性,和大家分享下心得。

我们常用到的动态特性主要是反射,在运行时查找对象属性、开发方法 ,修改作用域,通过开发方法 名称调用开发方法 等。在线的应用开发不会频繁使用反射,因为反射的性能开销较大。其实还有一种和反射一样强大的特性,但是开销却很低,它就是javassit。

javassit其实就是一个二方包,提供了运行时操作java字节码的开发方法 。大家都知道,java代码编译完会生成.class文件,就是一堆字节码。jvm(准确说是jit)会解释执行这些字节码(转换为机器码并执行),由于字节码的解释执行是在运行时进行的,那我们能否手工编写字节码,再由jvm执行呢?答案是肯定的,而javassist就提供了一些方便的开发方法 ,让我们通过这些开发方法 生成字节码。

类似字节码操作开发方法 还有asm。几种动态编程开发方法 相比较,在性能上javassist高于反射,但低于asm,因为javassist增加了一层抽象。在实现成本上javassist和反射都很低,而asm由于直接操作字节码,相比javassist源码级别的api实现成本高很多。几个开发方法 有自己的应用开发场景,比如kryo使用的是asm,追求性能的最大化。而nbeancopyutil采用的是javassist,在对象拷贝的性能上也已经明显高于其他的库,并保持高易用性。实际项目中推荐先用javassist实现原型,若在性能测试中发现javassist成为了性能瓶颈,再考虑使用其他字节码操作开发方法 做优化。

javassist的使用很简单,首先获取到class定义的容器classpool,通过它获取已经编译好的类(compile time class),并给这个类设置一个父类,而writefile讲这个类的定义从新写到磁盘,以便后面使用。

classpool pool = classpool.getdefault();
ctclass cc = pool.get("test.rectangle");
cc.setsuperclass(pool.get("test.point"));
cc.writefile();

由ctclass可以方便的获取字节码和加载字节码:

byte[] b = cc.tobytecode();
class clazz = cc.toclass();

如果需要定义一个新类,只需要

classpool pool = classpool.getdefault();
ctclass cc = pool.makeclass("point");

同样的还可以通过ctmethod和ctfield构造开发方法 和成员甚至annotation。

classpool pool = classpool.getdefault();
ctclass cc = pool.makeclass("foo");
ctmethod mthd = ctnewmethod.make("public integer getinteger() { return null; }", cc);
cc.addmethod(mthd);
ctfield f = new ctfield(ctclass.inttype, "i", cc);
point.addfield(f);
clazz = cc.toclass(); object instance = class.newinstance();

javassist不仅可以生成类、变量和开发方法 ,还可

 

此文来自: 马开东博客 转载请注明出处 网址:

以操作现有的 开发方法 ,这在aop上非常有用,比如做 开发方法 调用的埋点

 

// point.java
class point {
    int x, y;
    void move(int dx, int dy) { x += dx; y += dy; }
}

// 对已有代码每次move执行时做埋点
classpool pool = classpool.getdefault();
ctclass cc = pool.get("point");
ctmethod m = cc.getdeclaredmethod("move");
m.insertbefore("{ system.out.println($1); system.out.println($2); }");
cc.writefile();

其中$1和$2表示调用栈中的第一和第二个参数,写到磁盘后的class定义类似:

class point {
    int x, y;
    void move(int dx, int dy) {
        { system.out.println(dx); system.out.println(dy); }
        x += dx; y += dy;
    }
}

在使用javassist时遇到过一些问题。

1 因为tomcat和jboss使用的是独立的classloader,而javassist是通过默认的classloader加载类,因此直接对tomcat context中定义的类做toclass会抛出classcastexception异常,可以用tomcat的classloader加载字节码。

ctclass cc = ...;
class c = cc.toclass(bean.getclass().getclassloader());

2 发现在简单的测试中可以load的类,在tomcat中无法load。这是因为,classpool.getdefault()查找的路径和底层的jvm路径。而tomcat中定义了多个classloader,因此额外的class路径需要注册到classpool中。

pool.insertclasspath(new classclasspath(this.getclass()));

3 我想在运行时修改类的一个开发方法 ,但是jvm是不允许动态的reload类定义的。一旦classloader加载了一个class,在运行时就不能重新加载这个class的另一个版本,调用toclass()会抛linkageerror。因此需要绕过这种方式定义全新的class。而toclass()其实是当前thread所在的classloader加载class。

4 javassist生成的字节码由于没有class声明,字节码创建变量及开发方法 调用都需要通过反射。这点在在线的应用开发上的性能损失是不能接受的,受到nbeancopyutil实现的启发,可以定义一个interface,javassist的字节码实现这个interface,而调用方通过这个接口调用字节码,而不是反射,这样避免了反射调用的开销。还有一点字节码new一个变量也是通过反射,因此通过代理的开发方法 ,将每个pv都需要new的字节码对象改为每次new一个代理对象,代理到常驻内存的字节码对象中,这样避免了每次反射的开销。

参考资料:

http://asm.ow2.org/

http://notatube.blogspot.com/2010/11/project-lombok-trick-explained.html

http://www.csg.ci.i.u-tokyo.ac.jp/~chiba/javassist/tutorial/tutorial.html

http://www.ibm.com/developerworks/cn/java/coretech/java-dynamic.html

转载于:https://my.oschina.net/milletes/blog/3085212

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值