java之 javassist簡單使用

0x0一、javassist介紹

什麼是javassist,這個詞一聽起來感受就很懵,對吧~java

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 使用

這裏主要講一下主要的幾個類:app

一、ClassPool

ClassPool是CtClass對象的容器,它按需讀取類文件來構造CtClass對象,而且保存CtClass對象以便之後使用。ide

從實現的角度來看,ClassPool 是一個存儲 CtClass 的 Hash 表,類的名稱做爲 Hash 表的 key。ClassPool 的 get() 函數用於從 Hash 表中查找 key 對應的 CtClass 對象。若是沒有找到,get() 函數會建立並返回一個新的 CtClass 對象,這個新對象會保存在 Hash 表中。函數

須要注意的是ClassPool會在內存中維護全部被它建立過的CtClass,當CtClass數量過多時,會佔用大量的內存,API中給出的解決方案是從新建立ClassPool 或 有意識的調用CtClass的detach()方法以釋放內存。this

經常使用方法:

//返回默認的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)

二、CtClass

CtClass類表示一個class文件,每一個CtClass對象都必須從ClassPool中獲取,CtClass須要關注的方法:idea

經常使用方法:

//更改超類,除非此對象表示接口。
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。

三、CtMethod

CtMethod:表示類中的方法。spa

insertBefore:在方法的起始位置插入代碼;
insterAfter: 在方法的全部 return 語句前插入代碼以確保語句可以被執行,除非遇到exception;
insertAt:	 在指定的位置插入代碼;
setBody:	 將方法的內容設置爲要寫入的代碼,當方法被abstract修飾時,該修飾符被移除;
make:		 建立一個新的方法。

四、CtConstructor

CtConstructor的實例表示一個構造函數。它可能表明一個靜態構造函數(類初始化器)。能夠經過CtConstructor.make方法建立.net

經常使用方法

//設置構造函數主體。
void	setBody(java.lang.String src)	

//從另外一個構造函數複製一個構造函數主體。
void	setBody(CtConstructor src, ClassMap map)	

//複製此構造函數並將其轉換爲方法。
CtMethod	toMethod(java.lang.String name, CtClass declaring)

五、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()));

將本類所在的路徑插入到搜索路徑中

六、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);
    }

}

七、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
    }
}

0x0三、建立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);
 	}
}

0x0四、調用生成的類對象

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();
// 下面和經過反射的方式同樣去使用
相關文章
相關標籤/搜索