字節碼及ASM使用

字節碼及ASM使用

什麼是字節碼?

  • 機器碼
    機器碼(machine code)是CPU可直接解讀的指令。機器碼與硬件等有關,不一樣的CPU架構支持的硬件碼也不相同。
  • 字節碼
    字節碼(bytecode)是一種包含執行程序、由一序列 op 代碼/數據對 組成的二進制文件。字節碼是一種中間碼,它比機器碼更抽象,須要直譯器轉譯後才能成爲機器碼的中間代碼。一般狀況下它是已經通過編譯,但與特定機器碼無關。字節碼主要爲了實現特定軟件運行和軟件環境、與硬件環境無關。

字節碼的實現方式是經過編譯器和虛擬機器。編譯器將源碼編譯成字節碼,特定平臺上的虛擬機器將字節碼轉譯爲能夠直接執行的指令。
例如:C# IL,Java bytecodejava

* JAVA代碼編譯和執行
Java 代碼編譯是由 Java 源碼編譯器來完成,流程圖以下所示:

Java 字節碼的執行是由 JVM 執行引擎來完成,流程圖以下所示:
  ![](http://ortsyq47e.bkt.clouddn.com/qnsource/images/%E5%AD%97%E8%8A%82%E7%A0%81%E5%8F%8AASM%E4%BD%BF%E7%94%A8/img2.gif)

JVM字節碼執行

JVM楨棧結構

如下內容能夠參照示例閱讀,理解會不同git

方法調用在JVM中轉換成的是字節碼執行,字節碼指令執行的數據結構就是棧幀(stack frame)。也就是在虛擬機棧中的棧元素。虛擬機會爲每一個方法分配一個棧幀,由於虛擬機棧是LIFO(後進先出)的,因此當前線程正在活動的棧幀,也就是棧頂的棧幀,JVM規範中稱之爲「CurrentFrame」,這個當前棧幀對應的方法就是「CurrentMethod」。字節碼的執行操做,指的就是對當前棧幀數據結構進行的操做。
  JVM的運行時數據區的結構以下圖,本文主要講楨棧結構。
github

運行時數據區web

  棧幀的數據結構主要分爲四個部分:局部變量表、操做數棧、動態連接以及方法返回地址(包括正常調用和異常調用的完成結果)。下面就一一介紹下這四種數據結構。spring

局部變量表(local variables)apache

  當方法被調用時,參數會傳遞到從0開始的連續的局部變量表的索引位置上。棧幀中局部變量表的長度存儲在類或接口的二進制表示中。閱讀Class文件會找到Code屬性,因此能知道local variables的最大長度是在編譯期間決定的。<font color=FFA500>一個局部變量表的佔用了32位的存儲空間(一個存儲單位稱之爲slot,槽),因此能夠存儲一個boolean、byte、char、short、float、int、refrence和returnAdress數據,long和double須要2個連續的局部變量表來保存,經過較小位置的索引來獲取。若是被調用的是實例方法,那麼第0個位置存儲「this」關鍵字表明當前實例對象的引用。</font>這個能夠經過javap 工具查看實例方法和靜態方法對比字節碼指令的0位置。例子也能夠參考JVM 字節碼指令對於棧幀數據操做舉例編程

操做數棧(operand stack)json

 操做數棧同局部變量表同樣,也是編譯期間就能決定了其存儲空間(最大的單位長度),經過 Code屬性存儲在類或接口的字節流中。操做數棧也是個LIFO棧。
  操做數棧是在JVM字節碼執行一些指令(第二部分會介紹一些指令集)時建立的,主要是把局部變量表中的變量壓入操做數棧,在操做數棧中進行字節碼指令的操做,再將變量出操做數棧,結果入操做數棧。<font color=FFA500>同局部變量表,除了long和double,其餘類型數據都只佔用一個棧的單位深度。</font>設計模式

動態連接數組

  每一個棧幀指向運行時常量池中該棧幀所屬的方法的引用,也就是字節碼的發放調用的引用。動態連接就是將符號引用所表示的方法,轉換成方法的直接引用。加載階段或第一次使用時轉化爲直接引用的(將變量的訪問轉化爲訪問這些變量的存儲結構所在的運行時內存位置)就叫作靜態解析。JVM的動態連接還支持運行期轉化爲直接引用。也能夠叫作Late Binding,晚期綁定。動態連接是java靈活OO的基礎結構。能夠參考一個例子來加深理解從字節碼指令看重寫在JVM中的實現

方法返回地址

  <font color=FFA500>方法正常退出,JVM執行引擎會恢復上層方法局部變量表操做數棧並把返回值壓入調用者的棧幀的操做數棧,PC計數器的值就會調整到方法調用指令後面的一條指令。</font>這樣使得當前的棧幀可以和調用者鏈接起來,而且讓調用者的棧幀的操做數棧繼續往下執行。
  方法的異常調用完成,主要是JVM拋出的異常,若是異常沒有被捕獲住,或者遇到athrow字節碼指令顯示拋出,那麼就沒有返回值給調用者。

<font color=FFA500>
注:

  • 操做long double類型數據,必定要分配兩個slot,若是分配了一個slot不會報錯,但會致使數據丟失;
  • 實例方法的第0個位置存儲「this」關鍵字表明當前實例對象的引用;
  • 引用自JVM字節碼執行模型及字節碼指令集,建議閱讀《深刻了解JVM》-- 虛擬機執行子系統;

</font>

字節碼指令集

如下的字節碼指令集比較枯燥,可參照ASM Opcodes

加載和存儲指令
加載和存儲指令用於將數據從棧幀的局部變量表和操做數棧之間來回傳輸。

1)將一個局部變量加載到操做數棧的指令包括:iload,iload_<n\>,lload、lload_<n\>、float、 fload_<n\>、dload、dload_<n\>,aload、aload_<n\>。
   2)將一個數值從操做數棧存儲到局部變量表的指令:istore,istore_<n\>,lstore,lstore_<n\>,fstore,fstore_<n\>,dstore,dstore_<n\>,astore,astore_<n\>
   3)將常量加載到操做數棧的指令:bipush,sipush,ldc,ldc_w,ldc2_w,aconst_null,iconst_ml,iconst_<i\>,lconst_<l\>,fconst_<f\>,dconst_<d\>
   4)局部變量表的訪問索引指令:wide

