首頁(yè)技術(shù)文章正文

Javassist重要的類有哪些?怎么運(yùn)用?

更新時(shí)間:2020-03-15 來(lái)源:黑馬程序員 瀏覽量:

Javassist是一個(gè)開(kāi)源的分析、編輯和創(chuàng)建Java字節(jié)碼的類庫(kù),可以直接編輯和生成Java生成的字節(jié)碼。相對(duì)于bcel,asm等這些工具,開(kāi)發(fā)者不需要了解虛擬機(jī)指令,就能動(dòng)態(tài)改變類的結(jié)構(gòu),或者動(dòng)態(tài)生成類。javassist簡(jiǎn)單易用,快速。

重要的類
1. ClassPool:javassist的類池,使用ClassPool 類可以跟蹤和控制所操作的類,它的工作方式與 JVM 類裝載器非常相似
2. CtClass: CtClass提供了類的操作,如在類中動(dòng)態(tài)添加新字段、方法和構(gòu)造函數(shù)、以及改變類、父類和接口的方法。
3. CtField:類的屬性,通過(guò)它可以給類創(chuàng)建新的屬性,還可以修改已有的屬性的類型,訪問(wèn)修飾符等
4. CtMethod:類中的方法,通過(guò)它可以給類創(chuàng)建新的方法,還可以修改返回類型,訪問(wèn)修飾符等, 甚至還可以修改方法體內(nèi)容代碼
5. CtConstructor:與CtMethod類似。推薦了解java培訓(xùn)課程。

API運(yùn)用
ClassPool
// 類庫(kù), jvm中所加載的class
ClassPool pool = ClassPool.getDefault();
// 加載一個(gè)已知的類, 注:參數(shù)必須為全量類名
CtClass ctClass = pool.get("com.itheima.Student");
// 創(chuàng)建一個(gè)新的類, 類名必須為全量類名
CtClass tClass = pool.makeClass("com.itheima.Calculator");

CtField
// 獲取已知類的屬性
CtField ctField = ctClass.getDeclaredField("name");
// 構(gòu)建新的類的成員變量
CtField ctFieldNew = new CtField(CtClass.intType,"age",ctClass);
// 設(shè)置類的訪問(wèn)修飾符為public
ctFieldNew.setModifiers(Modifier.PUBLIC);
// 將屬性添加到類中
ctClass.addField(ctFieldNew);


CtMethod
// 獲取已有方法
CtMethod ctMethod = ctClass.getDeclaredMethod("sayHello");
//創(chuàng)建新的方法, 參數(shù)1:方法的返回類型,參數(shù)2:名稱,參數(shù)3:方法的參數(shù),參數(shù)4:方法所屬的類
CtMethod ctMethod = new CtMethod(CtClass.intType, "calc", new CtClass[]
{CtClass.intType,CtClass.intType}, tClass);
// 設(shè)置方法的訪問(wèn)修飾
ctMethod.setModifiers(Modifier.PUBLIC);
// 將新建的方法添加到類中
ctClass.addMethod(ctMethod);
// 方法體內(nèi)容代碼 $1代表第一個(gè)參數(shù),$2代表第二個(gè)參數(shù)
ctMethod.setBody("return $1 + $2;");
// 直接創(chuàng)建方法
CtMethod getMethod = CtNewMethod.make("public int getAge() { return this.age;}", ctClass);
CtMethod setMethod = CtNewMethod.make("public void setAge(int age) { this.age = age;}",
ctClass);
ctClass.addMethod(getMethod);
ctClass.addMethod(setMethod);


CtConstructor
// 獲取已有的構(gòu)造方法, 參數(shù)為構(gòu)建方法的參數(shù)類型數(shù)組
CtConstructor ctConstructor = ctClass.getDeclaredConstructor(new CtClass[]{});
// 創(chuàng)建新的構(gòu)造方法
CtConstructor ctConstructor = new CtConstructor(new CtClass[]{CtClass.intType},ctClass);
ctConstructor.setModifiers(Modifier.PUBLIC);
ctConstructor.setBody("this.age = $1;");
ctClass.addConstructor(ctConstructor);
// 也可直接創(chuàng)建
ctConstructor = CtNewConstructor.make("public Student(int age){this.age=age;}", ctClass);


