教你用Java字節碼作點有趣的事(二)之ASM

0.寫在前面

本篇是本系列的第二篇,主要介紹什麼是ASM,以及如何使用ASM。 若是沒有閱讀以前的教你用Java字節碼作點有趣的事,還請閱讀一下,由於須要上一章的部分需求。java

1.什麼是ASM

在上節咱們知道,經過javac編譯生成以後生成的是字節碼,可是咱們可能會有一些需求,好比須要AOP切面,事務的統一管理,有些重複的代碼須要咱們來回的敲,又或者咱們須要生成本身的字節碼來使用(fastjson就是這麼作的)。可是字節碼若是咱們直接操做,成本太大,而且效率也不高。這個時候你就須要一款利器,將字節碼轉換成java語言,從而你就能夠爲所欲爲的操縱字節碼。這些工具如ASM,例如Javaassit,BCEL等等,均可以用來操做字節碼。 而這裏我要介紹的就是操做字節碼的一把利器-ASM,ASM是一個java字節碼操縱框架,它能被用來動態生成類或者加強既有類的功能。ASM 能夠直接產生二進制 class 文件,也能夠在類被加載入 Java 虛擬機以前動態改變類行爲。Java class 被存儲在嚴格格式定義的 .class文件裏,這些類文件擁有足夠的元數據來解析類中的全部元素:類名稱、方法、屬性以及 Java 字節碼(指令)。ASM從類文件中讀入信息後,可以改變類行爲,分析類信息,甚至可以根據用戶要求生成新類。 ASM的優勢以下:web

  • 有一個簡單的模塊API,設計完善,使用方便。
  • 更新速度快,支持最新的Java版本
  • 小而快,很是可靠
  • 已經有不少著名的開源框架都在使用,例如cglib,spring,fastjson等等。
  • 源許可開放,幾乎容許任意使用。 asm的具體流程以下所示:

2.ASM的簡單入門

在這個小標題我會簡單的介紹,如何去使用ASM。在這裏以前我但願你有idea編譯器,若是你有的話,能夠去插件庫裏面下載一個ASM Bytecode Outline。有了這個咱們後面開發ASM將會感覺到美滋滋,如魚得水。面試

ASM 庫􁨀供了兩個用於生成和轉換已編譯類的API,一個是核心API,以基於事件的形式來表示類,另外一個是樹API,以基於對象的形式來表示類。spring

  • 核心api,能夠對比XML中解析的SAX,不須要把這個類的整個結構讀取進來,節約內存,可是編程難度較大。在採用基於事件的模型時,類是用一系列事件來表示的,每一個事件表示類的一個元素,好比它的一個字段、一個方法聲明、一條指令,等等。基於事件的API定義了一組可能事件,以及這些事件必須遵循的發生順序,還􁨀供了一個類分析器,爲每一個被分析元素生成一個事件,還􁨀供一個類寫入器,由這些事件的序列生成通過編譯的類。
  • 樹API,對比XML解析中的DOM,須要把整個類的結構讀取到內存中,消耗內存多,可是變成較爲簡單

沒有asm jar包的同窗須要引入下面的maven:編程

<dependency>
            <groupId>org.ow2.asm</groupId>
            <artifactId>asm</artifactId>
            <version>5.0.4</version>
        </dependency>
複製代碼

2.1核心api

這裏你們是否已經下好了那個插件(ASM Bytecode Outline)呢?若是下載完畢,還記得咱們上一節的那個例子嗎?json

public class ByteCodeDemo {
    private static final String name = "xiaoming";

    private int age;

    public ByteCodeDemo(int age) {
        this.age = age;
    }

    public int getAge() {
        return age;
    }
    public void setAge(){
        this.age = age;
    }
    public static void main(String[] args) {
        ByteCodeDemo byteCodeDeomo = new ByteCodeDemo(12);
        System.out.println("name:" + name + "age:" + byteCodeDeomo.getAge());
    }
}
複製代碼

在這個類中,右鍵點擊下方後端

而後右方會生成咱們的若是用ASM生成這個類,那麼應該是哪些代碼:

複製旁邊的代碼,你就能生成你的class的二進制文件。

若是你看不懂,不要緊,我這裏會慢慢的講。api

2.1.1 核心類

