javassist動態生成class

 

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

javassist.jar下載路徑:點擊下載javassist

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章