JVM学习笔记3:字节码操纵框架ASM

10 篇文章 0 订阅
1 篇文章 0 订阅

一:ASM概述

         ASM是一个Java字节码操纵框架,它能被用来动态生成类或者增强既有类的功能。ASM可以直接产生二进制class文件,也可以在类被加载入Java虚拟机之前动态改变类行为,ASM从类文件中读入信息后,能够改变类行为,分析类信息,甚至能够根据用户要求生成新类。目前许多框架如cglib、Hibernate、Spring都直接或间接地使用ASM操作字节码。


二:ASM的Core API

 

Core API 中操纵字节码的功能基于 ClassVisitor 接口。这个接口中的每个方法对应了 class文件中的每一项。ASM 提供了三个基于 ClassVisitor 接口的类来实现 class 文件的生成和转换:

      1:ClassReader:ClassReader 解析一个类的 class 字节码,该类的 accept 方法接受一个 ClassVisitor的对象,在 accept 方法中,会按上文描述的顺序逐个调用 ClassVisitor 对象的方法。

      2:ClassAdapter:ClassAdapter 是 ClassVisitor 的实现类。它的构造方法中需要一个 ClassVisitor 对象,并保存为字段 protected ClassVisitor cv。在它的实现中,每个方法都是原封不动的直接调用cv 的对应方法,并传递同样的参数。

     3:ClassWriter:ClassWriter 也是 ClassVisitor 的实现类。ClassWriter 可以用来以二进制的方式创建一个类的字节码。可以通过 toByteArray 方法获取生成的字节数组。

三:ASM的ASMifer工具

    ASM给我们提供了ASMifer工具来帮助开发,可使用ASMifer工具生成ASM结构来对比,比如使用命令:

