Javassist詳解

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>


由於JVM通過字節碼的二進制信息加載類的,那麼如果我們在程序運行期,遵循Java編譯系統組織.class文件的格式和結構,生成相應的二進制數據,然後再把這個二進制數據加載轉換成相應的類,這樣,就完成了在代碼中,動態創建一個類的能力了。



在運行期間可以按照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文件。



發佈了73 篇原創文章 · 獲贊 18 · 訪問量 23萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章