案例
1. 創(chuàng)建maven工程并添加依賴
Java動(dòng)態(tài)字節(jié)技術(shù)之Javassis01
添加依賴
Java動(dòng)態(tài)字節(jié)技術(shù)之Javassis02
添加Student類

package com.itheima;
    public class Student {
        private int age;
        private String name;
        public int getAge() {
             return age;
        }
        public void setAge(int age) {
            this.age = age;
         }
        public String getName() {
            return name;
        }
        public void setName(String name) {
            this.name = name;
        }
       public void sayHello(){
            System.out.println("Hello, My name is " + this.name);
        }
}


2. 修改已有方法體,插入新的代碼
對(duì)已有的student類中的sayHello方法,當(dāng)調(diào)用時(shí),控制臺(tái)會(huì)輸出: Hello, My name is 張三(name=張三)
需求:通過(guò)動(dòng)態(tài)修改sayHello方法,當(dāng)調(diào)用sayHello時(shí),除了輸出已經(jīng)的內(nèi)容外,再輸出當(dāng)前學(xué)生的age信息創(chuàng)建JavassistDemo測(cè)試類,代碼實(shí)現(xiàn)如下:

package com.itheima.test;
import org.junit.Test;
import com.itheima.Student;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtMethod;
@SuppressWarnings("unchecked")
public class JavassistDemo {
    @Test
    public void t1() throws Exception{
        // 類庫(kù)池, jvm中所加載的class
        ClassPool pool = ClassPool.getDefault();
        // 獲取指定的Student類
        CtClass ctClass = pool.get("com.itheima.Student");
        // 獲取sayHello方法
        CtMethod ctMethod = ctClass.getDeclaredMethod("sayHello");
        // 在方法的代碼后追加 一段代碼
        ctMethod.insertAfter("System.out.println(\"I'm \" + this.age + \" years old.\");");
        // 使用當(dāng)前的ClassLoader加載被修改后的類
        Class<Student> newClass = ctClass.toClass();
        Student stu = newClass.newInstance();
        stu.setName("張三");
        stu.setAge(18);
        stu.sayHello();
    }
}

運(yùn)行結(jié)果:
Java動(dòng)態(tài)字節(jié)技術(shù)之Javassist03

3. 動(dòng)態(tài)添加方法

接下來(lái)我們給Student類添加一個(gè)計(jì)算的方法,但不是直接在Student類中添加,而是使用javassist,動(dòng)態(tài)添加


public int calculate(int a, int b){
    return a+b;
}

創(chuàng)建測(cè)試方法t2,代碼如下


@Test
public void t2() throws Exception{
    // 類庫(kù)池, jvm中所加載的class
    ClassPool pool = ClassPool.getDefault();
    // 獲取指定的Student類
    CtClass ctClass = pool.get("com.itheima.Student");
    // 創(chuàng)建calc方法, 帶兩個(gè)參數(shù),參數(shù)的類型都為int類型
    CtMethod ctMethod = new CtMethod(CtClass.intType,"calc",
    new CtClass[]{CtClass.intType,CtClass.intType}, ctClass);
    // 設(shè)置方法的訪問(wèn)修飾
    ctMethod.setModifiers(Modifier.PUBLIC);
    // 設(shè)置方法體代碼
    ctMethod.setBody("return $1 + $2;");
    // 添加新建的方法到原有的類中
    ctClass.addMethod(ctMethod);
    // 加載修改后的類
    ctClass.toClass();
    // 創(chuàng)建對(duì)象
    Student stu = new Student();
    // 獲取calc方法
    Method dMethod = Student.class.getDeclaredMethod("calc", new Class[]
    {int.class,int.class});
    // 反射調(diào)用 方法
    Object result = dMethod.invoke(stu, 10,20);
    // 打印結(jié)果
    System.out.println(String.format("調(diào)用calc方法,傳入?yún)?shù):%d,%d", 10,20));
    System.out.println("返回結(jié)果:" + (int)result);
}

控制臺(tái)輸出結(jié)果:
Java動(dòng)態(tài)字節(jié)技術(shù)之Javassist06

4. 動(dòng)態(tài)創(chuàng)建類
下面我們?cè)賮?lái)個(gè)神的魔術(shù),無(wú)中生有

