Dubbo引用Javassist外部框架


Javaassist 就是一個用來處理 Java 字節碼的類庫。它可以在一個已經編譯好的類中添加新的方法,或者是修改已有的方法,並且不需要對字節碼方面有深入的了解。同時也可以去生成一個新的類對象,通過完全手動的方式。
 
引入依賴
<dependency>
    <groupId>org.javassist</groupId>
    <artifactId>javassist</artifactId>
    <version>3.25.0-GA</version>
</dependency>

 

在 Javassist 中,類 Javaassit. CtClass 表示 class 文件。一個 GtClass (編譯時類)對象可以處理一個 class 文件, ClassPool是 CtClass 對象的容器。它按需讀取類文件來構造 CtClass 對象,並且保存 CtClass 對象以便以后使用。
 
需要注意的是 ClassPool 會在內存中維護所有被它創建過的 CtClass,當 CtClass 數量過多時,會占用大量的內存,API中給出的解決方案是有意識的調用CtClass的detach()方法以釋放內存。
 
ClassPool常用方法:
  • getDefault : 返回默認的ClassPool 是單例模式的,一般通過該方法創建我們的ClassPool;
  • appendClassPath, insertClassPath : 將一個ClassPath加到類搜索路徑的末尾位置 或 插入到起始位置。通常通過該方法寫入額外的類搜索路徑,以解決多個類加載器環境中找不到類的尷尬;
  • toClass : 將修改后的CtClass加載至當前線程的上下文類加載器中,CtClass的toClass方法是通過調用本方法實現。需要注意的是一旦調用該方法,則無法繼續修改已經被加載的class;
  • get , getCtClass : 根據類路徑名獲取該類的CtClass對象,用於后續的編輯。
 
CtClass常用方法:
  • freeze : 凍結一個類,使其不可修改;
  • isFrozen : 判斷一個類是否已被凍結;
  • prune : 刪除類不必要的屬性,以減少內存占用。調用該方法后,許多方法無法將無法正常使用,慎用;
  • defrost : 解凍一個類,使其可以被修改。如果事先知道一個類會被defrost, 則禁止調用 prune 方法;
  • detach : 將該class從ClassPool中刪除;
  • writeFile : 根據CtClass生成 .class 文件;
  • toClass : 通過類加載器加載該CtClass。
上面我們創建一個新的方法使用了CtMethod類。CtMthod代表類中的某個方法,可以通過CtClass提供的API獲取或者CtNewMethod新建,通過CtMethod對象可以實現對方法的修改。
 
CtMethod常用方法:
  • insertBefore : 在方法的起始位置插入代碼;
  • insterAfter : 在方法的所有 return 語句前插入代碼以確保語句能夠被執行,除非遇到exception;
  • insertAt : 在指定的位置插入代碼;
  • setBody : 將方法的內容設置為要寫入的代碼,當方法被 abstract修飾時,該修飾符被移除;
  • make : 創建一個新的方法。
 
 
一、生成class文件
 
使用示例
ClassPool pool = ClassPool.getDefault();

// 1.創建一個類
CtClass cc = pool.makeClass("com.test.Student");

// 2.新增一個字段 
CtField param = new CtField(pool.get("java.lang.String"), "username", cc);
param.setModifiers(Modifier.PRIVATE);
cc.addField(param, CtField.Initializer.constant("zhangshan"));

// 3.無參構造
CtConstructor cons = new CtConstructor(new CtClass[] {}, cc);
cons.setBody("{username = \"lisi\";}");
cc.addConstructor(cons);

// 4.有參構造
cons = new CtConstructor(new CtClass[] { pool.get("java.lang.String") }, cc);
// $0=this / $1,$2,$3... 代表方法參數
cons.setBody("{$0.username = $1;}");
cc.addConstructor(cons);

// 5.getter、setter方法
cc.addMethod(CtNewMethod.setter("setUsername", param));
cc.addMethod(CtNewMethod.getter("getUsername", param));

// 6. 創建一個名為print方法,無參數,無返回值,輸出name值
CtMethod ctMethod = new CtMethod(CtClass.voidType, "print", new CtClass[] {}, cc);
ctMethod.setModifiers(Modifier.PUBLIC);
ctMethod.setBody("{System.out.println(\"username : \" + username);}");
cc.addMethod(ctMethod);

// 這里會將這個創建的類對象編譯為.class文件
cc.writeFile("D:\\workspace-neno\\spring\\target");

 

生成一個Student.class文件,經過反編譯
public class Student {
  private String username = "zhangshan";
  
  public Student() {
    this.username = "lisi";
  }
  
  public Student(String paramString) {
    this.username = paramString;
  }
  
  public void setUsername(String paramString) {
    this.username = paramString;
  }
  
  public String getUsername() {
    return this.username;
  }
  
  public void print() {
    System.out.println("username : " + this.username);
  }
}

 

 
 
二、如何使用
 
這里不寫入文件,直接實例化
(1)通過反射方式調用
// 實例化
Object student = cc.toClass().newInstance();
System.out.println(student); // com.test.Student@59690aa4
// 調用方法
Method setUsername = student.getClass().getMethod("setUsername", String.class);
setUsername.invoke(student, "caoxiaobo");
Method print = student.getClass().getMethod("print");
print.invoke(student);

 

(2)通過讀取 .class 文件的方式調用
// ClassPool pool = ClassPool.getDefault();
// 設置類路徑
pool.appendClassPath("D:\\workspace-neno\\spring\\target");
CtClass ctClass = pool.get("com.test.Student");
Object student = ctClass.toClass().newInstance();
System.out.println(student);

 

 
(3)通過接口的方式
public interface StudentIF {
    public String getUsername();
    public void setUsername(String paramString);    
    public void print();
}

 

// ClassPool pool = ClassPool.getDefault();
pool.appendClassPath("D:\\workspace-neno\\spring\\target");

// 獲取接口
CtClass codeClassI = pool.get("com.javassist.StudentIF");
// 獲取上面生成的類
CtClass ctClass = pool.get("com.test.Student");
// 使代碼生成的類,實現 StudentIF 接口
ctClass.setInterfaces(new CtClass[]{codeClassI});

// 以下通過接口直接調用 強轉
StudentIF student = (StudentIF)ctClass.toClass().newInstance();
System.out.println(student.getUsername());
student.setUsername("caoxiaobo");
student.print();

 

 
 
三、修改操作
public class StudentService {
    
    public void getStudent() {
        System.out.println("get student");
    }

    public void study() {
        System.out.println("learn javassist");
    }
}

 

 
1.調用AOP功能的方法
    insertBefore
    insertAfter
2.新增方法
    addMethod
ClassPool pool = ClassPool.getDefault();
CtClass cc = pool.get("com.javassist.StudentService");

CtMethod study = cc.getDeclaredMethod("study");
study.insertBefore("System.out.println(\"學習之前...\");");
study.insertAfter("System.out.println(\"學習之后...\");");

// 新增一個方法
CtMethod ctMethod = new CtMethod(CtClass.voidType, "sleep", new CtClass[] {}, cc);
ctMethod.setModifiers(Modifier.PUBLIC);
ctMethod.setBody("{System.out.println(\"准備睡覺,晚安!\");}");
cc.addMethod(ctMethod);

Object studentService = cc.toClass().newInstance();
// 調用 study 方法
Method _study = studentService.getClass().getMethod("study");
_study.invoke(studentService);
// 調用 joinFriend 方法
Method sleep = studentService.getClass().getMethod("sleep");
sleep.invoke(studentService);

 

 
 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM