Java 動態性(4) – 位元組碼操作

Matrix42發表於2019-01-19

1.位元組碼操作

  • JAVA動態性的兩種常見實現方式

    位元組碼操作

    反射

  • 執行時操作位元組碼可以讓我們實現如下功能

    動態生成新的類

    動態改變某個類的結構(新增/刪除/修改 新的屬性/方法)

  • 優勢

    比反射開銷小,效能高

    JAVAasist效能高於反射,低於asm

2.常見的位元組碼操作類庫

  • BCEL

    Byte Code Engineering Library (BCEL), 這是Apache Software Foundation 的 Jakarta 專案的一部分.BCEL是Java classworking廣泛使用的一種框,它可以讓您深入JVM組合語言進行類操作的細節.BCEL與Javassist有不同的處理位元組碼方法,BCEL在實際的JVM指令層次上進行操作(BCEI擁有豐富的JVM指令級支援)而Javassist所強調的是原始碼級別的工作

  • ASM

    是一個輕量級ava位元組碼操作框架,直接涉及量到VM底層的操作和指令

  • CGLIB(Code Generation Library)

    是一個強大的,高效能,高質量的Code生成類庫,基於ASM實現

  • Javassist

    是一個開源的分析、編輯和建立Jaw位元組碼的類庫.效能較ASM差,跟cglib差不多,但是使用簡單.很多開源框架都在使用它

    主頁:http://jboss-javassist.github…

3.JAVAssist庫

  • 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.
  • 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-level API allows the users to directly edit a class file as other editors.
  • Aspect Oriented Programming(AOP面向切面程式設計):Javassist can be a good tool for adding new methods into a class and for inserting before/after/around advice at the both caller and callee sides.
  • Reflection:Ones of applications of Javassist is runtime reflection;Javassist enables Java programs to use a metaobject that controls method calls on base-level objects.No specialized complier or virtual machine are needed.

4.JAVAssist庫的API簡介

  • javaassist的最外層的API和JAVA的反射包中的API頗為類似
  • 它主要由CtClass,CtMethod,以及CtField幾個類組成.用以執行和JDK反射API中java.lang.Class,java.lang.reflect.Method,java.lang.reflect.Method.Field相同的操作(Ct為Complie Time)

5.JAVAssist庫的簡單使用

  • 建立一個全新的類
  • 使用XJAD反編譯工具,將生成的class檔案反編譯成JAVA檔案

使用前先匯入javassist的jar包
Demo:

/**
 * 使用javassist生成一個新的類
 * @author Matrix42
 *
 */
public class Demo01 {

    public static void main(String[] args) throws Exception{
        
        ClassPool pool = ClassPool.getDefault();
        CtClass cc = pool.makeClass("com.lorinda.bean.Emp");
        
        //建立屬性
        CtField f1 = CtField.make("private int empno;", cc);
        CtField f2 = CtField.make("private String ename;", cc);
        cc.addField(f1);
        cc.addField(f2);
        
        //建立方法
        CtMethod m1 = CtMethod.make("public int getEmpno(){return empno;}", cc);
        CtMethod m2 = CtMethod.make("public void setEmpno(int empno){this.empno = empno;}", cc);
        cc.addMethod(m1);
        cc.addMethod(m2);
        CtMethod m3 = CtMethod.make("public String getEname(){return ename;}", cc);
        CtMethod m4 = CtMethod.make("public void setEname(String empno){this.ename = ename;}", cc);
        cc.addMethod(m3);
        cc.addMethod(m4);
        
        //新增構造器
        CtConstructor constructor = new CtConstructor(new CtClass[]{CtClass.intType,pool.get("java.lang.String")}, cc);
        constructor.setBody("{this.empno=$1;this.ename=$2;}");
        cc.addConstructor(constructor);
        
        //將上面構造好的類寫入到d:/myjava
        cc.writeFile("d:/myjava");
        System.out.println("生成類,成功!");
        
    }

}

建立完成後使用XJAD反編譯就可以看到原始碼了

反編譯原始碼:

package com.lorinda.bean;

public class Emp
{
    private int empno;
    private String ename;

    public int getEmpno()
    {
        return empno;
    }

    public void setEmpno(int i)
    {
        empno = i;
    }

    public String getEname()
    {
        return ename;
    }

    public void setEname(String s)
    {
        ename = ename;
    }

    public Emp(int i, String s)
    {
        empno = i;
        ename = s;
    }
}

