1. 程式人生 > >關於java位元組碼框架ASM的學習

關於java位元組碼框架ASM的學習

原文:http://www.cnblogs.com/liuling/archive/2013/05/25/asm.html

 一、什麼是ASM

  ASM是一個java位元組碼操縱框架,它能被用來動態生成類或者增強既有類的功能。ASM 可以直接產生二進位制 class 檔案,也可以在類被載入入 Java 虛擬機器之前動態改變類行為。Java class 被儲存在嚴格格式定義的 .class檔案裡,這些類檔案擁有足夠的元資料來解析類中的所有元素:類名稱、方法、屬性以及 Java 位元組碼(指令)。ASM從類檔案中讀入資訊後,能夠改變類行為,分析類資訊,甚至能夠根據使用者要求生成新類。

  使用ASM框架需要匯入asm的jar包,下載連結:

asm-3.2.jar

  二、如何使用ASM

  ASM框架中的核心類有以下幾個:

  ①  ClassReader:該類用來解析編譯過的class位元組碼檔案。

  ②  ClassWriter:該類用來重新構建編譯後的類,比如說修改類名、屬性以及方法,甚至可以生成新的類的位元組碼檔案。

  ③  ClassAdapter:該類也實現了ClassVisitor介面,它將對它的方法呼叫委託給另一個ClassVisitor物件。

  示例1.通過asm生成類的位元組碼

package com.asm3;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;

import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.Opcodes;

/**
 * 通過asm生成類的位元組碼
 * @author Administrator
 *
 */
public class GeneratorClass {

    public static void main(String[] args) throws IOException {
        //生成一個類只需要ClassWriter元件即可
        ClassWriter cw = new ClassWriter(0);
        //通過visit方法確定類的頭部資訊
        cw.visit(Opcodes.V1_5, Opcodes.ACC_PUBLIC+Opcodes.ACC_ABSTRACT+Opcodes.ACC_INTERFACE,
                "com/asm3/Comparable", null, "java/lang/Object", new String[]{"com/asm3/Mesurable"});
        //定義類的屬性
        cw.visitField(Opcodes.ACC_PUBLIC+Opcodes.ACC_FINAL+Opcodes.ACC_STATIC,
                "LESS", "I", null, new Integer(-1)).visitEnd();
        cw.visitField(Opcodes.ACC_PUBLIC+Opcodes.ACC_FINAL+Opcodes.ACC_STATIC,
                "EQUAL", "I", null, new Integer(0)).visitEnd();
        cw.visitField(Opcodes.ACC_PUBLIC+Opcodes.ACC_FINAL+Opcodes.ACC_STATIC,
                "GREATER", "I", null, new Integer(1)).visitEnd();
        //定義類的方法
        cw.visitMethod(Opcodes.ACC_PUBLIC+Opcodes.ACC_ABSTRACT, "compareTo",
                "(Ljava/lang/Object;)I", null, null).visitEnd();
        cw.visitEnd(); //使cw類已經完成
        //將cw轉換成位元組陣列寫到檔案裡面去
        byte[] data = cw.toByteArray();
        File file = new File("D://Comparable.class");
        FileOutputStream fout = new FileOutputStream(file);
        fout.write(data);
        fout.close();
    }
}
 生成一個類的位元組碼檔案只需要用到ClassWriter類即可,生成Comparable.class後用javap指令對其進行反編譯:javap -c Comparable.class >test.txt  ,編譯後的結果如下:
public interface com.asm3.Comparable extends com.asm3.Mesurable {
  public static final int LESS;

  public static final int EQUAL;

  public static final int GREATER;

  public abstract int compareTo(java.lang.Object);
}

注:一個編譯後的java類不包含package和import段,因此在class檔案中所有的型別都使用的是全路徑。

  示例2.修改類的位元組碼檔案

package com.asm5;

public class C {
    public void m() throws InterruptedException{
        Thread.sleep(100); 
    }
}
將C.java類的內容改為如下:
package com.asm5;