@Test
public void t3() throws Exception{
    ClassPool pool = ClassPool.getDefault();
    // 創(chuàng)建teacher類
    CtClass teacherClass = pool.makeClass("com.itheima.Teacher");
    // 設(shè)置為公有類
    teacherClass.setModifiers(Modifier.PUBLIC);
    // 獲取String類型
    CtClass stringClass = pool.get("java.lang.String");
    // 獲取list類型
    CtClass listClass = pool.get("java.util.List");
    // 獲取學(xué)生的類型
    CtClass studentClass = pool.get("com.itheima.Student");
    // 給teacher添加name屬性
    CtField nameField = new CtField(stringClass, "name", teacherClass);
    nameField.setModifiers(Modifier.PUBLIC);
    teacherClass.addField(nameField);
    // 給teacher類添加students屬性
    CtField studentList = new CtField(listClass, "students",teacherClass);
    studentList.setModifiers(Modifier.PUBLIC);
    teacherClass.addField(studentList);
    // 給teacher類添加無(wú)參構(gòu)造方法
    CtConstructor ctConstructor = CtNewConstructor.make("public Teacher()
    {this.name=\"abc\";this.students = new java.util.ArrayList();}", teacherClass);
    teacherClass.addConstructor(ctConstructor);
    // 給teacher類添加addStudent方法
    CtMethod m = new CtMethod(CtClass.voidType, "addStudent", new CtClass[]{studentClass},
    teacherClass);
    m.setModifiers(Modifier.PUBLIC);
    // 添加學(xué)生對(duì)象到students屬性中, $1代表參數(shù)1
    m.setBody("this.students.add($1);");
    teacherClass.addMethod(m);
    // 給teacher類添加sayHello方法
    m = new CtMethod(CtClass.voidType, "sayHello", new CtClass[]{}, teacherClass);
    m.setModifiers(Modifier.PUBLIC);
    m.setBody("System.out.println(\"Hello, My name is \" + this.name);");
    m.insertAfter("System.out.println(\"I have \" + this.students.size() + \"students\");");
    teacherClass.addMethod(m);
    // 加載修改后的類
    Class<?> cls = teacherClass.toClass();
    // 實(shí)例teacher對(duì)象
    Object obj = cls.newInstance();
    // 獲取addStudent方法
    Method method = cls.getDeclaredMethod("addStudent", Student.class);
    // 創(chuàng)建張三和李四2個(gè)學(xué)生
    Student stu = new Student();
    stu.setName("張三");
    // 調(diào)用teacher類的addStudent方法,添加張三
    method.invoke(obj, stu);
    stu = new Student();
    stu.setName("李四");
    // 調(diào)用teacher類的addStudent方法,添加李四
    method.invoke(obj, stu);
    // 調(diào)用sayHello方法
    Method teacherSayHello = cls.getDeclaredMethod("sayHello",new Class[]{});
    teacherSayHello.invoke(obj, new Object[]{});
}
運(yùn)行結(jié)果:


總結(jié)
javassist被用于struts2和hibernate中,都用來(lái)做動(dòng)態(tài)字節(jié)碼修改使用。一般開(kāi)發(fā)中不會(huì)用到,但在封裝框架時(shí)比較有用。雖然javassist提供了一套簡(jiǎn)單易用的API,但如果用于平常的開(kāi)發(fā),會(huì)有如下幾點(diǎn)不好的地方:
1. 所引用的類型,必須通過(guò)ClassPool獲取后才可以使用
2. 代碼塊中所用到的引用類型,使用時(shí)必須寫(xiě)全量類名
3. 即使代碼塊內(nèi)容寫(xiě)錯(cuò)了,它也不會(huì)像eclipse等開(kāi)發(fā)工具一樣有提示,它只有在運(yùn)行時(shí)才報(bào)錯(cuò)
4. 動(dòng)態(tài)修改的類,必須在修改之前,jvm中不存在這個(gè)類的實(shí)例對(duì)象。修改方法的實(shí)現(xiàn)必須在修改的類加載之前進(jìn)行

猜你喜歡

北京比較好的Java培訓(xùn)機(jī)構(gòu)


分享到:
在線咨詢 我要報(bào)名
和我們?cè)诰€交談!