6.JAVAssist庫的API詳解

  • 方法操作

    修改已有方法的方法體(插入程式碼到已有方法體)

    新增方法

    刪除方法

    a b c
    $0,$1,$2,… this and actual parameters $0 代表的是 this,$1 代表方法引數的第一個引數,$2 代表方法引數的第二個引數, 以此類推,$N 代表方法引數的第 N 個引數
    $args An arrar of parameters The type of $args is Object[], $args[0] 對應的是 $1 而不是 $0
    $$ 所有方法引數的簡寫, 主要用在方法呼叫上 move(String a,String b) move($$) 相當於 move($1,$2)
    fallthrough path 在類路徑, 原始檔路徑等中有不存在的路徑警告
    $cflow
    $r
    $_
    addCatch()
    $class
    $sig

  • 屬性操作

    修改已有方法的方法體(插入程式碼到已有方法體)

    新增方法

    刪除方法

Demo:

import java.awt.color.CMMException;
import java.io.IOException;
import java.lang.reflect.Method;
import java.util.Arrays;

public class Demo02 {

    public static void test01() throws Exception{
        ClassPool pool = ClassPool.getDefault();
        CtClass cc = pool.get("javassist.Emp");
        
        byte[] bytes = cc.toBytecode();
        System.out.println(Arrays.toString(bytes));
        
        System.out.println(cc.getName());       //獲得類名
        System.out.println(cc.getSimpleName()); //獲得簡要類名
        System.out.println(cc.getSuperclass()); //獲得父類
        System.out.println(cc.getInterfaces()); //獲得介面
    }
    
    public static void test02()throws Exception{
        ClassPool pool = ClassPool.getDefault();
        CtClass cc = pool.get("javassist.Emp");
        
        //CtMethod m = CtMethod.make("public int add(int a,int b){return a+b;}", cc);
        CtMethod m = new CtMethod(CtClass.intType,"add",
                new CtClass[]{CtClass.intType,CtClass.intType},cc);
        m.setModifiers(Modifier.PUBLIC);
        m.setBody("{System.out.println("Ha Ha");return $1+$2;}");
        
        cc.addMethod(m);
        
        //通過反射呼叫新生產的方法
        Class<?> clazz = cc.toClass();
        Object obj = clazz.newInstance();
        Method method = clazz.getDeclaredMethod("add", int.class,int.class);
        Object result = method.invoke(obj, 200,300);
        System.out.println(result);
    }
    
    public static void test03()throws Exception{
        
        ClassPool pool = ClassPool.getDefault();
        CtClass cc = pool.get("javassist.Emp");
        
        CtMethod cm = cc.getDeclaredMethod("sayHello", new CtClass[]{CtClass.intType});
        cm.insertBefore("System.out.println($1);");
        
        Class<?> clazz = cc.toClass();
        Object obj = clazz.newInstance();
        Method method = clazz.getDeclaredMethod("sayHello", int.class);
        method.invoke(obj, 90);
        
    }
    
    public static void test04() throws Exception{
        
        ClassPool pool = ClassPool.getDefault();
        CtClass cc = pool.get("javassist.Emp");
        
        //CtField f1 = CtField.make("private int empno", cc);
        CtField f1 = new CtField(CtClass.intType,"salary",cc);
        f1.setModifiers(Modifier.PRIVATE);
        cc.addField(f1,"1000");//1000位預設值
        
       // cc.getDeclaredField("ename"); //獲取指定屬性
        
        cc.addMethod(CtNewMethod.getter("salary",f1));
        cc.addMethod(CtNewMethod.setter("salary", f1));
    }
    
    public static void test05()throws Exception{
        ClassPool pool = ClassPool.getDefault();
        CtClass cc = pool.get("javassist.Emp");
        
        CtConstructor[] cs = cc.getConstructors();
        for (CtConstructor ctConstructor : cs) {
            System.out.println(ctConstructor.getLongName());
        }
    }
    
    public static void main(String[] args) throws Exception {
        //test01();
        //test02();
       // test03();
       // test04();
        test05();

    }

}
  • 構造方法操作

    getConstructors()

  • 註解操作

程式碼片段:

public @interface Author {
String name();
int year();
}
@Author(name="Chiba",year=2005)
public class Point{
    int x,y;
}
CtClass cc = ClassPool.getDefault.get("Point");
Object[] all = cc.getAnnotations();
Author a = (Author)all[0];
String name = a.name();
int year = a.year();
System.out.println("name:"+name+",year:"+year);

當呼叫了writeFile(),toClass(),toBytecode(),Javassist會把那個CtClass物件凍結,如果想使用凍結的物件可以呼叫.defrose()方法

  • 侷限性

    JDK5.0新語法不支援(包括泛型,列舉),不支援註解修改,單可以的通過底層javasist類來解決,具體參考:javassist.bytecode.annotation

    不支援陣列的初始化,如String[]{“1″,”2”},除非只有陣列容量為1

    不支援內部類盒匿名類

    不支援continue盒break表示式

    對於繼承關係,有些語法不支援,如:

    • class A{}
    • class B extends A{}
    • class C extends B{}

相關文章