public class C {
    public static long timer;
    public void m() throws InterruptedException{
        timer -= System.currentTimeMillis();
        Thread.sleep(100); 
        timer += System.currentTimeMillis();
    }
}

為了弄清楚ASM是如何實現的,我們先編譯這兩個類,然後比對它們的TraceClassVisitor的輸出,我們可以發現如下的不同(粗體表示)

GETSTATIC C.timer : J
INVOKESTATIC java/lang/System.currentTimilis()J
LSUB
PUTSTATIC C.timer : J
LDC 100
INVOKESTATIC java/lang/Thread.sleep(J)V
GETSTATIC C.timer : J
INVOKESTATIC java/lang/System.currentTimilis()J
LADD
PUTSTATIC C.timer : J
RETURN
MAXSTACK=4
MAXLOCALS=1

  通過比對上面的指令,我們可以發現必須在m()方法的最前面增加四條指令,在RETURN指令前也增加四條指令,同時這四條必須位於xRETURN和ATHROW之前,因為這些指令都會結束方法的執行。

具體程式碼如下:

AddTimeClassAdapter.java

package com.asm5;

import org.objectweb.asm.ClassAdapter;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.FieldVisitor;
import org.objectweb.asm.MethodAdapter;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;

public class AddTimeClassAdapter extends ClassAdapter {
    private String owner;
    private boolean isInterface;
    public AddTimeClassAdapter(ClassVisitor cv) {
        super(cv);
    }
    @Override
    public void visit(int version, int access, String name, String signature,
            String superName, String[] interfaces) {
        cv.visit(version, access, name, signature, superName, interfaces);
        owner = name;
        isInterface = (access & Opcodes.ACC_INTERFACE) != 0;
    }
    @Override
    public MethodVisitor visitMethod(int access, String name, String desc,
            String signature, String[] exceptions) {
        MethodVisitor mv = cv.visitMethod(access, name, desc, signature, exceptions);
        if(!name.equals("<init>") && !isInterface && mv!=null){
            //為方法新增計時功能
            mv = new AddTimeMethodAdapter(mv);
        }
        return mv;
    }
    @Override
    public void visitEnd() {
        //新增欄位
        if(!isInterface){
            FieldVisitor fv = cv.visitField(Opcodes.ACC_PUBLIC+Opcodes.ACC_STATIC, "timer", "J", null, null);
            if(fv!=null){
                fv.visitEnd();
            }
        }
        cv.visitEnd();
    }
    
    class AddTimeMethodAdapter extends MethodAdapter{
        public AddTimeMethodAdapter(MethodVisitor mv) {
            super(mv);
        }
        @Override
        public void visitCode() {
            mv.visitCode();
            mv.visitFieldInsn(Opcodes.GETSTATIC, owner, "timer", "J");
            mv.visitMethodInsn(Opcodes.INVOKESTATIC, "java/lang/System", "currentTimeMillis", "()J");
            mv.visitInsn(Opcodes.LSUB);
            mv.visitFieldInsn(Opcodes.PUTSTATIC, owner, "timer", "J");
        }
        @Override
        public void visitInsn(int opcode) {
            if((opcode>=Opcodes.IRETURN && opcode<=Opcodes.RETURN) || opcode==Opcodes.ATHROW){
                mv.visitFieldInsn(Opcodes.GETSTATIC, owner, "timer", "J");
                mv.visitMethodInsn(Opcodes.INVOKESTATIC, "java/lang/System", "currentTimeMillis", "()J");
                mv.visitInsn(Opcodes.LADD);
                mv.visitFieldInsn(Opcodes.PUTSTATIC, owner, "timer", "J");
            }
            mv.visitInsn(opcode);
        }
        @Override
        public void visitMaxs(int maxStack, int maxLocal) {
            mv.visitMaxs(maxStack+4, maxLocal);
        }
    }
    
}

Generator.java

package com.asm5;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;

import org.objectweb.asm.ClassAdapter;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassWriter;



public class Generator {

