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
Java动态编程初探——Javassist的更多相关文章
- 【java编程-Javassist】秒懂Java动态编程(Javassist研究)
作者:ShuSheng007 来源:CSDN 原文:https://blog.csdn.net/ShuSheng0007/article/details/81269295 版权声明:本文为博主原创文章 ...
- Java动态编程---动态代理
java中动态编程用到的技术有:反射(动态代理),javassist和ASM,这几种动态编程方法相比较,在性能上Javassist高于反射,但低于ASM,因为Javassist增加了一层抽象.在实现成 ...
- Java 网络编程初探
Java 网络编程 网络编程 网络编程:进行服务器端与客户端编程的开发操作实现. java.net:网络操作包 B/S结构: 浏览器/服务器模式(Browser/Server) 不在开发客户端代码 开 ...
- java动态编程库,利用动态编程打印运行时调用全景(函数调用关系链)
如果是一般java程序,不追求性能极致,想使用方便,推荐使用 Javassist 库. 如果是android程序,或者一般java程序欲追求性能极限,推荐使用 asm for java 及 asmde ...
- Java网络编程初探
IP地址案例 package ch17; import javax.swing.text.Style; import java.net.InetAddress; /** * Created by Ji ...
- Java并发编程初探
package test; import java.io.File; import java.io.FileReader; import java.io.IOException; import jav ...
- Java安全之Javassist动态编程
Java安全之Javassist动态编程 0x00 前言 在调试CC2链前先来填补知识盲区,先来了解一下Javassist具体的作用.在CC2链会用到Javassist以及PriorityQueue来 ...
- 使用javassist进行动态编程
今天在研究dubbo时,发现一个新的知识点,可以使用javassist包进行动态编程,hibernate也使用该包进行编程.晚上百度了很多资料,将它的特性以代码的形式展现出来. package com ...
- JVM插码之四:Java动态代理机制的对比(JDK 和CGLIB,Javassist,ASM)
一.class文件简介及加载 Java编译器编译好Java文件之后,产生.class 文件在磁盘中.这种class文件是二进制文件,内容是只有JVM虚拟机能够识别的机器码.JVM虚拟机读取字节码文件, ...
随机推荐
- ql 判断 函数 存储过程是否存在的方法
下面为您介绍sql下用了判断各种资源是否存在的代码,需要的朋友可以参考下,希望对您学习sql的函数及数据库能够有所帮助. 库是否存在 if exists(select * from master..s ...
- c#处理空白字符
空白字符是指在屏幕不会显示出来的字符(如空格,制表符tab,回车换行等).空格.制表符.换行符.回车.换页垂直制表符和换行符称为 “空白字符”,因为它们为与间距单词和行在打印的页 )的用途可以读取更加 ...
- WPF - 属性系统 (1 of 4)
本来我希望这一系列文章能够深入讲解WPF属性系统的实现以及XAML编译器是如何使用这些依赖项属性的,并在最后分析WPF属性系统的实际实现代码.但是在编写的过程中发现对WPF属性系统代码的讲解要求之前的 ...
- 企业IT管理员IE11升级指南【6】—— Internet Explorer 11面向IT专业人员的常见问题
企业IT管理员IE11升级指南 系列: [1]—— Internet Explorer 11增强保护模式 (EPM) 介绍 [2]—— Internet Explorer 11 对Adobe Flas ...
- 利用Hexo搭建个人博客-环境搭建篇
我是一个爱写博客进行总结分享的人.然而,有着热爱写博客并且深知写博客好处的我,却没有好好的把这个习惯坚持下来.如今毕业已经一年多了吧,每一次与师弟师妹们聊天,我总会意味深长的建议他们,一定要定期梳理总 ...
- 为jQuery添加Webkit的触摸方法支持
前些日子收到邮件,之前兼职的一个项目被转给了其他人,跟进的人来问我相关代码的版权问题. 我就呵呵了. 这段代码是我在做13年一份兼职的时候无聊加上去的,为jQuery添加触摸事件的支持.因为做得有点无 ...
- 解决记录日志导致VS2013缓慢的问题
最近VS2013启动时.编译时.显示项目属性速度狂慢,遇到项目多的,显示项目属性时甚至VS挂掉. 把所有的VS插件卸载.甚至重装VS也不见效果. 用ProcessMonitor发现VS一直在Defau ...
- Qt Disable QDebug And Warning Output
如何禁止qDebug的输出 在项目开发的过程中,为了开发方便,我们常常在Qt的Application Output中输出一些内容,慢慢的. 有些qDebug就会被我们遗忘再角落里. 虽然对整个程序影响 ...
- 【PRINCE2是什么】PRINCE2认证之七大原则(3)
我们先来回顾一下,PRINCE2七大原则分别是持续的业务验证,经验学习,角色与责任,按阶段管理,例外管理,关注产品,剪裁. 第三个原则:明确定义的角色和职责. 项目离不开人员,错误的人来了,合适的人没 ...
- Apache Spark源码剖析
Apache Spark源码剖析(全面系统介绍Spark源码,提供分析源码的实用技巧和合理的阅读顺序,充分了解Spark的设计思想和运行机理) 许鹏 著 ISBN 978-7-121-25420- ...