在ASM的core API編程中有幾個關鍵類:bash

  • ClassReader:能夠讀取編譯好的二進制Class文件
  • ClassWriter:用來從新構建編譯後的類,好比說修改類名、屬性以及方法,甚至能夠生成新的類的字節碼文件。
  • ClassVisitor:用於生成和變轉已編譯類。在ClassVisitor定義了不少方法,例如:類上的註解,類的構造方法,類的字段,類的方法,靜態代碼塊訪問。用於咱們去重寫,以便作一些類上的邏輯擴展。要注意的是ClassWriter繼承的是ClassVistor,這裏ClassWriter就能夠邊訪問邊寫入。
  • 上面說了ClassVisitor,這裏再說說其餘的Visitor,ASM core api裏的代碼是根據字節碼從上到下依次生成,能夠看見裏面還有一些其餘的Visitor,好比MethodVisitor,用於訪問Method,若是重寫其部分方法能夠對方法進行修改。FieldVisitor,用於訪問類的變量,常量,若是重寫其部分方法能夠進行修改。AnnotationVisitor,用於訪問的註解。

visitor是咱們擴展咱們類本身碼的關鍵架構

2.2 coreApi如何編寫代碼

public static void main(String[] args) throws Exception {
        FileInputStream fileInputStream = new FileInputStream("java/java8/ByteCodeDemo.class");
        ClassReader classReader = new ClassReader(fileInputStream);
        ClassWriter cw = new ClassWriter(classReader, ClassWriter.COMPUTE_MAXS);
        //Java8選擇ASM5,
        ClassVisitor classVisitor = new ClassVisitor(Opcodes.ASM5, cw) {
            @Override
            public FieldVisitor visitField(int access, String name, String desc, String signature, Object value) {
                System.out.println("field:" + name);
                return super.visitField(access, name, desc, signature, value);
            }

            @Override
            public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
                System.out.println("方法" + name);
                return super.visitMethod(access, name, desc, signature, exceptions);
            }
        };
        //忽略調試信息
        classReader.accept(classVisitor, ClassReader.SKIP_DEBUG);

    }
複製代碼

咱們下面輸出:

field:name
field:age
方法<init>
方法getAge
方法setAge
方法main
複製代碼

能夠看到咱們已經經過visitField和visitMethod,進行對每一個field和每一個Method的名字都進行了輸出,其中方法包括了編譯器幫咱們建立的構造方法和咱們自定義的三個方法。

3.樹形API

因爲後面的代碼都會經過coreApi來作,這裏樹形API簡單用例子說明一下:

public static void main(String[] args) throws Exception {
        FileInputStream fileInputStream = new FileInputStream("/Users/lizhao/Documents/RPC/test/src/main/java/java8/ByteCodeDemo.class");
        ClassReader classReader = new ClassReader(fileInputStream);
        ClassWriter cw = new ClassWriter(classReader, ClassWriter.COMPUTE_MAXS);
        //忽略調試信息
        ClassNode classNode = new ClassNode(org.objectweb.asm.Opcodes.ASM5);


        classReader.accept(classNode, ClassReader.SKIP_DEBUG);
        for (MethodNode methodNode:classNode.methods) {
            System.out.println(methodNode.name);
        }
        classNode.accept(cw);
    }
複製代碼

輸出以下:

<init>
getAge
setAge
main
複製代碼

4.最後

本文簡單介紹了ASM的介紹,以及簡單的使用,下一章會介紹如何去用ASM作上一篇介紹的小工具,以及java的instrument機制。 同時想獲取ASM更多高級用法能夠關注個人公衆號回覆asm便可獲取。

因爲水平不足,若有錯誤還請批評與指正!

爲了方便你們學習交流,建了個qq java後端交流羣:837321192,裏面有我收藏的百G學習視頻(涵蓋面試,架構等等),也有不少面試資料,能夠加入進來一塊兒交流。

若是你們以爲這篇文章對你有幫助,或者想提早獲取後續章節文章,或者你有什麼疑問想提供1v1免費vip服務,均可以關注個人公衆號,關注便可免費領取上百G最新java學習資料視頻,以及最新面試資料,你的關注和轉發是對我最大的支持,O(∩_∩)O:

相關文章
相關標籤/搜索