Markdown版本筆記 | 個人GitHub首頁 | 個人博客 | 個人微信 | 個人郵箱 |
---|---|---|---|---|
MyAndroidBlogs | baiqiantao | baiqiantao | bqt20094 | baiqiantao@sina.com |
官網
GitHub
WIKI -很是詳細java
Java bytecode engineering toolkit since 1999git
Javassist是一個開源的分析、編輯和建立Java字節碼的類庫
。是由日本的 Shigeru Chiba 所建立的,它已加入了開放源代碼 JBoss 應用服務器項目,經過使用 Javassist 對字節碼操做爲 JBoss 實現動態"AOP"框架。github
關於java字節碼的處理,目前有不少工具,如bcel,ASM
。不過這些都須要直接跟虛擬機指令
打交道。若是你不想了解虛擬機指令,能夠採用javassist。javassist是jboss的一個子項目,其主要的優勢在於簡單、快速。直接使用java編碼的形式,而不須要了解虛擬機指令,就能動態改變類的結構,或者動態生成類
。服務器
官方簡介
Javassist(Java Programming Assistant)使Java字節碼
操做變得簡單。它是一個用於在Java中編輯字節碼的類庫;它使Java程序可以在運行時
定義新類,並在JVM加載時修改類文件。微信
Javassist (Java Programming Assistant) makes Java
bytecode
manipulation simple. It is a class library for editing bytecodes in Java; it enables Java programs to define a new class atruntime
and to modify a class file when the JVM loads it. 框架
與其餘相似的字節碼編輯器不一樣,Javassist提供兩個級別的API:源級別和字節碼級別
。若是用戶使用源級API,他們能夠在不瞭解Java字節碼規範
的狀況下編輯class文件。整個API僅使用Java語言的詞彙表進行設計。您甚至能夠以源文本的形式指定插入的字節碼; Javassist即時
編譯它。另外一方面,字節碼級API容許用戶直接
編輯class文件做爲其餘編輯器。編輯器
Unlike other similar bytecode editors, Javassist provides two levels of API:
source level and bytecode level
. If the users use the source- level API, they can edit a class file without knowledge ofthe specifications of the Java bytecode
. The whole API is designed with only the vocabulary of the Java language. You can even specify inserted bytecode in the form of source text; Javassist compiles iton the fly
. On the other hand, the bytecode-level API allows the users todirectly
edit a class file as other editors.ide
PS:這個框架在Android中可用,目前還沒發現兼容性問題!工具
package com.bqt.test; public class Person { public void hello(String s) { System.out.println(s); } }
ClassPool pool = ClassPool.getDefault(); CtClass cc = pool.get("com.bqt.test.Person"); CtMethod cm = cc.getDeclaredMethod("hello", new CtClass[] { pool.get("java.lang.String") }); cm.setBody("{" + "System.out.println(\"你好:\" + $1);" + "}"); cc.writeFile("d:/test");//保存到指定目錄 cc.toClass(); //加載修改後的類,注意:必須保證調用前此類未加載 new Person().hello("包青天");
運行結果爲:測試
你好:包青天
注意,若是使用 JDK9 如下的JDK ,同時使用 3.20.0-GA 以上版本的 Javassist,調用 toClass 方法會報 StackWalker 異常
生成的類經反編譯後的源碼爲:
package com.bqt.test; import java.io.PrintStream; public class Person { public void hello(String paramString) { System.out.println("你好" + paramString); } }
同個 Class 是不能在同個 ClassLoader 中加載兩次的,因此在輸出 CtClass 的時候須要注意下。
例如上例中,若是在調用toClass()
前 Person 類已經加載過了,則直接報異常:
new Person().hello("包青天"); ClassPool.getDefault().get("com.bqt.test.Person").toClass();
Exception in thread "main" javassist.CannotCompileException: by java.lang.LinkageError: loader (instance of sun/misc/Launcher$AppClassLoader): attempted duplicate class definition for name: "com/bqt/test/Person" at javassist.ClassPool.toClass(ClassPool.java:1170) at javassist.ClassPool.toClass(ClassPool.java:1113) at javassist.ClassPool.toClass(ClassPool.java:1071) at javassist.CtClass.toClass(CtClass.java:1264) at com.bqt.test.Main.main(Main.java:9) Caused by: java.lang.LinkageError: loader (instance of sun/misc/Launcher$AppClassLoader): attempted duplicate class definition for name: "com/bqt/test/Person" at java.lang.ClassLoader.defineClass1(Native Method) at java.lang.ClassLoader.defineClass(Unknown Source) at java.lang.ClassLoader.defineClass(Unknown Source) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source) at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source) at java.lang.reflect.Method.invoke(Unknown Source) at javassist.ClassPool.toClass2(ClassPool.java:1183) at javassist.ClassPool.toClass(ClassPool.java:1164) ... 4 more
解決方法:指定一個未加載的ClassLoader
爲了方便,Javassist 也提供一個 Classloader 供使用,例如
new Person().hello("包青天"); ClassPool pool = ClassPool.getDefault(); CtClass cc = pool.get("com.bqt.test.Person"); CtMethod cm = cc.getDeclaredMethod("hello", new CtClass[] { pool.get("java.lang.String") }); cm.setBody("{" + "System.out.println(\"你好鴨:\" + $1);" + "}"); cc.writeFile("d:/test");//保存到指定目錄 Translator translator = new Translator() { @Override public void start(ClassPool classPool) throws NotFoundException, CannotCompileException { System.out.println("start"); } @Override public void onLoad(ClassPool classPool, String paramString) throws NotFoundException, CannotCompileException { System.out.println("onLoad:" + paramString); //com.bqt.test.Person new Person().hello("白乾濤"); //調用的是原始類的方法 } }; Loader classLoader = new Loader(pool); //Javassist 提供的 Classloader classLoader.addTranslator(pool, translator); //監聽 ClassLoader 的生命週期 Class clazz = classLoader.loadClass("com.bqt.test.Person"); new Person().hello("白乾濤2"); //調用的是原始類的方法 clazz.getDeclaredMethod("hello", String.class).invoke(clazz.newInstance(), "你妹"); //調用的是新類的方法 Class clazz2 = Class.forName("com.bqt.test.Person"); clazz2.getDeclaredMethod("hello", String.class).invoke(clazz2.newInstance(), "你妹2"); //調用原始類的方法
打印日誌:
包青天 start onLoad:com.bqt.test.Person 白乾濤 白乾濤2 你好鴨:你妹 你妹2
ClassPool pool = ClassPool.getDefault(); CtClass cc = pool.get("com.bqt.test.Person"); byte[] bytes = cc.toBytecode();//獲得字節碼 System.out.println(bytes.length); System.out.println(cc.getName());//獲取類名 System.out.println(cc.getSimpleName());//獲取簡要類名 System.out.println(cc.getSuperclass().getName());//獲取父類 System.out.println(Arrays.toString(cc.getInterfaces()));//獲取接口 for (CtConstructor con : cc.getConstructors()) {//獲取構造方法 System.out.println("構造方法 "+con.getLongName()); } for (CtMethod method : cc.getMethods()) {//獲取方法 System.out.println(method.getLongName()); }
打印內容:
562 com.bqt.test.Person Person java.lang.Object [] 構造方法 com.bqt.test.Person() java.lang.Object.equals(java.lang.Object) java.lang.Object.finalize() com.bqt.test.Person.hello2(java.lang.String) java.lang.Object.toString() java.lang.Object.getClass() java.lang.Object.notifyAll() java.lang.Object.hashCode() java.lang.Object.wait() java.lang.Object.notify() com.bqt.test.Person.hello(java.lang.String) java.lang.Object.wait(long) java.lang.Object.wait(long,int) java.lang.Object.clone()
獲取註解信息
Object[] annotations = cf.getAnnotations(); //獲取類、方法、字段等上面定義的註解信息 SerializedName annotation = (SerializedName) annotations[0]; //遍歷判斷註解類型 System.out.println(annotation.value()); //獲取註解的值
ClassPool pool = ClassPool.getDefault(); CtClass cc = pool.makeClass("com.bqt.test.User"); //建立屬性 cc.addField(CtField.make("private int id;", cc)); cc.addField(CtField.make("private String name;", cc)); //建立方法 cc.addMethod(CtMethod.make("public String getName(){return name;}", cc)); cc.addMethod(CtMethod.make("public void setName(String name){this.name = name;}", cc)); //添加構造器 CtConstructor constructor = new CtConstructor(new CtClass[] { CtClass.intType, pool.get("java.lang.String") }, cc); constructor.setBody("{this.id=id;this.name=name;}"); cc.addConstructor(constructor); cc.writeFile("d:/test");
生成的類經反編譯後的源碼爲:
package com.bqt.test; public class User { private int id; private String name; public User(int paramInt, String paramString) { this.id = this.id; this.name = this.name; } public String getName() { return this.name; } public void setName(String paramString) { this.name = paramString; } }
package com.bqt.test; public class Person { public int hello(String s) { return s.length(); } public String hello2(String s) { return s; } }
ClassPool pool = ClassPool.getDefault(); CtClass cc = pool.get("com.bqt.test.Person"); //方式一 CtMethod cm1 = CtMethod.make("public int add1(int a, String b){return a+b.length();}", cc);//第一種方式,完整的方法以字符串形式傳遞過去 cc.addMethod(cm1); //cc.removeMethod(cm3) 刪除一個方法 //方式二 CtClass[] parameters = new CtClass[] { CtClass.intType, pool.get("java.lang.String") }; CtMethod cm2 = new CtMethod(CtClass.intType, "add2", parameters, cc);//第二種方式,返回值類型,方法名,參數,對象 cm2.setModifiers(Modifier.PUBLIC);//訪問範圍 cm2.setBody("{return $1+$2.length();}");//方法體 cc.addMethod(cm2); cc.writeFile("D:/test");//保存到指定位置
生成的類經反編譯後的源碼爲:
package com.bqt.test; public class Person { public int hello(String s) { return s.length(); } public String hello2(String s) { return s; } public int add1(int paramInt, String paramString) { return paramInt + paramString.length(); } public int add2(int paramInt, String paramString) { return paramInt + paramString.length(); } }
ClassPool pool = ClassPool.getDefault(); CtClass cc = pool.get("com.bqt.test.Person"); //方式一 CtField cf = new CtField(CtClass.intType, "age", cc); cf.setModifiers(Modifier.PRIVATE); cc.addField(cf); cc.addMethod(CtNewMethod.getter("getAge", cf)); cc.addMethod(CtNewMethod.setter("setAge", cf)); //方式二 CtField cf2 = CtField.make("private String name;", cc); cc.addField(cf2); cc.addMethod(CtNewMethod.getter("getName", cf2)); //快捷的添加get/set方法 cc.addMethod(CtNewMethod.setter("setName", cf2)); cc.writeFile("D:/test");//保存到指定位置
生成的類經反編譯後的源碼爲:
package com.bqt.test; public class Person { private int age; private String name; public int hello(String s) { return s.length(); } public String hello2(String s) { return s; } public int getAge() { return this.age; } public void setAge(int paramInt) { this.age = paramInt; } public String getName() { return this.name; } public void setName(String paramString) { this.name = paramString; } }
ClassPool pool = ClassPool.getDefault(); CtClass cc = pool.get("com.bqt.test.Person"); //方式一 CtMethod cm = cc.getDeclaredMethod("hello", new CtClass[] { pool.get("java.lang.String") }); cm.insertBefore("System.out.println(\"調用前2\");");//調用前 cm.insertBefore("System.out.println(\"調用前1\");"); cm.insertAt(20094, "System.out.println(\"在指定行插入代碼\");");//貌似行號胡亂寫也能夠 cm.insertAfter("System.out.println(\"調用後1\");");//調用後 cm.insertAfter("System.out.println(\"調用後2\");");//調用後 //方式二 CtMethod cm2 = cc.getDeclaredMethod("hello2", new CtClass[] { pool.get("java.lang.String") }); cm2.setBody("{" + // 你只須要正常寫代碼邏輯就能夠了,複製過來時,一些IDE,好比AS會自動幫你添加轉義字符 "if ($1 == null) {\n" + //$0表明的是this,$1表明方法參數的第一個參數、$2表明方法參數的第二個參數 "\treturn \"\";\n" + // "}\n" + // "return \"你好:\" + $1;" + // "}"); cc.writeFile("D:/test");//保存到指定位置
生成的類經反編譯後的源碼爲:
package com.bqt.test; import java.io.PrintStream; public class Person { public int hello(String s) { System.out.println("調用前1"); System.out.println("調用前2"); System.out.println("在指定行插入代碼"); int i = s.length(); System.out.println("調用後1"); int j = i; System.out.println("調用後2"); return j; } public String hello2(String paramString) { if (paramString == null) { return ""; } return "你好:" + paramString; } }
ClassPool pool = ClassPool.getDefault(); CtClass cc = pool.get("com.bqt.test.Person"); CtField cf = cc.getField("age"); FieldInfo fInfo = cf.getFieldInfo(); ConstPool cp = cc.getClassFile().getConstPool(); //修改註解的值 Annotation annotation = new Annotation(SerializedName.class.getName(), cp);//獲取註解 annotation.addMemberValue("value", new StringMemberValue("_AGE", cp));//設置註解指定字段的值 AnnotationsAttribute annotationsAttribute = new AnnotationsAttribute(cp, AnnotationsAttribute.visibleTag);//獲取註解信息 annotationsAttribute.setAnnotation(annotation);//設置註解 fInfo.addAttribute(annotationsAttribute);//添加(覆蓋)註解信息 cc.writeFile("D:/test");//保存到指定位置
修改前
class Person { @SerializedName("AGE") public int age = 28; }
修改後
class Person { @SerializedName("_AGE") public int age = 28; }
ClassPool pool = ClassPool.getDefault(); CtClass cc = pool.get("com.bqt.test.Person"); ProxyFactory factory = new ProxyFactory();//代理類工廠 factory.setSuperclass(cc.toClass());//設置父類,ProxyFactory將會動態生成一個類,繼承該父類 //設置過濾器,判斷哪些方法調用須要被攔截 factory.setFilter(new MethodFilter() { @Override public boolean isHandled(Method m) { return m.getName().startsWith("hello"); } }); Class<?> clazz = factory.createClass();//建立代理類型 Person proxy = (Person) clazz.newInstance();//建立代理實例 //設置代理處理方法 ((ProxyObject) proxy).setHandler(new MethodHandler() { @Override public Object invoke(Object self, Method thisMethod, Method proceed, Object[] args) throws Throwable { System.out.println(thisMethod.getName() + "被調用了"); try { Object ret = proceed.invoke(self, args);//thisMethod爲被代理方法,proceed爲代理方法,self爲代理實例,args爲方法參數 System.out.println("返回值: " + ret); return ret; } finally { System.out.println(thisMethod.getName() + "調用完畢"); } } }); //測試 proxy.hello("包青天"); System.out.println(proxy.getClass().getName()); //com.bqt.test.Person_$$_jvstee7_0
打印日誌:
hello被調用了 返回值: 3 hello調用完畢 com.bqt.test.Person_$$_jvstee7_0
2019-1-7