1. 程式人生 > >Javassist (位元組碼修改工具)用法

Javassist (位元組碼修改工具)用法

Javassist簡介

javassist是一個開源的分析、編輯和建立Java位元組碼的類庫。可以對.class進行直接修改和建立,直接使用java程式設計的方式,不需要了解虛擬機器的指令,就能動態的改變類的結構或者動態生成

Javassist 應用場景

應用效能的監控、動態代理等

重要類簡介:

  • ClassPool:javassist的類池,使用ClassPool 類可以跟蹤和控制所操作的類,它的工作方式與 JVM 類裝載器非常相似。

     使用方法: 

ClassPool pool = new ClassPool();
pool.appendSystemPath();

或者

ClassPool classPool = new ClassPool();
classPool.insertClassPath(new LoaderClassPath(loader));
  • CtClass: CtClass提供了檢查類資料(如欄位和方法)以及在類中新增新欄位、方法和建構函式、以及改變類、父類和介面的方法。不過,Javassist 並未提供刪除類中欄位、方法或者建構函式的任何方法。

           建立新類: pool.makeClass("com.ty.pojo.User");;

           載入已有類:pool.get("com.ty.javaagent.UserServiceImpl")

  • CtField:用來訪問域
  • CtMethod :用來訪問方法
  • CtConstructor:用來訪問構造器
  • 生成並裝載Class toClass()或者 toBytecode()

語法:

符號
$0,$1,$2....$0表示this,其他的表示實際的引數
$args引數陣列,相當於new Object[]{$1,$2,.....}
$$所有的引數
$r用於型別轉換表示返回值的型別
$w將基礎型別轉換為一個包裝型別,如Integer a = ($w)5; 表示5轉換為 Integer。 如果不是基本型別則什麼都不做

a) 不能引用 在方法中其他地方定義的區域性變數

b) 不會對型別進行強制檢查

c) 使用特殊的專案語法符號

示例

匯入javassist jar

      <dependency>
            <groupId>org.javassist</groupId>
            <artifactId>javassist</artifactId>
            <version>3.18.1-GA</version>
            <scope>compile</scope>
        </dependency>

1.建立新類

	@Test
	// 建立User類
	public void createUser() throws Exception {
		ClassPool pool = new ClassPool();
		pool.appendSystemPath();
		// 定義類
		CtClass userClass = pool.makeClass("com.ty.pojo.User");
		// id屬性
		CtField idField = new CtField(CtClass.longType, "id", userClass);
		userClass.addField(idField);

		// name 屬性
		CtField nameField = new CtField(pool.get("java.lang.String"), "name", userClass);
		userClass.addField(nameField);

		CtMethod getMethod = CtNewMethod.make("public String getName(){return this.name;}", userClass);
		CtMethod setMethod = CtNewMethod.make("public void setName(String name){this.name = name;}", userClass);

		userClass.addMethod(setMethod);
		userClass.addMethod(getMethod);

		Class<?> clazz = userClass.toClass();

		System.err.println("class:" + clazz.getName());
		System.err.println("屬性列表----------");
		Field[] fields = clazz.getDeclaredFields();
		for (Field field : fields) {
			System.out.println(field.getType() + ":" + field.getName());
		}

		System.err.println("方法列表------------------------");
		Method[] methods = clazz.getDeclaredMethods();
		for (Method method : methods) {
			System.out.println(method.getReturnType() + "-" + method.getName() + "-"
					+ Arrays.toString(method.getParameterTypes()));
		}

	}

結果:

屬性列表----------
long  id
class java.lang.String  name
------------------------------------------------
方法列表------------------------
class java.lang.String   getName  []
void   setName  [class java.lang.String]

2.修改方法,統計方法的執行時間

@Test
	public void updateGetUserInfoMethod() throws Exception {
		ClassPool pool = new ClassPool();
		pool.appendSystemPath();
		// 定義類
		CtClass userServiceClass = pool.get("com.ty.javaagent.UserServiceImpl");
		// 需要修改的方法
		CtMethod method = userServiceClass.getDeclaredMethod("getUserInfo");
		// 修改原有的方法
		method.setName("getUserInfo$agent");
		// 建立新的方法,複製原來的方法
		CtMethod newMethod = CtNewMethod.copy(method, "getUserInfo", userServiceClass, null);
		// 注入的程式碼
		StringBuffer body = new StringBuffer();

		body.append("{\nlong start = System.currentTimeMillis();\n");
		// 呼叫原有程式碼,類似於method();($$)表示所有的引數
		body.append("getUserInfo$agent($$);\n");
		body.append("System.out.println(\" take \" +\n (System.currentTimeMillis()-start) + " + "\" ms.\");\n");

		body.append("}");
		newMethod.setBody(body.toString());
		// 增加新方法
		userServiceClass.addMethod(newMethod);

		UserServiceImpl userServiceImpl = (UserServiceImpl) userServiceClass.toClass().newInstance();
		userServiceImpl.getUserInfo();
	}

結果:

user:1
 take 2000 ms.