0x01、javassist介紹
什么是javassist,這個詞一聽起來感覺就很懵,對吧~
public void DynGenerateClass() {
ClassPool pool = ClassPool.getDefault();
CtClass ct = pool.makeClass("com.ideaGenerateClass");//創建類
ct.setInterfaces(new CtClass[]{pool.makeInterface("java.lang.Cloneable")});//讓類實現Cloneable接口
try {
CtField f= new CtField(CtClass.intType,"id",ct);//獲得一個類型為int,名稱為id的字段
f.setModifiers(AccessFlag.PUBLIC);//將字段設置為public
ct.addField(f);//將字段設置到類上
//添加構造函數
CtConstructor constructor=CtNewConstructor.make("public GeneratedClass(int pId){this.id=pId;}",ct);
ct.addConstructor(constructor);
//添加方法
CtMethod helloM=CtNewMethod.make("public void hello(String des){ System.out.println(des);}",ct);
ct.addMethod(helloM);
ct.writeFile();//將生成的.class文件保存到磁盤
//下面的代碼為驗證代碼
Field[] fields = ct.toClass().getFields();
System.out.println("屬性名稱:" + fields[0].getName() + " 屬性類型:" + fields[0].getType());
} catch (CannotCompileException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch (NotFoundException e) {
e.printStackTrace();
}
}
參考該篇文章java編程-javassist,
0x02 Javassist 使用
這里主要講一下主要的幾個類:
1、ClassPool
ClassPool是CtClass對象的容器,它按需讀取類文件來構造CtClass對象,並且保存CtClass對象以便以后使用。
從實現的角度來看,ClassPool 是一個存儲 CtClass 的 Hash 表,類的名稱作為 Hash 表的 key。ClassPool 的 get() 函數用於從 Hash 表中查找 key 對應的 CtClass 對象。如果沒有找到,get() 函數會創建並返回一個新的 CtClass 對象,這個新對象會保存在 Hash 表中。
需要注意的是ClassPool會在內存中維護所有被它創建過的CtClass,當CtClass數量過多時,會占用大量的內存,API中給出的解決方案是重新創建ClassPool 或 有意識的調用CtClass的detach()方法以釋放內存。
常用方法:
//返回默認的ClassPool,一般通過該方法創建我們的ClassPool;
static ClassPool getDefault()
//在搜索路徑的開頭插入目錄或jar(或zip)文件。
ClassPath insertClassPath(java.lang.String pathname)
//ClassPath在搜索路徑的開頭插入一個對象。
ClassPath insertClassPath(ClassPath cp)
//獲取類加載器toClass(),getAnnotations()在 CtClass等
java.lang.ClassLoader getClassLoader()
//從源中讀取類文件,並返回對CtClass 表示該類文件的對象的引用。
CtClass get(java.lang.String classname)
//將ClassPath對象附加到搜索路徑的末尾。
ClassPath appendClassPath(ClassPath cp)
//創建一個新的public類
CtClass makeClass(java.lang.String classname)
2、CtClass
CtClass
類表示一個class文件,每個CtClass對象
都必須從ClassPool
中獲取,CtClass需要關注的方法:
常用方法:
//更改超類,除非此對象表示接口。
void setSuperclass(CtClass clazz)
//將此類轉換為java.lang.Class對象。
java.lang.Class<?> toClass(java.lang.invoke.MethodHandles.Lookup lookup)
//將該類轉換為類文件。
byte[] toBytecode()
//將由此CtClass 對象表示的類文件寫入當前目錄。
void writeFile()
//將由此CtClass 對象表示的類文件寫入本地磁盤。
void writeFile(java.lang.String directoryName)
//在當前類中創建了一個靜態代碼塊
CtConstructor makeClassInitializer()
freeze:凍結一個類,使其不可修改;
isFrozen:判斷一個類是否已被凍結;
defrost:解凍一個類,使其可以被修改;
prune:刪除類不必要的屬性,以減少內存占用。調用該方法后,許多方法無法將無法正常使用,慎用;
detach:將該class從ClassPool中刪除;
writeFile:根據CtClass生成.class文件;
toClass:通過類加載器加載該CtClass;
addField,removeField:添加/移除一個CtField;
addMethod,removeMethod:添加/移除一個CtMethod;
addConstructor,removeConstructor:添加/移除一個CtConstructor。
3、CtMethod
CtMethod
:表示類中的方法。
insertBefore:在方法的起始位置插入代碼;
insterAfter: 在方法的所有 return 語句前插入代碼以確保語句能夠被執行,除非遇到exception;
insertAt: 在指定的位置插入代碼;
setBody: 將方法的內容設置為要寫入的代碼,當方法被abstract修飾時,該修飾符被移除;
make: 創建一個新的方法。
4、CtConstructor
CtConstructor的實例表示一個構造函數。它可能代表一個靜態構造函數(類初始化器)。可以通過CtConstructor.make
方法創建
常用方法
//設置構造函數主體。
void setBody(java.lang.String src)
//從另一個構造函數復制一個構造函數主體。
void setBody(CtConstructor src, ClassMap map)
//復制此構造函數並將其轉換為方法。
CtMethod toMethod(java.lang.String name, CtClass declaring)
5、ClassPath
該類作用是用於通過 getResourceAsStream() 在 java.lang.Class 中獲取類文件的搜索路徑。
構造方法:
//創建一個搜索路徑。
ClassClassPath(java.lang.Class<?> c)
常見方法:
//獲取指定類文件的URL。
java.net.URL find (java.lang.String classname)
//通過獲取類文getResourceAsStream()。
java.io.InputStream openClassfile(java.lang.String classname)
代碼實例:
ClassPool pool = ClassPool.getDefault();
在默認系統搜索路徑獲取ClassPool
對象。
如果需要修改類搜索的路徑需要使用insertClassPath
方法進行修改。
pool.insertClassPath(new ClassClassPath(this.getClass()));
將本類所在的路徑插入到搜索路徑中
6、oBytecode
package com.demo;
import javassist.*;
import java.io.IOException;
import java.util.Arrays;
public class testssit {
public static void main(String[] args) throws NotFoundException, CannotCompileException, IOException {
ClassPool pool = ClassPool.getDefault();
pool.insertClassPath(new ClassClassPath(demo.class.getClass()));
CtClass ctClass = pool.get("com.demo.test");
ctClass.setSuperclass(pool.get("com.demo.test"));
// System.out.println(ctClass);
byte[] bytes = ctClass.toBytecode();
String s = Arrays.toString(bytes);
System.out.println(s);
}
}
7、toClass
toClass:將修改后的CtClass加載至當前線程的上下文類加載器中,CtClass的toClass方法是通過調用本方法實現。
Hello類:
public class Hello {
public void say() {
System.out.println("Hello");
}
}
Test 類
public class Test {
public static void main(String[] args) throws Exception {
ClassPool cp = ClassPool.getDefault();//在默認系統搜索路徑獲取ClassPool對象。
CtClass cc = cp.get("com.demo.Hello"); //獲取hello類的
CtMethod m = cc.getDeclaredMethod("say"); //獲取hello類的say方法
m.insertBefore("{ System.out.println(\"Hello.say():\"); }");//在正文的開頭插入字節碼
Class c = cc.toClass();//將此類轉換為java.lang.Class對象
Hello h = (Hello)c.newInstance(); //反射創建對象並進行強轉
h.say();調用方法say
}
}
0x03、創建Class文件
public class App {
public static void main(String[] args) {
try {
createPerson();
} catch (Exception e) {
e.printStackTrace();
}
}
private static void createPerson() throws Exception {
ClassPool pool = ClassPool.getDefault();
// 1. 創建一個空類
CtClass cc = pool.makeClass("com.hearing.demo.Person");
// 2. 新增一個字段 private String name = "hearing";
CtField param = new CtField(pool.get("java.lang.String"), "name", cc);
param.setModifiers(Modifier.PRIVATE);
cc.addField(param, CtField.Initializer.constant("hearing"));
// 3. 生成 getter、setter 方法
cc.addMethod(CtNewMethod.setter("setName", param));
cc.addMethod(CtNewMethod.getter("getName", param));
// 4. 添加無參的構造函數
CtConstructor cons = new CtConstructor(new CtClass[]{}, cc);
cons.setBody("{name = \"hearing\";}");
cc.addConstructor(cons);
// 5. 添加有參的構造函數
cons = new CtConstructor(new CtClass[]{pool.get("java.lang.String")}, cc);
// $0=this / $1,$2,$3... 代表方法參數
cons.setBody("{$0.name = $1;}");
cc.addConstructor(cons);
// 6. 創建一個名為printName方法,無參數,無返回值,輸出name值
CtMethod ctMethod = new CtMethod(CtClass.voidType, "printName", new CtClass[]{}, cc);
ctMethod.setModifiers(Modifier.PUBLIC);
ctMethod.setBody("{System.out.println(name);}");
cc.addMethod(ctMethod);
//這里會將這個創建的類對象編譯為.class文件
cc.writeFile("../");
}
}
創建的class文件如下
public class Person {
private String name = "hearing";
public void setName(String var1) {
this.name = var1;
}
public String getName() {
return this.name;
}
public Person() {
this.name = "hearing";
}
public Person(String var1) {
this.name = var1;
}
public void printName() {
System.out.println(this.name);
}
}
0x04、調用生成的類對象
1.通過反射的方式調用:
Object person = cc.toClass().newInstance();
Method setName = person.getClass().getMethod("setName", String.class);
setName.invoke(person, "hearing1");
Method execute = person.getClass().getMethod("printName");
execute.invoke(person);
2.通過讀取class文件的方式調用:
ClassPool pool = ClassPool.getDefault();
// 設置類路徑
pool.appendClassPath("../");
CtClass ctClass = pool.get("com.hearing.demo.Person");
Object person = ctClass.toClass().newInstance();
// 下面和通過反射的方式一樣去使用