java -cp .;asm-all-5.2.jar org.objectweb.asm.util.ASMifier Test (其中Test是编译后的class文件名

四:ASM实战(通过ASM为类的所有方法添加一个耗时计算的功能)

  1. 创建一个类CC.java 
    package com.yuy.jvm;
    
    public class CC {
        public void m() throws InterruptedException {
           
            System.out.println("now in CC.m");
    
            Thread.sleep(100L);
         
        }
       
    }

     

2.通过java -cp 查看其编译的class文件编码(java -cp .;asm-all-5.2.jar org.objectweb.asm.util.ASMifier CC)

package asm.com.yuy.jvm;
import java.util.*;
import org.objectweb.asm.*;
public class CCDump implements Opcodes {

public static byte[] dump () throws Exception {

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

cw.visit(V1_7, ACC_PUBLIC + ACC_SUPER, "com/yuy/jvm/CC", null, "java/lang/Object", null);

{
mv = cw.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
mv.visitCode();
mv.visitVarInsn(ALOAD, 0);
mv.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
mv.visitInsn(RETURN);
mv.visitMaxs(1, 1);
mv.visitEnd();
}
{
mv = cw.visitMethod(ACC_PUBLIC, "m", "()V", null, new String[] { "java/lang/InterruptedException" });
mv.visitCode();
mv.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
mv.visitLdcInsn("now in CC.m");
mv.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
mv.visitLdcInsn(new Long(100L));
mv.visitMethodInsn(INVOKESTATIC, "java/lang/Thread", "sleep", "(J)V", false);
mv.visitInsn(RETURN);
mv.visitMaxs(2, 1);
mv.visitEnd();
}
cw.visitEnd();

return cw.toByteArray();
}
}

3.修改CC.java类,(修改只是为了查看加上计时器的class编码,拷贝到编码之后需要将CC还原)如下:

package com.yuy.jvm;

public class CC {
    public void m() throws InterruptedException {
         long startTime = System.currentTimeMillis();
        System.out.println("now in CC.m");

        Thread.sleep(100L);
        long endTime = System.currentTimeMillis() - startTime;
         System.out.println("方法耗时为:"+endTime );
    }

}

4.通过java -cp 查看修改之后的class文件编码

package asm.com.yuy.jvm;
import java.util.*;
import org.objectweb.asm.*;
public class CCDump implements Opcodes {

public static byte[] dump () throws Exception {

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

cw.visit(V1_7, ACC_PUBLIC + ACC_SUPER, "com/yuy/jvm/CC", null, "java/lang/Object", null);

{
mv = cw.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
mv.visitCode();
mv.visitVarInsn(ALOAD, 0);
mv.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
mv.visitInsn(RETURN);
mv.visitMaxs(1, 1);
mv.visitEnd();
}
{
mv = cw.visitMethod(ACC_PUBLIC, "m", "()V", null, new String[] { "java/lang/InterruptedException" });
mv.visitCode();
mv.visitMethodInsn(INVOKESTATIC, "java/lang/System", "currentTimeMillis", "()J", false);
mv.visitVarInsn(LSTORE, 1);
mv.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
mv.visitLdcInsn("now in CC.m");
mv.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
mv.visitLdcInsn(new Long(100L));
mv.visitMethodInsn(INVOKESTATIC, "java/lang/Thread", "sleep", "(J)V", false);
mv.visitMethodInsn(INVOKESTATIC, "java/lang/System", "currentTimeMillis", "()J", false);
mv.visitVarInsn(LLOAD, 1);
mv.visitInsn(LSUB);
mv.visitVarInsn(LSTORE, 3);
mv.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
mv.visitTypeInsn(NEW, "java/lang/StringBuilder");
mv.visitInsn(DUP);
mv.visitMethodInsn(INVOKESPECIAL, "java/lang/StringBuilder", "<init>", "()V", false);
mv.visitLdcInsn("\u65b9\u6cd5\u8017\u65f6\u4e3a\uff1a");
mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder", "append", "(Ljava/lang/String;)Ljava/lang/StringBuilder;", false);
mv.visitVarInsn(LLOAD, 3);
mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder", "append", "(J)Ljava/lang/StringBuilder;", false);
mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder", "toString", "()Ljava/lang/String;", false);
mv.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
mv.visitInsn(RETURN);
mv.visitMaxs(4, 5);
mv.visitEnd();
}
cw.visitEnd();

return cw.toByteArray();
}
}

5.对比修改前后的class文件编码:

6.创建MyClassVisitor类:

package com.yuy.jvm.asm;


import org.objectweb.asm.AnnotationVisitor;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;

public class MyClassVisitor extends ClassVisitor {

    public MyClassVisitor(ClassVisitor cv){
        super(Opcodes.ASM5,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);
    }

    @Override
    public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
        MethodVisitor mv = cv.visitMethod(access,name,desc,signature,exceptions);
        //字节码中的init为构造方法
        if(!name.equals("<init>") && mv != null){
            //为方法增加计时的功能
            mv = new MyMethodVisitor(mv);
        }
        return mv;
    }
    class MyMethodVisitor extends MethodVisitor{
        public MyMethodVisitor(MethodVisitor mv){
            super(Opcodes.ASM5,mv);
        }

        @Override
        public void visitCode() {
            super.visitCode();
            mv.visitMethodInsn(Opcodes.INVOKESTATIC, "java/lang/System", "currentTimeMillis", "()J", false);
            mv.visitVarInsn(Opcodes.LSTORE, 1);
        }

        @Override
        public void visitInsn(int opcode) {
            if(opcode >= Opcodes.IRETURN && opcode <=Opcodes.RETURN || opcode ==Opcodes.ATHROW){
                mv.visitMethodInsn(Opcodes.INVOKESTATIC, "java/lang/System", "currentTimeMillis", "()J", false);
                mv.visitVarInsn(Opcodes.LLOAD, 1);
                mv.visitInsn(Opcodes.LSUB);
                mv.visitVarInsn(Opcodes.LSTORE, 3);
                mv.visitFieldInsn(Opcodes.GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
                mv.visitTypeInsn(Opcodes.NEW, "java/lang/StringBuilder");
                mv.visitInsn(Opcodes.DUP);
                mv.visitMethodInsn(Opcodes.INVOKESPECIAL, "java/lang/StringBuilder", "<init>", "()V", false);
                mv.visitLdcInsn("\u65b9\u6cd5\u8017\u65f6\u4e3a\uff1a");
                mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/StringBuilder", "append", "(Ljava/lang/String;)Ljava/lang/StringBuilder;", false);
                mv.visitVarInsn(Opcodes.LLOAD, 3);
                mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/StringBuilder", "append", "(J)Ljava/lang/StringBuilder;", false);
                mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/StringBuilder", "toString", "()Ljava/lang/String;", false);
                mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
            }
            mv.visitInsn(opcode);
        }
    }
}

7.创建Generator

package com.yuy.jvm.asm;

import com.sun.org.apache.xpath.internal.SourceTree;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.ClassWriter;

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

public class Generator {
    public static void main(String[] args) throws IOException {
        ClassReader cr = new ClassReader("com/yuy/jvm/CC");
        ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS);
        ClassVisitor cv = new MyClassVisitor(cw);
        cr.accept(cv,ClassReader.SKIP_DEBUG);
        byte[]data = cw.toByteArray();
        //输出
        File f = new File("out/production/JVMTest/com/yuy/jvm/CC.class");
        FileOutputStream fout = new FileOutputStream(f);
        fout.write(data);
        fout.close();
        System.out.println("now generator cc success");
    }

}

8.执行Genrator中的main方法,改写CC.calss

9.执行test测试

package com.yuy.jvm.asm;

import com.yuy.jvm.CC;

public class MyTest {
    public static void main(String[] args) throws InterruptedException {
        CC cc = new CC();
        cc.m();
    }
}

10.说明:a.修改字节码之前执行Mytest控制台输出。

                    now in CC.m

                b.执行Generator .main方法修改class文件之后执行Mytest控制台输出

                    now in CC.m
                   方法耗时为:101

                    CC.java 里面是没有方法耗时的代码 的,但是CC.class 经过重新Generator重新改造之后将统计耗时方法的字节码                        代 码添加到CC.class文件中,所以会有统计方法耗时的输入会打印出来。

                c.为了验证是统计所有方法的执行耗时。所以在CC.java中复制方法m 改名a b执行调a方法或b方法均有耗时打印

说明是支持的CC类的所有方法的耗时统计。

package com.yuy.jvm;

public class CC {
    public void m() throws InterruptedException {

        System.out.println("now in CC.m");

        Thread.sleep(100L);

    }
    public void a() throws InterruptedException {

        System.out.println("now in CC.m");

        Thread.sleep(100L);

    }
    public void b() throws InterruptedException {

        System.out.println("now in CC.m");

        Thread.sleep(100L);

    }
}

 

 

  

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值