什么是javassist?
Javassist是一个开源的分析、编辑和创建Java字节码的类库。是由东京工业大学的数学和计算机科学系的 Shigeru Chiba (千叶 滋)所创建的。它已加入了开放源代码JBoss应用服务器项目,通过使用Javassist对字节码操作为JBoss实现动态"AOP"框架。
关于java字节码的处理,目前有很多工具,如bcel,asm。不过这些都需要直接跟虚拟机指令打交道。如果你不想了解虚拟机指令,可以采用javassist。javassist是jboss的一个子项目,其主要的优点,在于简单,而且快速。直接使用java编码的形式,而不需要了解虚拟机指令,就能动态改变类的结构,或者动态生成类
联想
由上可知,javassist 可以用来动态生成class文件,并且JVM可以直接加载生成的class文件。
具体作用:
设计一个对接系统,通过动态模型的增删改触发业务系统相应服务的调用。模型增删改方法动态发布为WebService服务。WebService服务采用CXF发布,动态类生成采用Javassist。由于WebService服务类需要添加WebService相关注解。
实战演练
从0开始实战,首先我们认识最初级的由javassist生成class类并且修改保存
package com.bsoft.javassis;
import java.io.FileOutputStream;
import java.io.IOException;
import java.lang.reflect.Method;
import javassist.CannotCompileException;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtConstructor;
import javassist.CtField;
import javassist.CtMethod;
import javassist.CtNewMethod;
import javassist.NotFoundException;
public class TestJavassis {
/**
* @param args
* @author yuzg
* update time 2017-06-20
*/
public static void main(String[] args) {
// TODO Auto-generated method stub
/**
* ClassPool是缓存CtClass对象的容器,所有的CtClass对象都在ClassPool中。
* 所以,CtClass对象很多时,ClassPool会消耗很大的内存,为了避免内存的消耗
* ,创建ClassPool对象时可以使用单例模式,
* 或者对于CtClass对象,调用detach方法将其从ClassPool中移除
* 在ClassPool源码中getDefault 是单例模式生成
*
* public static synchronized ClassPool getDefault() {
if (defaultPool == null) {
defaultPool = new ClassPool(null);
defaultPool.appendSystemPath();
}
return defaultPool;
}
*/
ClassPool classpool =ClassPool.getDefault();
//创建类名
CtClass ctClass = classpool.makeClass("com.bsoft.esb.IEsbInvoker1");
// ctClass.stopPruning(true);
try {
//添加属性
ctClass.addField(CtField.make("private int age;", ctClass));
//添加setAge方法
ctClass.addMethod(CtMethod.make("public void setAge(int age){this.age = age;}", ctClass));
ctClass.addMethod(CtMethod.make("public int getAge(){return this.age;}", ctClass));
byte[] byteArray = ctClass.toBytecode();
FileOutputStream output = new FileOutputStream("D:\\IEsbInvoker1.class");
output.write(byteArray);
output.close();
/***
* 如果下面的判断和解冻方法不加会报错
* java.lang.RuntimeException: com.bsoft.esb.IEsbInvoker1 class is frozen
* 原因解释如下:
* 当CtClass对象通过writeFile()、toClass()、toBytecode()转化为Class后,
* Javassist冻结了CtClass对象,因此,JVM不允许再次加载Class文件,所以不允许对其修改。
*/
if(ctClass.isFrozen()){
ctClass.defrost();
}
ctClass = classpool.get("com.bsoft.esb.IEsbInvoker1");
System.out.println(ctClass);
CtField param = new CtField(classpool.get("java.lang.String"), "name", ctClass);
ctClass.addField(CtField.make("private java.lang.String sex;", ctClass));
ctClass.addField(CtField.make("private java.lang.String name;", ctClass));
ctClass.addMethod(CtNewMethod.setter("setName", param));
ctClass.addMethod(CtNewMethod.getter("getName", param));
// // 添加无参的构造体
// CtConstructor cons = new CtConstructor(new CtClass[] {}, ctClass);
// cons.setBody("{name = \"Brant\";}");
// ctClass.addConstructor(cons);
// 添加有参的构造体
CtConstructor cons = new CtConstructor(new CtClass[] {classpool.get("java.lang.String")}, ctClass);
cons.setBody("{$0.name = $1;}");
ctClass.addConstructor(cons);
byteArray = ctClass.toBytecode();
output = new FileOutputStream("D:\\IEsbInvoker1.class");
output.write(byteArray);
output.close();
//ctClass转class后创建对象
Object o =ctClass.toClass().newInstance();
//这样写会报错:java.lang.ClassNotFoundException: com.bsoft.esb.IEsbInvoker1
/***
* 此时应该还在pool中
*/
// Class.forName("com.bsoft.esb.IEsbInvoker1").newInstance();
//获取方法
Method methodSet = o.getClass().getMethod("setName", new Class[] {String.class});
//反射原理
methodSet.invoke(o, "Alen");
Method getter = o.getClass().getMethod("getName");
System.out.println("name:"+getter.invoke(o, null));
} catch (NotFoundException e) {
System.out.println(e.getMessage());
// TODO Auto-generated catch block
e.printStackTrace();
}catch (CannotCompileException e) {
System.out.println(e.getMessage());
e.printStackTrace();
}
catch (IOException e) {
e.printStackTrace();
}catch (Exception e) {
e.printStackTrace();
}
}
$0, $1, $2, ... 代表的含义:
$
0代表的是
this,$
1代表方法参数的第一个参数、$
2代表方法参数的第二个参数,以此类推,$N代表是方法参数的第N个。例如:
setName(String Name){
$0.name=$1;
}
相当于 this.name=Name;
分别查看两次保存文件前后的class 文件 可用debug断点
首次保存生成的class文件:
package com.bsoft.esb;
public class IEsbInvoker1
{
private int age;
public void setAge(int paramInt)
{
this.age = paramInt;
}
public int getAge()
{
return this.age;
}
}
修改后:
输出:
name:Alen
一个class 文件就生成好了。并且通过java反射机制可以明显看出,和jvm主动调用class类中的方法并无两样。稍后上传jar包资源文件