深入理解《java 7 核心技術和最佳實踐》讀書筆記--(4)

java源代碼到字節代碼再到虛擬機的運行過程,每一步都有不同的實現方式,比如,可以不使用javac,直接在運行時動態編譯源代碼;字節代碼不由編譯器生成,而是使用工具來動態創建;在字節代碼被虛擬機執行前,可以通過修改字節代碼的內容來改變程序行爲

字節代碼的一些知識點

  • 字節代碼並不是只存在於class文件中,還可以通過網絡下載,或者由程序動態生成,字節代碼更精確的定義是包含單個java類或接口定義的字節流,通常由byte[]表示
  • 在java源代碼中引用一個接口或類,可以通過import語句,減少麻煩。但是在java字節碼中,始終使用全名,並且把全名中的.替換爲/,即類似com/java/xx/sample這樣的形式
  • java字節碼中的常量池的作用:常量池中包含的是java中基本類型和字符串常量值、類和接口的名稱及域的名稱。這些常量被代碼中其他部分引用。相同的常量只會出現一次。可以將常量池看作是一個常量的查找表
  • 字節代碼中只存在基本類型 int、long、float、double,其他基本類型都由int來表示

動態編譯java源代碼

通過動態編譯java源代碼,可以把編譯和運行統一起來,都在運行時完成,基本思路就是運行時使用API編譯出字節代碼,再使用類加載器加載到虛擬機中運行,運行時一般使用java 反射API(其實方法句柄也ok吧?)

  • 使用javac工具,代碼中運行javac命令,缺點是輸入輸出都只能以文件形式存在
  • java編譯器API,編譯器API僅包含接口聲明,對應的實現由平臺實現者提供(類似SPI?).使用者通過工廠方法或服務加載器來查找具體實現。(比javac工具粒度更細,可以控制更多細節)
  • Eclipse JDT編譯器,是eclipse自己開發的,提供的接口比java編譯器API更加豐富,但使用頁更復雜

字節代碼增強

字節代碼增強的含義就是對已有的java字節代碼進行修改,改變其運行的行爲。增強的行爲可以發生在程序運行前或運行時。發生在程序運行前的做法是先對字節代碼進行處理,得到修改後的字節碼,再有虛擬機運行(編譯完成後,由工具對編譯後的字節代碼進行處理)。程序運行時的做法是在字節代碼將要被虛擬機加載之前對字節代碼進行修改,通常有增強代理和類加載器實現(一般框架使用第二種方式)

使用ASM

可以實現下列三種(使用ASM即是在程序運行時的字節碼增加)

  • 讀取字節代碼
  • 生成字節代碼
  • 修改字節代碼
讀取字節代碼

import org.objectweb.asm.*;

import java.io.IOException;

/**
 * 利用asm統計String類中方法個數
 * @author: whp
 * @create: 2020-01-17 23:54
 **/
public class MethodCounter implements ClassVisitor {

    private int count = 0;

    @Override
    public void visit(int i, int i1, String s, String s1, String s2, String[] strings) {

    }

    @Override
    public void visitSource(String s, String s1) {

    }

    @Override
    public void visitOuterClass(String s, String s1, String s2) {

    }

    @Override
    public AnnotationVisitor visitAnnotation(String s, boolean b) {
        return null;
    }

    @Override
    public void visitAttribute(Attribute attribute) {

    }

    @Override
    public void visitInnerClass(String s, String s1, String s2, int i) {

    }


    @Override
    public FieldVisitor visitField(int i, String s, String s1, String s2, Object o) {

        return null;
    }
    //訪問訪問時,該方法會被調用
    @Override
    public MethodVisitor visitMethod(int i, String s, String s1, String s2, String[] strings) {
        count++;
        System.out.println(s);//打印出方法名
        return null;
    }

    @Override
    public void visitEnd() {

    }
    public int getCount(){
        return  count;
    }

    public static void main(String[] args) throws IOException {
        ClassReader reader = new ClassReader("java.lang.String");
        MethodCounter methodCounter = new MethodCounter();
        reader.accept(methodCounter,0);
        System.out.println(methodCounter.getCount());
    }
}

生成字節代碼

一般先按照設計思路編寫最終的java源代碼,在編譯字節碼,再利用asm中類逆向生成,使用ASM的java代碼!

修改字節代碼

基本思想類似,也是會利用逆向的思想

增強代理

ASM 的 使用方式 主要分爲兩種

  • 在源代碼編譯之後,在運行相關的程序來對字節代碼進行處理
  • 運行時處理,可以使用類加載器來實現,不過比較麻煩,一種更簡單的方式是使用 java.lang.instrument包提供的API,其中比較重要的是Instrumentation這個接口

增強代理的的用法

代理程序是一個jar包,該jar包清單文件中定義了啓動代理的java類名稱,不同的虛擬機實現提供的啓動代理的方式不盡相同。主要有以下兩種。第一種做法是通過虛擬機的啓動參數指定代理程序jar包的路徑。所用參數是“-javaagent”,代理jar包的清單文件要包含Premain-Class屬性。該屬性指定一個java類,該類中包含一個premain方法,虛擬機啓動之後,代理類的premain會先被調用,然後纔是主java類的main方法被調用 ; 第二種方法是虛擬機運行主程序之後,再啓動代理程序,這類的代理程序jar包清單文件要有Agent-Class屬性指明代理類的名稱。當代理類被加載後,虛擬機會嘗試調用類中的agentmain方法

詳細參考
基於Java Instrument的Agent實現

註解的使用場景

處理註解的兩種方式:

  • 一種是編譯時,對於保留策略是SOURCE的註解來說,編譯時是惟一的處理方式,類似@override,@deprecated ,拓展知識 JSR-269 (可插拔式註解處理機制)
  • 對於註解聲明爲運行時仍然保留,那麼可以通過反射API操作
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章