一. API相關java
最近使用Javassist框架開發了一些功能,使用的過程當中遇見了很多問題,將使用方法總結下,以防往後重複踩坑。express
先來看一段代碼:編程
ClassPool classPool = ClassPool.getDefault(); classPool.appendClassPath(new LoaderClassPath(Thread.currentThread().getContextClassLoader())); CtClass ctClass = classPool.getCtClass("cn.com.Test"); CtMethod ctMethod = CtNewMethod.make("public void helloWorld(){ System.out.println(\"hello world!\"); }", ctClass); ctClass.addMethod(ctMethod); ctClass.toClass();
這段代碼的功能很簡單,在建立了一個默認的classpool後,加入當前線程的上下文類加載器做爲額外的類搜索路徑,獲取Test類後向其中加入了helloWorld這個方法,並把修改後的類加載至當前線程所在的上下文類加載器中。app
能夠發現利用javassist框架進行動態編程是比較輕鬆簡單的,來看幾個比較重要的API:框架
ClassPoolui
ClassPool是CtClass對象的容器,每個CtClass對象都必須從ClassPool中獲取。this
ClassPool自身能夠造成層級結構,其工做機制與java的類加載器相似,只有當父節點找不到類文件時,纔會調用子節點的get()方法。經過設置 ClassPath.childFirstLookup 屬性能夠調整其工做流程。spa
須要注意的是ClassPool會在內存中維護全部被它建立過的CtClass,當CtClass數量過多時,會佔用大量的內存,API中給出的解決方案是
週期性的調用compress方法或 從新建立ClassPool 或 有意識的調用CtClass的detach()方法以釋放內存。.net須要關注的方法:線程
1. getDefault : 返回默認的ClassPool,單例模式!通常經過該方法建立咱們的ClassPool。
2. appendClassPath, insertClassPath : 將一個ClassPath加到類搜索路徑的末尾位置 或 插入到起始位置。一般經過該方法寫入額外的類搜索路徑,以解決多個類加載器環境中找不到類的尷尬。
3. toClass : 將修改後的CtClass加載至當前線程的上下文類加載器中,CtClass的toClass方法是經過調用本方法實現。須要注意的是一旦調用該方法,則沒法繼續修改已經被加載的class。
4. get , getCtClass : 根據類路徑名獲取該類的CtClass對象,用於後續的編輯。
ClassPath
ClassPath是一個接口,表明類的搜索路徑,含有具體的搜索實現。當經過其它途徑沒法獲取要編輯的類時,能夠嘗試定製一個本身的ClassPath。API提供的實現中值得關注的有:
1. ByteArrayClassPath : 將類以字節碼的形式加入到該path中,ClassPool 能夠從該path中生成所需的CtClass。
2. ClassClassPath : 經過某個class生成的path,經過該class的classloader來嘗試加載指定的類文件。
3. LoaderClassPath : 經過某個classloader生成path,並經過該classloader搜索加載指定的類文件。須要注意的是該類加載器以弱引用的方式存在於path中,當不存在強引用時,隨時可能會被清理。
CtClass
javassist爲每一個須要編輯的class都建立了一個CtClass對象,經過對CtClass對象的操做來實現對class的編輯工做。
該類方法較多,此處列出須要重點關注的方法:
1. freeze : 凍結一個類,使其不可修改。
2. isFrozen : 判斷一個類是否已被凍結。
3. prune : 刪除類沒必要要的屬性,以減小內存佔用。調用該方法後,許多方法沒法將沒法正常使用,慎用。
4. defrost : 解凍一個類,使其能夠被修改。若是事先知道一個類會被defrost, 則禁止調用 prune 方法。
5. detach : 將該class從ClassPool中刪除。
6. writeFile : 根據CtClass生成 .class 文件。
7. toClass : 經過類加載器加載該CtClass。
CtMethod
CtMthod表明類中的某個方法,能夠經過CtClass提供的API獲取或者CtNewMethod新建,經過CtMethod對象能夠實現對方法的修改。
須要注意的是寫入方法體的代碼沒法訪問在其它地方定義的成員變量,一些比較重要的方法:
1. insertBefore : 在方法的起始位置插入代碼。
2. insterAfter : 在方法的全部 return 語句前插入代碼以確保語句可以被執行,除非遇到exception。
3. insertAt : 在指定的位置插入代碼。
4. setBody : 將方法的內容設置爲要寫入的代碼,當方法被 abstract修飾時,該修飾符被移除。
5. make : 建立一個新的方法。
CtNewMethod
提供各類靜態方法來操做CtMethod,不進行詳細描述,有興趣能夠看下API。
特殊符號
$0, $1, $2, ... this and actual parameters $args An array of parameters. The type of $args is Object[]. $$ All actual parameters.For example, m($$) is equivalent to m($1,$2,...) $cflow(...) cflow variable $r The result type. It is used in a cast expression. $w The wrapper type. It is used in a cast expression. $_ The resulting value $sig An array of java.lang.Class objects representing the formal parameter types $type A java.lang.Class object representing the formal result type. $class A java.lang.Class object representing the class currently edited.
二. 使用場景總結
1. 實現代碼插入功能:
CtClass ctClass = classPool.getCtClass("com.netease.HelloWorld"); CtMethod ctMethod = ctClass.getDeclaredMethod("sayHello"); ctMethod.insertAfter("System.out.println(\"Hello world!\");"); ctClass.toClass();
2. 建立一個完整的類:
ClassPool classPool = ClassPool.getDefault(); CtClass ctClass = classPool.makeClass("com.netease.Class"); CtField ctField = new CtField(classPool.get("java.lang.String"), "teacher", ctClass); ctField.setModifiers(Modifier.PRIVATE); ctClass.addField(ctField); ctClass.addMethod(CtNewMethod.setter("setTeacher", ctField)); ctClass.addMethod(CtNewMethod.getter("getTeacher", ctField)); ctClass.writeFile();
3. 實現攔截器功能:
CtMethod ctMethod = clazz.getDeclaredMethod(method); String newName = method + "New"; ctMethod.setName(newName); CtMethod newCtMethod = CtNewMethod.copy(ctMethod, method, clazz, null); String type = ctMethod.getReturnType().getName(); StringBuilder body = new StringBuilder(); body.append("{\n System.out.println(\"Before Method Execute...\");\n"); if(!"void".equals(type)) { body.append(type).append(" result = "); } body.append(newName).append("($$);\n"); body.append("System.out.println(\"After Method Execute...\");;\n"); if(!"void".equals(type)) { body.append("return result;\n"); } body.append("}"); newCtMethod.setBody(body.toString()); clazz.addMethod(newCtMethod);