什麼是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包資源文件