    public static void main(String[] args){
        try {
            ClassReader cr = new ClassReader("com/asm5/C");
            ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS);
            ClassAdapter classAdapter = new AddTimeClassAdapter(cw);
            //使給定的訪問者訪問Java類的ClassReader
            cr.accept(classAdapter, ClassReader.SKIP_DEBUG);
            byte[] data = cw.toByteArray();
            File file = new File(System.getProperty("user.dir") + "\\WebRoot\\WEB-INF\\classes\\com\\asm5\\C.class");
            FileOutputStream fout = new FileOutputStream(file);
            fout.write(data);
            fout.close();
            System.out.println("success!");
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

}
下面是一個測試類:
package com.asm5;

public class Test {
    public static void main(String[] args) throws InterruptedException, NoSuchFieldException, SecurityException, IllegalArgumentException, IllegalAccessException {
        C c = new C();
        c.m();
        Class cc = c.getClass();
        System.out.println(cc.getField("timer").get(c));
    }
}





相關推薦

關於java位元組框架ASM學習

原文:http://www.cnblogs.com/liuling/archive/2013/05/25/asm.html 一、什麼是ASM  ASM是一個java位元組碼操縱框架,它能被用來動態生成類或者增強既有類的功能。ASM 可以直接產生二進位制 class 檔案,也可

動態生成Java位元組java位元組框架ASM學習

一、什麼是ASM   ASM是一個java位元組碼操縱框架,它能被用來動態生成類或者增強既有類的功能。ASM 可以直接產生二進位制 class 檔案,也可以在類被載入入 Java 虛擬機器之前動態改變類行為。Java class 被儲存在嚴格格式定義的 .class

Java 位元組ASM 實踐

1. 概述 AOP(面向切面程式設計)的概念現在已經應用的非常廣泛了,下面是從百度百科上摘抄的一段解釋,比較淺顯易懂 在軟體業,AOP為Aspect Oriented Programming的縮寫,意為:面向切面程式設計,通過預編譯方式和執行期動態代理實現程式功能的統一維護的一種技術。AOP是OOP

java位元組框架效能對比

因為專案需要用到動態代理,糾結於jdk原生代理,cglib和spring包裝過的cglib,以及javassist和byte code javassist之間的技術選型。所以寫個test case,測試他們之間的效能差異。話不多說,上程式碼。package proxy; im

Java位元組插樁修改HiBeaver(結合ASM,實現Hook需求、輕量級AOP、無埋點統計上報等)

Java彙編指令- https://segmentfault.com/a/1190000008606277 一次Android位元組碼插樁實戰- https://segmentfault.com/a/1190000008658815 Android位元組碼修改神器HiBeav

JAVA-代塊(自己學習當筆記而已)

釋放 AI 同步代碼塊 分類 ring {} [] 構造方法 驅動 * A:代碼塊概述 * 在Java中,使用{}括起來的代碼被稱為代碼塊。 * B:代碼塊分類 * 根據其位置和聲明的不同,可以分為局部代碼塊,構造代碼塊,靜態代碼塊,同步代碼塊(多線程講解)

Java】 Spring 框架初步學習總結(一)簡單實現 IoC 和 AOP

1.0 其中 表示 只需要 第一篇 否則 info fin pojo   Spring 是一個開源的設計層面的輕量級框架,Spring 的好處網上有太多,這裏就不在贅述。   IoC 控制反轉和 AOP 面向切面編程是 Spring 的兩個重要特性。   IoC(Inver

一文讓你明白 Java 位元組

前言 也許你寫了無數行的程式碼,也許你能非常溜的使用高階語言,但是你未必瞭解那些高階語言的執行過程。例如大行其道的Java。 Java號稱是一門“一次編譯到處執行”的語言,但是我們對這句話的理解深度又有多少呢?從我們寫的java檔案到通過編譯器編譯成java位元組碼檔案(也就是.class檔案),這個過程

大話+圖說:Java位元組指令——只為讓你懂

前言 隨著Java開發技術不斷被推到新的高度,對於Java程式設計師來講越來越需要具備對更深入的基礎性技術的理解,比如Java位元組碼指令。不然,可能很難深入理解一些時下的新框架、新技術,盲目一味追新也會越來越感乏力。 本文既不求照本宣科,亦不求炫技或著文立說,僅力圖以最簡明、最形象生動的方式,結合例子與

java位元組-this分析

1.this我們用的非常多,但是沒有搞清楚為啥我們可以在例項方法中使用this。這裡我從java位元組碼的角度來分析this。 2.程式碼: public class Test { private static String hello(String hello){

深入理解java位元組

Javap 反編譯class檔案 –verbose 顯示冗餘資訊 (1)魔數:所有的class位元組碼檔案的4個位元組都是魔數,魔數固定值:0xCAFEBABE (2)版本:魔數之後4個位元組是版本資訊,前兩個位元組minor version次版本號例如0,後兩個位元組是主機板號majo

例項分析理解Java位元組

Java語言最廣為人知的口號就是“一次編譯到處執行”,這裡的“編譯”指的是編譯器將Java原始碼編譯為Java位元組碼檔案(也就是.class檔案,本文中不做區分),“執行”則指的是Java虛擬機器執行位元組碼檔案。Java的跨平臺得益於不同平臺上不同的JVM的實現,只要提供規範的位元組碼檔案,無論是什麼平臺

Java位元組結構剖析二:欄位表

access_flags 訪問標誌資訊包括該class檔案是類還是介面,是否定義成public,是否是abstract,如果是類,是否被申明為final。access_flags 的取值範圍和相應含義見下表。 我們的位元組碼裡該位置的16進製表示是0×0021。0×0021=0×0001 ^ 0×00

Java位元組結構剖析三:方法表

這裡給大家介紹一款位元組碼分析小工具——jclasslib bytecode viewer。它可以將位元組碼檔案結構化的展現給我們看。 緊接著上篇『欄位表』的分析。後面的分析輪到了『方法表』。 方法表結構 u2 method_count:方法計數器,metho

Java位元組結構剖析一:常量池

這篇部落格開始,我打算帶大家去解讀一下JVM平臺下的位元組碼檔案(熟悉而又陌生的感覺)。眾所周知,Class檔案包含了我們定義的類或介面的資訊。然後位元組碼又會被JVM載入到記憶體中,供JVM使用。那麼,類資訊到了位元組碼檔案裡,它們如何表示的,以及在位元組碼裡是怎麼分佈的呢?帶著這些問題,讓我們

Java位元組指令收集大全

Java位元組碼指令大全 常量入棧指令 指令碼 操作碼(助記符) 運算元 描述(棧指運算元棧) 0x01 aconst_null  

JAVA介面自動化框架testng學習

一、. testng.xml檔案 1. 宣告suite,描述要執行的測試指令碼集合,可以根據自己需要任意命名,最終這個名字會在testng的測試報告中展示 <?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE

Java位元組詳解(三)位元組指令(轉)

一、概述 Java虛擬機器採用基於棧的架構,其指令由操作碼和運算元組成。 操作碼:一個位元組長度(0~255),意味著指令集的操作碼個數不能操作256條。 運算元:一條指令可以有零或者多個運算元,且運算元可以是1個或者多個位元組。編譯後的程式碼沒有采用運算元長

一文讓你明白Java位元組

也許你寫了無數行的程式碼,也許你能非常溜的使用高階語言,但是你未必瞭解那些高階語言的執行過程。例如大行其道的Java。 Java號稱是一門“一次編譯到處執行”的語言,但是我們對這句話的理解深度又有多少呢?從我們寫的java檔案到通過編譯器編譯成java位元組碼檔案(也就是.

java中集合框架綜合學習

本文主要關注Java程式設計中涉及到的各種集合類,以及它們的使用場景 相關學習資料 http://files.cnblogs.com/LittleHann/java%E9%9B%86%E5%90%88%E6%8E%92%E5%BA%8F%E5%8F%8Ajava%E9%9B%86%E5%90%88%E7%