1. 程式人生 > >8.5(java學習筆記)8.5 位元組碼操作(javassist)

8.5(java學習筆記)8.5 位元組碼操作(javassist)

一、javassist

  javassist讓我們操作位元組碼更加簡單,它是一個類庫,允許我們修改位元組碼。它允許java程式動態的建立、修改類。

  javassist提供了兩個層次的API,基於原始碼級別的和位元組碼級別的。

   

二、javassist建立類

  1.獲取類池

  2.在類池中新增要建立的類

  3.新增變數,方法,構造器

  4.將建立好的類寫出 

import java.io.IOException;
import java.lang.String;

import javassist.CannotCompileException;
import javassist.ClassPool; import javassist.CtClass; import javassist.CtConstructor; import javassist.CtField; import javassist.CtMethod; import javassist.NotFoundException; public class TestSsist { public static void main(String[] args) throws CannotCompileException, NotFoundException {
//獲取預設類池 ClassPool pool = ClassPool.getDefault(); //在類池中建立一個類 CtClass cc = pool.makeClass("com.TestSsist.TestUser");//包名(com.TestSsist)+類名(TestUser) //建立屬性 CtField name = CtField.make("private String name;", cc);//
(程式碼,類) CtField age = CtField.make("private int age;", cc); //新增屬性 cc.addField(name); cc.addField(age); //建立方法 CtMethod setAll = CtMethod.make("public void setName(String name,int age){" + "this.name = name;" + "this.age = age;" + "}", cc); CtMethod getName = CtMethod.make("public String getName(){" + "return this.name;" + "}", cc); //新增方法 cc.addMethod(setAll); cc.addMethod(getName); //建立構造器 CtConstructor constructor = new CtConstructor(new CtClass[]{pool.get("java.lang.String"),CtClass.intType},cc); //設定構造器體內容,即{}部分內容 constructor.setBody("{this.name = name;" + "this.age = age;}"); //新增構造器 cc.addConstructor(constructor); try { cc.writeFile("F:\\TestJava");//將建立的類寫出到指定路徑 } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } System.out.println("建立成功!"); } }
執行結果:
建立成功

我們可以看指定目錄下有建立好的class檔案。

位元組碼檔案我們是無法直接檢視的,我們可以通過工具(XJad)將其反編譯進行檢視我們建立好的類。

 

三、獲取類基本資訊(該類已存在)

 

測試類(TestUser)內容如下:(該類已存在)

import java.io.Serializable;

public class TestUser implements Serializable{
    private String naem;
    private int age;
    
    public TestUser() {}
    
    public TestUser(String naem, int age) {
        super();
        this.naem = naem;
        this.age = age;
    }
    
    public String getNaem() {
        return naem;
    }
    public void setNaem(String naem) {
        this.naem = naem;
    }
    public int getAge() {
        return age;
    }
    public void setAge(int age) {
        this.age = age;
    }
    
    public void testSsist(int a){
        System.out.println("ssist:"+a);
    }
}

 

  public String getName();//獲取類名

  public CtClass getSuperclass();//獲取父類

  public CtClass[] getInterfaces();//獲取介面

  

  1.獲取類池

  2.獲取指定類

  3.獲取類相關資訊

  

import java.io.IOException;
import java.lang.String;

import javassist.CannotCompileException;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtConstructor;
import javassist.CtField;
import javassist.CtMethod;
import javassist.NotFoundException;

public class TestSsist {
    public static void main(String[] args) throws CannotCompileException, NotFoundException {
        ClassPool pool = ClassPool.getDefault();
        CtClass cc = pool.get("TestSsist.TestUser");
        System.out.println("類名:" + cc.getName());
        System.out.println("父類:"+cc.getSuperclass().getName());
        for(CtClass temp:cc.getInterfaces())
            System.out.println("介面:" + temp.getName());
    }
}
執行結果:
類名:TestSsist.TestUser
父類:java.lang.Object
介面:java.io.Serializable

 

四、動態新增方法

  1.獲取類池

  2.獲取對應類(TestUser)

  3.動態新增方法

  4.通過反射呼叫動態新增的方法  

 

import java.io.IOException;
import java.lang.String;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;

import javassist.CannotCompileException;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtConstructor;
import javassist.CtField;
import javassist.CtMethod;
import javassist.NotFoundException;

public class TestSsist {
    public static void main(String[] args) throws CannotCompileException, NotFoundException, NoSuchMethodException,
                               SecurityException, InstantiationException, IllegalAccessException { ClassPool pool
= ClassPool.getDefault();//獲取類池 CtClass cc = pool.get("TestSsist.TestUser");//獲取指定類 //public CtMethod(CtClass returnType, String mname,CtClass[] parameters, CtClass declaring) //動態新增方法 CtMethod mAdd = new CtMethod(CtClass.intType,"add", new CtClass[]{CtClass.intType,CtClass.intType},cc); mAdd.setModifiers(Modifier.PUBLIC);//設定方法訪問許可權 //因為我們在前面新增方法時只指定了形參型別,並沒有指定形參名,所以要用如下方法設定。 //設定方法體,$1+$2,第一個引數+第二個引數:例如int add(int a,int b){...} retutn $1+$2就等於return a+b; mAdd.setBody("{System.out.println($1 +\"+\"+ $2);return $1+$2;}"); cc.addMethod(mAdd); //通過反射呼叫動態生成的方法 Class cl = cc.toClass(); Object u1 = cl.newInstance(); //獲取指定方法 Method addm = cl.getDeclaredMethod("add",int.class,int.class); Object result = null; try { result = addm.invoke(u1,50,60);//呼叫方法 } catch (IllegalAccessException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (IllegalArgumentException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (InvocationTargetException e) { // TODO Auto-generated catch block e.printStackTrace(); } System.out.println(result); } }
執行結果:
50+60
110

 新增的方法只是執行時存在,執行結束就會消失,並不會將其真正寫入TestUser類中,如果想儲存的話,就通過writeFile寫出。

 

五、動態修改方法

   1.獲取類池

   2.獲取指定類(TestUser)

   3.獲取指定類中指定方法

   4.呼叫insertBefore,insertAfter、insertAt插入新語句,即修改方法。

   5.通過反射呼叫動態修改後的方法

   

import java.io.IOException;
import java.lang.String;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;

import javassist.CannotCompileException;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtConstructor;
import javassist.CtField;
import javassist.CtMethod;
import javassist.NotFoundException;

public class TestSsist {
    public static void main(String[] args) throws CannotCompileException, NotFoundException, NoSuchMethodException, SecurityException, InstantiationException, IllegalAccessException {
        ClassPool pool = ClassPool.getDefault();//獲取類池
        CtClass cc = pool.get("TestSsist.TestUser");//獲取指定類
        //獲取指定方法,(方法名,方法型別(CtClass[]型別))
        CtMethod m1 = cc.getDeclaredMethod("testSsist",new CtClass[]{CtClass.intType});//獲取類中指定方法
        //在32行插入語句
        m1.insertAt(32, "System.out.println(\"32\");");
        //在方法體開頭插入語句
        m1.insertBefore("System.out.println(\"start\");");
        //在方法體末尾插入語句
        m1.insertAfter("System.out.println(\"end\");");
        //通過反射呼叫動態修改後的方法
        Class cl = cc.toClass();
        Object u1 = cl.newInstance();
        //獲取指定方法
        Method addm = cl.getDeclaredMethod("testSsist",int.class);
        Object result = null;
        try {
            
            result = addm.invoke(u1,50);//呼叫方法
        } catch (IllegalAccessException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (IllegalArgumentException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } 
    }
}
執行結果:
start ssist:
50 32 end
先執行方法體開頭插入的語句
System.out.println(\"start\");
然後執行方法中自帶的語句System.out.println("ssist:"+a);
然後執行插入到32行的語句Syste.out.println(32);
最後執行方法體末尾插入的語句System.out.println("end");

 

 六、動態建立修改屬性

   1.建立屬性

   2.為對應屬性建立set方法

   3.通過反射呼叫set方法為其賦值

   4.通過反射獲取屬性值

import java.io.IOException;
import java.lang.String;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;

import javassist.CannotCompileException;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtConstructor;
import javassist.CtField;
import javassist.CtMethod;
import javassist.CtNewMethod;
import javassist.NotFoundException;

public class TestSsist {
    public static void main(String[] args) throws CannotCompileException, NotFoundException, NoSuchMethodException, SecurityException, InstantiationException, IllegalAccessException, ClassNotFoundException {
        ClassPool pool = ClassPool.getDefault();//獲取類池
        CtClass cc = pool.get("TestSsist.TestUser");//獲取指定類
        //動態建立password屬性
        CtField nameF = CtField.make("private String password;", cc);
        cc.addField(nameF);
        //動態建立set方法
        CtMethod set = CtMethod.make("public void setPassword(String password){"
                + "this.password = password;"
                + "}", cc);
        cc.addMethod(set);
        Class clz = cc.toClass();
        Object test = clz.newInstance();
        try {
            //通過反射呼叫set方法,為password設定值
            Method setV = clz.getDeclaredMethod("setPassword",new Class[]{String.class});
            setV.invoke(test, "123456");
            //獲取其屬性值
            Field pw = clz.getDeclaredField("password");
            pw.setAccessible(true);//password是私有屬性,設定不做訪問檢查
            System.out.println(pw.get(test));
        } catch (NoSuchFieldException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (IllegalArgumentException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }

    }
}
執行結果:
123456

 

本例所用javassist下載地址及版本:https://github.com/jboss-javassist/javassist/releasesJavassist 3.21.0-GA