1、简介
javassist是一个开源的分析、编辑和创建java字节码的类库。不需要了解虚拟机指令,就能动态生成类或者改变类的结构。
官网首页www.javassist.org
2、下载
(1)下载链接http://www.csg.ci.i.u-tokyo.ac.jp/~chiba/javassist/
(2)使用的版本是javassist-3.18.0-GA。
Javassist是一个执行字节码操作的强而有力的驱动代码库。它允许开发者自由的在一个已经编译好的类中添加新的方法,或者是修改已有的方法。但是,和其他的类似库不同的是,Javassist并不要求开发者对字节码方面具有多么深入的了解,同样的,它也允许开发者忽略被修改的类本身的细节和结构。
字节码驱动通常被用来执行对于已经编译好的类的修改,或者由程序自动创建执行类等等等等相关方面的操作。这就要求字节码引擎具备无论是在运行时或是编译时都能修改程序的能力。当下有些技术便是使用字节码来强化已经存在的Java类的,也有的则是使用它来使用或者产生一些由系统在运行时动态创建的类。举例而言,JDO1.0规范就使用了字节码技术对数据库中的表进行处理和预编译,并进而包装成Java类。特别是在面向对象驱动的系统开发中,相当多的框架体系使用字节码以使我们更好的获得程序的范型性和动态性。而某些EJB容器,比如JBOSS项目,则通过在运行中动态的创建和加载EJB,从而戏剧性的缩短了部署EJB的周期。这项技术是如此的引人入胜,以至于在JDK中也有了标准的java.lang.reflect.Proxy类来执行相关的操作。
但是,尽管如此,编写字节码对于框架程序开发者们而言,却是一个相当不受欢迎的繁重任务。学习和使用字节码在某种程度上就如同使用汇编语言。这使得于大多数开发者而言,尽管在程序上可以获得相当多的好处,可攀登它所需要的难度则足以冷却这份热情。不仅如此,在程序中使用字节码操作也大大的降低了程序的可读性和可维护性。
这是一块很好的奶油面包,但是我们却只能隔着橱窗流口水…难道我们只能如此了吗?
所幸的是,我们还有Javassist。Javassist是一个可以执行字节码操作的函数库,可是尽管如此,它却是简单而便与理解的。他允许开发者对自己的程序自由的执行字节码层的操作,当然了,你并不需要对字节码有多深的了解,或者,你根本就不需要了解。
二、具体使用
Javassist的最外层的API和JAVA的反射包中的API颇为类似。它使你可以在装入ClassLoder之前,方便的查看类的结构。它主要由CtClass,,CtMethod,,以及CtField几个类组成。用以执行和JDK反射API中java.lang.Class,,java.lang.reflect.Method,, java.lang.reflect.Method .Field相同的操作。这些类可以使你在目标类被加载前,轻松的获得它的结构,函数,以及属性。此外,不仅仅是在功能上,甚至在结构上,这些类的执行函数也和反射的API大体相同。比如getName,getSuperclass,getMethods,,getSignature,等等。如果你对JAVA的反射机制有所了解的话,使用Javassist的这一层将会是轻松而快乐的。
接下来我们将给出一个使用Javassist来读取org.geometry.Point.class的相关信息的例子(当然了,千万不要忘记引入javassist.*包):
ClassPool pool = ClassPool.getDefault();
CtClass pt = pool.get("org.geometry.Point");
System.out.println(pt.getSuperclass().getName());
其中,ClassPool是CtClass 的创建工厂。它在class path中查找CtClass的位置,并为每一个分析请求创建一个CtClass实例。而“getSuperclass().getName()”则展示出org.geometry.Point.class所继承的父类的名字。
1、目标类和其父类之间的关系
例1:
pt.setSuperclass(pool.get("Figure"));
这样做将修改目标类和其父类之间的关系。我们将使org.geometry.Point.clas改继承自Figure类。当然了,就一致性而言,必须确保Figure类和原始的父类之间的兼容性。
2、往目标类中新增一个新的方法
而往目标类中新增一个新的方法则更加的简单了。首先我们来看字节码是如何形成的:
CtMethod m = CtNewMethod.make("public int xmove(int dx) { x += dx; }", pt);
pt.addMethod(m);
CtMethod类的让我们要新增一个方法只需要写一段小小的函数。这可是一个天大的好消息,开发者们再也不用为了实现这么一个小小的操作而写一大段的虚拟机指令序列了。Javassist将使用一个它自带的编译器来帮我们完成这一切。
(1)生成的目标类Emp.java
package com.study.javassist;
public class Emp {
private String ename;
private int eno;
public Emp(){
ename="yy";
eno=001;
}
public String getEname() {
return ename;
}
public void setEname(String ename) {
this.ename = ename;
}
public int getEno() {
return eno;
}
public void setEno(int eno) {
this.eno = eno;
}
//添加一个自定义方法
public void printInfo(){
System.out.println("begin!");
System.out.println(ename);
System.out.println(eno);
System.out.println("over!");
}
}
package com.study.javassist;
import java.io.File;
import java.io.FileOutputStream;
import java.lang.reflect.Modifier;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtConstructor;
import javassist.CtField;
import javassist.CtMethod;
import javassist.CtNewMethod;
/**
*使用javassit动态生成一个java类
* @author yy
* @version 1.0
*
*/
public class GenerateNewClassByJavassist {
public static void main(String[] args) throws Exception{
//ClassPool:CtClass对象的容器
ClassPool pool = ClassPool.getDefault();
//通过ClassPool生成一个public新类Emp.java
CtClass ctClass = pool.makeClass("com.study.javassist.Emp");
//添加字段
//首先添加字段private String ename
CtField enameField = new CtField(pool.getCtClass("java.lang.String"),"ename",ctClass);
enameField.setModifiers(Modifier.PRIVATE);
ctClass.addField(enameField);
//其次添加字段privtae int eno
CtField enoField = new CtField(pool.getCtClass("int"),"eno",ctClass);
enoField.setModifiers(Modifier.PRIVATE);
ctClass.addField(enoField);
//为字段ename和eno添加getXXX和setXXX方法
ctClass.addMethod(CtNewMethod.getter("getEname", enameField));
ctClass.addMethod(CtNewMethod.setter("setEname", enameField));
ctClass.addMethod(CtNewMethod.getter("getEno", enoField));
ctClass.addMethod(CtNewMethod.setter("setEno", enoField));
//添加构造函数
CtConstructor ctConstructor = new CtConstructor(new CtClass[]{}, ctClass);
//为构造函数设置函数体
StringBuffer buffer = new StringBuffer();
buffer.append("{\n")
.append("ename=\"yy\";\n")
.append("eno=001;\n}");
ctConstructor.setBody(buffer.toString());
//把构造函数添加到新的类中
ctClass.addConstructor(ctConstructor);
//添加自定义方法
CtMethod ctMethod = new CtMethod(CtClass.voidType,"printInfo",new CtClass[]{},ctClass);
//为自定义方法设置修饰符
ctMethod.setModifiers(Modifier.PUBLIC);
//为自定义方法设置函数体
StringBuffer buffer2 = new StringBuffer();
buffer2.append("{\nSystem.out.println(\"begin!\");\n")
.append("System.out.println(ename);\n")
.append("System.out.println(eno);\n")
.append("System.out.println(\"over!\");\n")
.append("}");
ctMethod.setBody(buffer2.toString());
ctClass.addMethod(ctMethod);
//为了验证效果,下面使用反射执行方法printInfo
Class> clazz = ctClass.toClass();
Object obj = clazz.newInstance();
obj.getClass().getMethod("printInfo", new Class[]{}).invoke(obj, new Object[]{});
//把生成的class文件写入文件
byte[] byteArr = ctClass.toBytecode();
FileOutputStream fos = new FileOutputStream(new File("D://Emp.class"));
fos.write(byteArr);
fos.close();
}
}
如下例所示,清单1首先包含了一个CtMethod,它主要针对Screen类的draw方法。然后,我们定义一个Point类,该类有一个move操作,用来实现该Point的移动。当然了,在移动前,我们希望可以通过draw方法得到该point目前的位置,那么,我们需要对该move方法加增如下的定义:
{ System.out.println("move"); $_ = $proceed($$); }
这样,在执行move之前,我们就可以打印出它的位置了。请注意这里的调用语句,它是如下格式的:
$_ = $proceed($$);
Javassist也同样允许开发者在某个函数的头,或者某个函数的尾上插入某段语句(代码)。比如,它有一个insertBefore方法用以在某函数的调用前执行某个操作,它的使用大致是这个样子的:
ClassPool pool = ClassPool.getDefault();
CtClass cc = pool.get("Screen");
CtMethod cm = cc.getDeclaredMethod("draw", new CtClass[0]);
cm.insertBefore("{ System.out.println($1); System.out.println($2); }");
cc.writeFile();
CtClass cc = sloader.get("Point");
CtMethod m1 = cc.getDeclaredMethod("move");
CtMethod m2 = CtNewMethod.copy(m1, cc, null);
m1.setName(m1.getName() + "_orig");
m2.setBody("{ System.out.println("call"); return $proceed($$);
}", "this", m1.getName());
cc.addMethod(m2);
cc.writeFile();
三、其他
以下是一个简单的完整示例:
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtMethod;
public class Test {
public static void main(String[] args) throws Exception {
ClassPool pool = ClassPool.getDefault();
//设置目标类的路径,确保能够找到需要修改的类,这里我指向firestorm.jar
//解包后的路径
pool.insertClassPath("d:/work/firestorm/firestorm") ;
//获得要修改的类
CtClass cc = pool.get("com.codefutures.if.if");
//设置方法需要的参数
CtClass[] param = new CtClass[3] ;
param[0] = pool.get("java.security.PublicKey") ;
param[1] = pool.get("byte[]") ;
param[2] = pool.get("byte[]") ;
//得到方法
CtMethod m = cc.getDeclaredMethod("a", param);
//插入新的代码
m.insertBefore("{return true ;}") ;
//保存到文件里
cc.writeFile() ;
}
}
结束!