一部分以尖括號結尾的指令表明了一組指令,如iload_<i>,表明了iload_0,iload_1等,這幾組指令都是帶有一個操做數的通用指令。

運算指令
算術指令用於對兩個操做數棧上的值進行某種特定運算,並把結果從新存入到操做棧頂。

1)加法指令:iadd,ladd,fadd,dadd
   2)減法指令:isub,lsub,fsub,dsub
   3)乘法指令:imul,lmul,fmul,dmul
   4)除法指令:idiv,ldiv,fdiv,ddiv
   5)求餘指令:irem,lrem,frem,drem
   6)取反指令:ineg,leng,fneg,dneg
   7)位移指令:ishl,ishr,iushr,lshl,lshr,lushr
   8)按位或指令:ior,lor
   9)按位與指令:iand,land
   10)按位異或指令:ixor,lxor
   11)局部變量自增指令:iinc
   12)比較指令:dcmpg,dcmpl,fcmpg,fcmpl,lcmp

Java虛擬機沒有明確規定整型數據溢出的狀況,但規定了處理整型數據時,只有除法和求餘指令出現除數爲0時會致使虛擬機拋出異常。
Java虛擬機要求在浮點數運算的時候,全部結果否必須舍入到適當的精度,若是有兩種可表示的形式與該值同樣,會優先選擇最低有效位爲零的。稱之爲最接近數舍入模式。
浮點數向整數轉換的時候,Java虛擬機使用IEEE 754標準中的向零舍入模式,這種模式舍入的結果會致使數字被截斷,全部小數部分的有效字節會被丟掉。

