Javassist 字節碼 簡介 案例 MD

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 at runtime 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 of the 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 it on the fly. On the other hand, the bytecode-level API allows the users to directly 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

相關文章
相關標籤/搜索