1. 簡介
在博客中我們有提到關於Java反射,Java反射可以實現運行時加載,探知,自省,使用編譯期完全未知的classes,獲悉其完整構造,並生成其實體對象,或對fields設值。
自審:通過Java的反射機制能夠探知到java類的基本機構,這種對java類結構探知的能力,我們稱爲Java類的“自審”。
Java的反射原理最典型的應用就是各種java IDE:比如Jcreateor,eclipse,idea等,當我們構造出一個對象時,去調用該對象的方法和屬性的的時候。一按點,IDE工具就會自動的把該對象能夠使用的素有的方法和屬性全部列出來,供我們進行選擇。這就是利用了Java反射的原理,是對我們創建對象的探知,自審的過程。Java反射能夠將二進制class文件加載到虛擬機中並接着可以生成類的實例,而class文件的生成則是由java編譯器由編譯器生成,那麼是否存在一種技術,我們可以在程序運行時,可以動態創建類,更改類的屬性,添加類的方法,以及動態生成class文件呢,Javassist就會幫我們完成這種功能。
在介紹Javassist之前,我們先來簡單介紹下class文件及其加載
2. class文件簡介及其加載
Java編譯器編譯好Java文件之後,產生.class文件在磁盤中,class字節碼文件是根據JVM虛擬機規範中規定的字節碼組織規則生成的。這種class文件是二進制文件,內容是隻有JVM虛擬機能夠識別的機器碼。JVM虛擬機讀取字節碼文件,讀取二進制數據,加載到內存中,解析.class文件內的信息,生成對應的Class對象。
下面通過一段代碼演示手動加載class文件到系統內,轉換成class對象,然後再實例化的過程:
a. 先創建一個Person類
<span style="font-size:14px;">public class Person {
private String name;
private String address;
/**
* @return the name
*/
public String getName() {
return name;
}
/**
* @param name the name to set
*/
public void setName(String name) {
this.name = name;
}
/**
* @return the address
*/
public String getAddress() {
return address;
}
/**
* @param address the address to set
*/
public void setAddress(String address) {
this.address = address;
}
/* (non-Javadoc)
* @see java.lang.Object#toString()
*/
@Override
public String toString() {
return "Person [name=" + name + ", address=" + address + "]";
}
}</span>
b. 自定義一個ClassLoader
<span style="font-size:14px;">/**
* <pre>
* 項目名: javassist-demo1
* 類名: CustomClassLoader.java
* 類描述:自定義類加載器
* </pre>
*/
public class CustomClassLoader extends ClassLoader {
/**
* @param b
* @param off
* @param len
* @return
*/
@SuppressWarnings("deprecation")
public Class<?> defineCustomClass(byte[] b, int off, int len) {
return super.defineClass(b, off, len);
}
}</span>
c. 測試實例
<span style="font-size:14px;">/**
* <pre>
* 項目名: javassist-demo1
* 類名: TestMain.java
* 類描述:
*/
public class TestMain {
public static void main(String[] args) throws FileNotFoundException, IOException, InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException, NoSuchMethodException, SecurityException{
File file = new File(".");
System.out.println(file.getCanonicalPath());
InputStream input = new FileInputStream(file.getCanonicalPath()+"\\target\\classes\\cn\\test\\Person.class");
byte[] result = new byte[1024];
int count = input.read(result);
CustomClassLoader classLoader = new CustomClassLoader();
Class clazz = classLoader.defineCustomClass(result, 0, count);
System.out.println(clazz.getName());
}
}</span>
在運行期間可以按照Java虛擬機規範對class文件的組織規則生成對應的二進制字節碼。當前有很多開源框架可以完成這些功能,如ASM,Javassist。
3. Java字節碼生成開源框架-Javassist
Javassist是一個開源的分析、編輯和創建Java字節碼的類庫。關於java字節碼的處理,目前有很多工具,如bcel,asm。不過這些都需要直接跟虛擬機指令打交道。如果你不想了解虛擬機指令,可以採用javassist。javassist是jboss的一個子項目,其主要的優點,在於簡單,而且快速。直接使用java編碼的形式,而不需要了解虛擬機指令,就能動態改變類的結構,或者動態生成類。簡而言之:Javassist 能夠轉換現有類的基本內容,或創建一個新類。
Javassist 可以檢查、編輯以及創建 Java 二進制類。檢查方面基本上與通過 Reflection API 直接在 Java 中進行的一樣。Javassist 使用類池 javassist.ClassPool 類跟蹤和控制所操作的類。其工作方式與 JVM 類裝載器非常相似,但是有一個重要的區別是它不是將裝載的、要執行的類作爲應用程序的一部分鏈接,類池使所裝載的類可以通過 Javassist API 作爲數據使用。可以使用默認的類池,它是從 JVM 搜索路徑中裝載的,也可以定義一個搜索自定義路徑列表的類池。甚至可以直接從字節數組或者流中裝載二進制類,以及從頭開始創建新類。
裝載到類池中的類由 javassist.CtClass 實例表示。與標準的 Java java.lang.Class 類一樣, CtClass 提供了檢查類數據(如字段和方法)的方法。不過,這只是 CtClass 的部分內容,它還定義了在類中添加新字段、方法和構造函數、以及改變類、父類和接口的方法。奇怪的是,Javassist 沒有提供刪除一個類中字段、方法或者構造函數的任何方法。字段、方法和構造函數分別由 javassist.CtField、javassist.CtMethod 和 javassist.CtConstructor 的實例表示。這些類定義了修改由它們所表示的對象的所有方法的方法,包括方法或者構造函數中的實際字節碼內容。
下面就是用javassist的api在程序運行期創建一個新類,然後實例化:
public class DynamicCreateObject {
<span style="font-size:14px;">
public static void main(String[] args) throws NotFoundException,
CannotCompileException, IllegalAccessException,
InstantiationException, NoSuchMethodException,
InvocationTargetException, ClassNotFoundException, IOException {
DynamicCreateObject dco = new DynamicCreateObject();
Object student1 = null, team = null;
Map<String, Object> fieldMap = new HashMap<String, Object>();// 屬性-取值map
fieldMap.put("name", "xiao ming");
fieldMap.put("age", 27);
student1 = dco.addField("Student", fieldMap);// 創建一個名稱爲Student的類
Class c = Class.forName("Student");
Object s1 = c.newInstance();// 創建Student類的對象
Object s2 = c.newInstance();
dco.setFieldValue(s1, "name", " xiao ming ");// 創建對象s1賦值
dco.setFieldValue(s2, "name", "xiao zhang");
fieldMap.clear();
List<Object> students = new ArrayList<Object>();
students.add(s1);
students.add(s2);
fieldMap.put("students", students);
team = dco.addField("Team", fieldMap);// //創建一個名稱爲Team的類
Field[] fields = team.getClass().getDeclaredFields();
if (fields != null) {
for (Field field : fields)
System.out.println(field.getName() + "=" + dco.getFieldValue(team, field.getName()));
}
}
/**
*
* 爲對象動態增加屬性,並同時爲屬性賦值
* @param className 需要創建的java類的名稱
* @param fieldMap 字段-字段值的屬性map,需要添加的屬性
* @return
* @throws NotFoundException
* @throws CannotCompileException
* @throws IOException
*/
@SuppressWarnings("rawtypes")
public Object addField(String className, Map<String, Object> fieldMap) throws NotFoundException, CannotCompileException, IllegalAccessException,
InstantiationException, IOException {
ClassPool pool = ClassPool.getDefault();// 獲取javassist類池
CtClass ctClass = pool.makeClass(className, pool.get(Object.class.getName()));// 創建javassist類
// 爲創建的類ctClass添加屬性
Iterator it = fieldMap.entrySet().iterator();
StringBuilder sb = new StringBuilder();
while (it.hasNext()) { // 遍歷所有的屬性
Map.Entry entry = (Map.Entry) it.next();
String fieldName = (String) entry.getKey();
Object fieldValue = entry.getValue();
// 增加屬性,這裏僅僅是增加屬性字段
String fieldType = fieldValue.getClass().getName();
CtField ctField = new CtField(pool.get(fieldType), fieldName, ctClass);
ctField.setModifiers(Modifier.PUBLIC);
ctClass.addField(ctField);
}
Class c = ctClass.toClass();// 爲創建的javassist類轉換爲java類
ctClass.writeFile("E://test");
Object newObject = c.newInstance();// 爲創建java對象
// 爲創建的類newObject屬性賦值
it = fieldMap.entrySet().iterator();
while (it.hasNext()) { // 遍歷所有的屬性
Map.Entry entry = (Map.Entry) it.next();
String fieldName = (String) entry.getKey();
Object fieldValue = entry.getValue();
// 爲屬性賦值
this.setFieldValue(newObject, fieldName, fieldValue);
}
return newObject;
}
/**
*
* 獲取對象屬性賦值
* @param dObject
* @param fieldName 字段別名
* @return
*/
public Object getFieldValue(Object dObject, String fieldName) {
Object result = null;
try {
Field fu = dObject.getClass().getDeclaredField(fieldName); // 獲取對象的屬性域
try {
fu.setAccessible(true); // 設置對象屬性域的訪問屬性
result = fu.get(dObject); // 獲取對象屬性域的屬性值
} catch (IllegalAccessException e) {
e.printStackTrace();
}
} catch (NoSuchFieldException e) {
e.printStackTrace();
}
return result;
}
/**
*
* 給對象屬性賦值
* @param dObject
* @param fieldName
* @param val
* @return
*/
public Object setFieldValue(Object dObject, String fieldName, Object val) {
Object result = null;
try {
Field fu = dObject.getClass().getDeclaredField(fieldName); // 獲取對象的屬性域
try {
fu.setAccessible(true); // 設置對象屬性域的訪問屬性
fu.set(dObject, val); // 設置對象屬性域的屬性值
result = fu.get(dObject); // 獲取對象屬性域的屬性值
} catch (IllegalAccessException e) {
e.printStackTrace();
}
} catch (NoSuchFieldException e) {
e.printStackTrace();
}
return result;
}
}</span>
程序中我們設置了新的class文件存放路徑:E:\Test文件夾下,打開文件夾,發現下面就有兩個Student.class文件和Team.class文件。