類型轉換指令
類型轉換指令將兩種Java虛擬機數值類型相互轉換,這些操做通常用於實現用戶代碼的顯式類型轉換操做。
JVM直接就支持寬化類型轉換(小範圍類型向大範圍類型轉換):

1)int類型到long,float,double類型
   2)long類型到float,double類型
   3)float到double類型

但在處理窄化類型轉換時,必須顯式使用轉換指令來完成,這些指令包括:i2b、i2c、i2s、l2i、f2i、f2l、d2i、d2l和 d2f。
將int 或 long 窄化爲整型T的時候,僅僅簡單的把除了低位的N個字節之外的內容丟棄,N是T的長度。這有可能致使轉換結果與輸入值有不一樣的正負號。
在將一個浮點值窄化爲整數類型T(僅限於 int 和 long 類型),將遵循如下轉換規則:

1)若是浮點值是NaN , 吶轉換結果就是int 或 long 類型的0
   2)若是浮點值不是無窮大,浮點值使用IEEE 754 的向零舍入模式取整,得到整數v, 若是v在T表示範圍以內,那就過就是v
   3)不然,根據v的符號, 轉換爲T 所能表示的最大或者最小正數

對象建立與訪問指令
雖然類實例和數組都是對象,Java虛擬機對類實例和數組的建立與操做使用了不一樣的字節碼指令。

1)建立實例的指令:new
   2)建立數組的指令:newarray,anewarray,multianewarray
   3)訪問字段指令:getfield,putfield,getstatic,putstatic
   4)把數組元素加載到操做數棧指令:baload,caload,saload,iaload,laload,faload,daload,aaload
   5)將操做數棧的數值存儲到數組元素中執行:bastore,castore,castore,sastore,iastore,fastore,dastore,aastore
   6)取數組長度指令:arraylength JVM支持方法級同步和方法內部一段指令序列同步,這兩種都是經過moniter實現的。
   7)檢查實例類型指令:instanceof,checkcast

操做數棧管理指令
如同操做一個普通數據結構中的堆棧那樣,Java 虛擬機提供了一些用於直接操做操做數棧的指令,包括:

1)將操做數棧的棧頂一個或兩個元素出棧:pop、pop2
   2)複製棧頂一個或兩個數值並將複製值或雙份的複製值從新壓入棧頂:dup、dup二、dup_x一、dup2_x一、dup_x二、dup2_x2。
   3)將棧最頂端的兩個數值互換:swap

控制轉移指令
讓JVM有條件或無條件從指定指令而不是控制轉移指令的下一條指令繼續執行程序。控制轉移指令包括:

1)條件分支:ifeq,iflt,ifle,ifne,ifgt,ifge,ifnull,ifnotnull,if_cmpeq,if_icmpne,if_icmlt,if_icmpgt等
   2)複合條件分支:tableswitch,lookupswitch
   3)無條件分支:goto,goto_w,jsr,jsr_w,ret

JVM中有專門的指令集處理int和reference類型的條件分支比較操做,爲了能夠無明顯標示一個實體值是不是null,有專門的指令檢測null 值。boolean類型和byte類型,char類型和short類型的條件分支比較操做,都使用int類型的比較指令完成,而 long,float,double條件分支比較操做,由相應類型的比較運算指令,運算指令會返回一個整型值到操做數棧中,隨後再執行int類型的條件比較操做完成整個分支跳轉。各類類型的比較都最終會轉化爲int類型的比較操做。

方法調用和返回指令
invokevirtual指令:調用對象的實例方法,根據對象的實際類型進行分派(虛擬機分派)。
invokeinterface指令:調用接口方法,在運行時搜索一個實現這個接口方法的對象,找出合適的方法進行調用。
invokespecial:調用須要特殊處理的實例方法,包括實例初始化方法,私有方法和父類方法
invokestatic:調用類方法(static)
方法返回指令是根據返回值的類型區分的,包括ireturn(返回值是boolean,byte,char,short和 int),lreturn,freturn,drturn和areturn,另一個return供void方法,實例初始化方法,類和接口的類初始化i方法使用。

異常處理指令
在Java程序中顯式拋出異常的操做(throw語句)都有athrow 指令來實現,除了用throw 語句顯示拋出異常狀況外,Java虛擬機規範還規定了許多運行時異常會在其餘Java虛擬機指令檢測到異常情況時自動拋出。
在Java虛擬機中,處理異常不是由字節碼指令來實現的,而是採用異常表來完成的。

同步指令
方法級的同步是隱式的,無需經過字節碼指令來控制,它實如今方法調用和返回操做中。虛擬機從方法常量池中的方法標結構中的 ACC_SYNCHRONIZED標誌區分是不是同步方法。方法調用時,調用指令會檢查該標誌是否被設置,若設置,執行線程持有moniter,而後執行方法,最後完成方法時釋放moniter。
同步一段指令集序列,一般由synchronized塊標示,JVM指令集中有monitorenter和monitorexit來支持synchronized語義。

結構化鎖定是指方法調用期間每個monitor退出都與前面monitor進入相匹配的情形。JVM經過如下兩條規則來保證結結構化鎖成立(T表明一線程,M表明一個monitor):

1)T在方法執行時持有M的次數必須與T在方法完成時釋放的M次數相等
   2)任什麼時候刻都不會出現T釋放M的次數比T持有M的次數多的狀況

<font color=FFA500>

注:

  • 大多數的指令有前綴和(或)後綴來代表其操做數的類型。以下表

    | 前/後綴 | 操做數類型 |
    | :------ | :------ |
    | i | 整數 | 
    | l | 長整數 | 
    | s | 短整數 | 
    | b | 字節 | 
    | c | 字符 | 
    | f | 單精度浮點數 | 
    | d | 雙精度浮點數 | 
    | z | 布爾值 | 
    | a | 引用 |
  • 引用自深刻理解java虛擬機 字節碼指令簡介
    </font>

示例:

JAVA源碼

package com.taobao.film;

/**
 * @author - zhupin(kaiqiang.gkq@alibaba-inc.com)
 */
public class Demo {
    private static final String HELLO_CONST = "Hello";
    private static String CONST = null;

    static {
        CONST = HELLO_CONST + "%s!";
    }

    public static void main(String[] args) {
        if (args != null && args.length == 1) {
            System.out.println(String.format(CONST, args[0]));
        }
    }
}

對應字節碼
簡記 ms:操做數棧最大深度 ml:局部變量表最大容量 s:操做數棧 l:局部變量表

// class version 49.0 (49)
// access flags 0x21
public class com/taobao/film/Demo {

  // compiled from: Demo.java

  // access flags 0x1A 常量,類被裝載時分配空間 private static final String HELLO_CONST = "Hello";
  private final static Ljava/lang/String; HELLO_CONST = "Hello"

  // access flags 0xA 靜態屬性,類被裝載時分配空間 private static String CONST ;
  private static Ljava/lang/String; CONST

  // access flags 0x1 沒有重載構造函數,默認構造函數<init>()V
  public <init>()V
   L0 // 標籤表示方法的字節碼中的位置。標籤用於跳轉,goto和切換指令,以及用於嘗試catch塊。標籤指定剛剛以後的指令。注意,在標籤和它指定的指令(例如其餘標籤,堆棧映射幀,行號等)之間能夠有其餘元素。
    LINENUMBER 6 L0 //異常的棧信息中對應的line number,可刪除不影響運行
    ALOAD 0 //this
    INVOKESPECIAL java/lang/Object.<init> ()V //this.super()
    RETURN
   L1
    LOCALVARIABLE this Lcom/taobao/film/Demo; L0 L1 0
    MAXSTACK = 1 //最大棧深度1,壓棧局部變量this
    MAXLOCALS = 1 //局部變量數1,局部變量this

  // access flags 0x9 簡記 ms:操做數棧最大深度 ml:局部變量表最大容量 s:操做數棧 l:局部變量表
  public static main([Ljava/lang/String;)V
   L0
    LINENUMBER 15 L0
    ALOAD 0 //非靜態方法,局部變量0即方法入參第一個參數args reference; ms=1,ml=1,s=[args ref],l=[args ref]
    IFNULL L1  //if(args ref==null)跳轉 L1 return
    ALOAD 0 //load args ms=1,ml=1,s=[args ref],l=[args ref]
    ARRAYLENGTH//計算args.length將結果入操做數棧 ms=1,ml=1,s=[args ref],l=[args ref] 
    ICONST_1 //數字常量1 ms=2,ml=1,s=[1,args ref],l=[args ref] 
    IF_ICMPNE L1 //if(args.length == 1) 跳轉 L1 return 
   L2 //ms=2,ml=1,s=[],l=[args ref] 
    LINENUMBER 16 L2
    GETSTATIC java/lang/System.out : Ljava/io/PrintStream;//訪問System.out,入操做數棧 ms=2,ml=1,s=[System.out],l=[args ref]  
    GETSTATIC com/taobao/film/Demo.CONST : Ljava/lang/String;//訪問CONST,入操做數棧 ms=2,ml=1,s=[CONST,System.out],l=[args ref]  
    {//這裏一段,實現的是new Object[]{args[0]},String.format(String format, Object... args) 
    ICONST_1//1  ms=3,ml=1,s=[1,CONST,System.out],l=[args ref]  
    ANEWARRAY java/lang/Object// new Object[1]; ms=3,ml=1,s=[objects ref,CONST,System.out],l=[args ref]  
    DUP // ms=4,ml=1,s=[objects ref,objects ref,CONST,System.out],l=[args ref]  
    ICONST_0//0 ms=5,ml=1,s=[0,objects ref,objects ref,CONST,System.out],l=[args ref]  
    ALOAD 0 //args reference入操做數棧; ms=6,ml=1,s=[args ref,0,objects ref,objects ref,CONST,System.out],l=[args ref]  
    ICONST_0 //0 ms=7,ml=1,s=[0,args ref,0,objects ref,objects ref,CONST,System.out],l=[args ref] 
    AALOAD //args[0] ms=7,ml=1,s=[args[0],0,objects ref,objects ref,CONST,System.out],l=[args ref]
    AASTORE //objects[0]=args[0]; ms=7,ml=1,s=[objects ref,CONST,System.out],l=[args ref]
    }
    INVOKESTATIC java/lang/String.format (Ljava/lang/String;[Ljava/lang/Object;)Ljava/lang/String;//String.format(CONST,objects) ms=7,ml=1,s=[objects ref,CONST,System.out],l=[args ref]
    INVOKEVIRTUAL java/io/PrintStream.println (Ljava/lang/String;)V //System.out.println(...); ms=7,ml=1,s=[],l=[args ref]
   L1
    LINENUMBER 18 L1
    RETURN
   L3
    LOCALVARIABLE args [Ljava/lang/String; L0 L3 0
    MAXSTACK = 7
    MAXLOCALS = 1

  // access flags 0x8 <clinit>V classload init,靜態代碼初始塊,類被裝在時init
  static <clinit>()V
   L0
    LINENUMBER 8 L0 
    ACONST_NULL //壓棧常量NULL
    PUTSTATIC com/taobao/film/Demo.CONST : Ljava/lang/String; //靜態屬性賦值,CONST=null
   L1
    LINENUMBER 11 L1
    LDC "Hello%s!" //加載常量Hello%s! 編譯優化,hello+%s編譯優化爲常量Hello%s!
    PUTSTATIC com/taobao/film/Demo.CONST : Ljava/lang/String;//賦值Hello%s!
   L2
    LINENUMBER 12 L2
    RETURN
    MAXSTACK = 1 //只有賦值操做,操做數棧深度1
    MAXLOCALS = 0 //靜態方法無局部變量
}

藉助字節碼能幹什麼

回答這個問題前咱們首先得明白字節碼是做用於運行期,因此通常用來在運行期改變類的行爲,基本上都會結合代理模式使用。

常見字節碼框架

本文來介紹下ASM框架的使用(瞭解一點底層字節碼操做,對了解JVM執行過程以及咱們經常使用框架的實現原理都會有新的認識)

ClassReader用來讀取原有的字節碼,ClassWriter用於寫入字節碼,ClassVisitor、FieldVisitor、MethodVisitor、AnnotationVisitor訪問修改對應組件。(通常不要經過ClassReader讀取再經過Vistor修改類型爲再ClassWriter回寫,覆蓋原有類行爲,採用繼承會更安全)

注:

使用示例

如下代碼效果等同於上面的Demo示例

import java.io.FileOutputStream;
import java.lang.reflect.Method;

import org.objectweb.asm.AnnotationVisitor;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.FieldVisitor;
import org.objectweb.asm.Label;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;

public class DemoDump implements Opcodes {

    public static byte[] dump() throws Exception {

        ClassWriter cw = new ClassWriter(0);
        FieldVisitor fv;
        MethodVisitor mv;
        AnnotationVisitor av0;

        cw.visit(V1_5, ACC_PUBLIC + ACC_SUPER, "com/taobao/film/Demo", null, "java/lang/Object", null);

        cw.visitSource("Demo.java", null);

        {
            fv = cw.visitField(ACC_PRIVATE + ACC_FINAL + ACC_STATIC, "HELLO_CONST", "Ljava/lang/String;", null,
                "Hello");
            fv.visitEnd();
        }
        {
            fv = cw.visitField(ACC_PRIVATE + ACC_STATIC, "CONST", "Ljava/lang/String;", null, null);
            fv.visitEnd();
        }
        {
            mv = cw.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
            mv.visitCode();
            Label l0 = new Label();
            mv.visitLabel(l0);
            mv.visitLineNumber(6, l0);
            mv.visitVarInsn(ALOAD, 0);
            mv.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
            mv.visitInsn(RETURN);
            Label l1 = new Label();
            mv.visitLabel(l1);
            mv.visitLocalVariable("this", "Lcom/taobao/film/Demo;", null, l0, l1, 0);
            mv.visitMaxs(1, 1);
            mv.visitEnd();
        }
        {
            mv = cw.visitMethod(ACC_PUBLIC + ACC_STATIC, "main", "([Ljava/lang/String;)V", null, null);
            mv.visitCode();
            Label l0 = new Label();
            mv.visitLabel(l0);
            mv.visitLineNumber(15, l0);
            mv.visitVarInsn(ALOAD, 0);
            Label l1 = new Label();
            mv.visitJumpInsn(IFNULL, l1);
            mv.visitVarInsn(ALOAD, 0);
            mv.visitInsn(ARRAYLENGTH);
            mv.visitInsn(ICONST_1);
            mv.visitJumpInsn(IF_ICMPNE, l1);
            Label l2 = new Label();
            mv.visitLabel(l2);
            mv.visitLineNumber(16, l2);
            mv.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
            mv.visitFieldInsn(GETSTATIC, "com/taobao/film/Demo", "CONST", "Ljava/lang/String;");
            mv.visitInsn(ICONST_1);
            mv.visitTypeInsn(ANEWARRAY, "java/lang/Object");
            mv.visitInsn(DUP);
            mv.visitInsn(ICONST_0);
            mv.visitVarInsn(ALOAD, 0);
            mv.visitInsn(ICONST_0);
            mv.visitInsn(AALOAD);
            mv.visitInsn(AASTORE);
            mv.visitMethodInsn(INVOKESTATIC, "java/lang/String", "format",
                "(Ljava/lang/String;[Ljava/lang/Object;)Ljava/lang/String;", false);
            mv.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
            mv.visitLabel(l1);
            mv.visitLineNumber(18, l1);
            mv.visitInsn(RETURN);
            Label l3 = new Label();
            mv.visitLabel(l3);
            mv.visitLocalVariable("args", "[Ljava/lang/String;", null, l0, l3, 0);
            mv.visitMaxs(7, 1);
            mv.visitEnd();
        }
        {
            mv = cw.visitMethod(ACC_STATIC, "<clinit>", "()V", null, null);
            mv.visitCode();
            Label l0 = new Label();
            mv.visitLabel(l0);
            mv.visitLineNumber(8, l0);
            mv.visitInsn(ACONST_NULL);
            mv.visitFieldInsn(PUTSTATIC, "com/taobao/film/Demo", "CONST", "Ljava/lang/String;");
            Label l1 = new Label();
            mv.visitLabel(l1);
            mv.visitLineNumber(11, l1);
            mv.visitLdcInsn("Hello%s!");
            mv.visitFieldInsn(PUTSTATIC, "com/taobao/film/Demo", "CONST", "Ljava/lang/String;");
            Label l2 = new Label();
            mv.visitLabel(l2);
            mv.visitLineNumber(12, l2);
            mv.visitInsn(RETURN);
            mv.visitMaxs(1, 0);
            mv.visitEnd();
        }
        cw.visitEnd();

        return cw.toByteArray();
    }

    public static class MyClassLoader extends ClassLoader {
        public MyClassLoader() {
            super();
        }

        public MyClassLoader(ClassLoader cl) {
            super(cl);
        }

        public Class<?> defineClass(String name, byte[] b) {
            return defineClass(name, b, 0, b.length);
        }
    }

    public static void main(String[] args) throws Exception {
        byte[] bytes = dump();
        FileOutputStream fileOutputStream = new FileOutputStream("D:\\Demo.class");
        fileOutputStream.write(bytes);
        fileOutputStream.close();
        MyClassLoader classLoader = new MyClassLoader(Thread.currentThread().getContextClassLoader());
        Class<?> testClass = classLoader.defineClass("com.taobao.film.Demo",
            bytes);
        // invoke static
        Method staticMain = testClass.getMethod("main", String[].class);
        staticMain.invoke(null, new Object[] {new String[] {"zhupin"}});
    }
}

XML快速序列化

爲無狀態邏輯類的指定函數產生一個代理,代理接口接受字符串數組,轉換後調用原函數

知名框架中的使用

  • fastjson hotcode asm
  • dubbo hsf javaassist
  • hibernate cglib javaassist
  • spring aop cglib (經過設置-Dcglib.debugLocation=D://tmp 開啓classdump)
  • mockito cglib

建議

最先接觸字節碼編程仍是在使用IL(C# Emit),也在大型項目中使用過ASM得到了不錯的效果。不過我的以爲操做字節碼編程仍是要保持謹慎態度。下面是個人一點小建議:

  • 操做字節碼做用於運行期,對於開發人員是徹底透明的。
  • 字節碼編程的可閱讀可維護性比較差,不要濫用字節碼編程。

    • 能經過設計模式實現的場景儘可能經過設計模式實現;
    • 字節碼編程中複雜的邏輯也儘可能使用java實現,在字節碼中調用;
    • 使用字節碼解決一些框架性的問題,不要用於處理易變邏輯;
  • 字節碼編程從邏輯塊着手,優先明確程序跳轉Label,再補充邏輯執行
  • 藉助工具
    推薦使用intellj idea插件ASM Bytecode Outline,目前生成的字節碼對應到 ASM 5.x。

使用decompile工具校驗生成代碼是否正確。

更多文章請訪問個人博客轉載請註明出處

相關文章
相關標籤/搜索