ASM字节码操作库---入门环境搭建

概述

ASM是一个操作字节码的类库,提到字节码,很多人想到的首先是JAVA字节码。其实Kotlin,Groovy等语言也会生成字节码,并且和Java的字节码一样,都能被虚拟机识别执行,这就意味着ASM也可以用在这些语言上,比如Android现在的主流开发语言Kotlin。就拿JAVA语言来说,我们都知道,我们编写代码的那个文件称为.java文件,经过Java编译器(javac), .java文件会被编译成.class文件,在.class文件中就存储着字节码(bytecode)数据,ASM可以对字节码数据做修改,然后生成一份新的字节码文件,而且ASM还可以在什么都没有的情况下,直接生成一个.class文件。本文的demo就是演示这个场景,除此之外,ASM还能做分析操作,分析操作是指它可以对一份字节码做分析统计的操作,但是不生成新的字节码文件,也就是说它只是单纯分析,不做任何修改。本文使用JAVA语言作为ASM的演示demo。

环境配置

当我们想要去了解一个语言或者是开源库的时候,第一件事都是将他们需要的环境配置好,然后跑起来看下效果。ASM的环境配置比较简单,直接引入相关的库就可以了,ASM在Android中一般是集成到gradle脚本中去使用,本文为了演示,直接使用 IDEA集成开发环境+maven的方式去做演示,后面集成到Android中的时候其实方法也是一样的,引入对应的依赖就可以了。

我们在IDEA集成开发环境中新建一个maven项目,如下图所示,注意红圈中的选择。 建完以后,项目的结构如下所示: 然后我们找到pom.xml文件,导入asm的依赖就可以了,如下:

cpp 复制代码
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 
         http:// maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>org.example</groupId>
    <artifactId>AsmDemo</artifactId>
    <version>1.0-SNAPSHOT</version>

    <properties>
        <maven.compiler.source>11</maven.compiler.source>
        <maven.compiler.target>11</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</
        project.build.sourceEncoding>
        <asm.version>9.0</asm.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.ow2.asm</groupId>
            <artifactId>asm</artifactId>
            <version>${asm.version}</version>
        </dependency>
        <dependency>
            <groupId>org.ow2.asm</groupId>
            <artifactId>asm-commons</artifactId>
            <version>${asm.version}</version>
        </dependency>
        <dependency>
            <groupId>org.ow2.asm</groupId>
            <artifactId>asm-util</artifactId>
            <version>${asm.version}</version>
        </dependency>
        <dependency>
            <groupId>org.ow2.asm</groupId>
            <artifactId>asm-tree</artifactId>
            <version>${asm.version}</version>
        </dependency>
        <dependency>
            <groupId>org.ow2.asm</groupId>
            <artifactId>asm-analysis</artifactId>
            <version>${asm.version}</version>
        </dependency>
    </dependencies>
</project>

demo尝鲜

demo会生成一个新的类,并且调用类中的toString方法打印一段日志,为了演示,定义一个ClassLoader类加载器去加载Asm生成的类。

生成的类对应的.java文件如下:

java 复制代码
public class HelloWorldAsm {
    @Override
    public String toString() {
        return "this is a HelloAsm class obj";
    }
}

上面的类我们不用自己写,而是用Asm去生成上面的类对应的字节码文件,然后通过类加载器去加载执行我们生成的类。生成类的代码如下:

cpp 复制代码
public class HelloWordAsm implements Opcodes {
    public static byte[] genHelloWorldClass(){
        ClassWriter cw = 
        new ClassWriter(ClassWriter.COMPUTE_FRAMES);
        cw.visit(V1_8,ACC_PUBLIC|ACC_SUPER,"demo/HelloWorldAsm",
                null,
                "java/lang/Object",
                null);

        {
            // 添加无参构造方法
            MethodVisitor mv1 = cw.visitMethod(
                    ACC_PUBLIC,
                    "<init>",
                    "()V",
                    null,
                    null
            );
            mv1.visitCode();
            mv1.visitVarInsn(ALOAD,0);
            // 调用父类的构造方法
            mv1.visitMethodInsn(
                    INVOKESPECIAL,
                    "java/lang/Object",
                    "<init>",
                    "()V",
                    false
            );
            mv1.visitInsn(RETURN);
            mv1.visitMaxs(1,1);
            mv1.visitEnd();
        }

        {
            // 添加一个toString 方法,返回String
            MethodVisitor mv2 = cw.visitMethod(
                    ACC_PUBLIC,
                    "toString",
                    "()Ljava/lang/String;",
                    null,
                    null
            );

            mv2.visitCode();
            mv2.visitLdcInsn("this is a HelloAsm class obj");
            mv2.visitInsn(ARETURN);
            mv2.visitMaxs(1,1);
            mv2.visitEnd();
        }
        cw.visitEnd();

        return cw.toByteArray();
    }
}

上面的代码生成一个类后,我们编写一个类加载器去加载我们生成的类

cpp 复制代码
public class MyClassLoader extends ClassLoader{
    @Override
    protected Class<?> findClass(String name) throws
     ClassNotFoundException {
        System.out.println("MyClassLoader: findClass: name==>" 
        + name);
        if("demo.HelloWorldAsm".equals(name)){
            byte [] bytes = HelloWordAsm.genHelloWorldClass();
            Class<?> aClass = defineClass(name, bytes, 0,
             bytes.length);
            return aClass;
        }
        return super.findClass(name);
    }
}

最后我们编写测试代码验证下生成类的正确性:

cpp 复制代码
public class TestHelloAsm {
    public static void main(String[] args) {
        MyClassLoader myClassLoader = new MyClassLoader();
        try {
            Class<?> aClass = 
            myClassLoader.loadClass("demo.HelloWorldAsm");
            Object instance = aClass.newInstance();
            System.out.println("instance: " + instance);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
}

如上面代码所示,我们使用类加载器去加载我们生成的类,然后创建了一个对象,当我们打印这个对象的时候,就会调用这个对象的toString方法,打印出我们的生成类中的toString方法返回的字符串信息。运行结果如下所示: 运行出上面的结果就证明环境OK了

源码地址

为了让读者能更快的看到效果,在此提供上源码,后续有对应的文章也会同步到这个源码上。源码地址

相关推荐
头顶一只喵喵几秒前
Vue基础知识:Vue3.3出现的defineOptions,如何使用,解决了什么问题?
前端·javascript·vue.js·vue3
黑色的糖果32 分钟前
echarts横向立体3D柱状图
前端·javascript·echarts
茶卡盐佑星_38 分钟前
vue3.0所采用的composition Api与vue2.x使用的Option Api有什么区别
前端·javascript·vue.js
乐安lan42 分钟前
01前端导入
前端
lauo1 小时前
【WEB前端2024】3D智体编程:乔布斯3D纪念馆-第55课-芝麻开门(语音 识别 控制3D纪念馆开门 和 关门)
前端·javascript·人工智能·3d·机器人·开源·语音识别
2401_857622661 小时前
探索 WebKit 的动感世界:设备方向和运动支持全解析
前端·webkit
开源博客1 小时前
Vue3 如何接入 i18n 实现国际化多语言
前端·vue·i18n·vite
Ann_R1 小时前
el-date-picker 开始时间选定后,结束时间不可选择开始时间之前的日期
前端·vue.js·elementui
濮水大叔1 小时前
2024已过半,还没试过在vue3中使用ioc容器吗?
前端·typescript·vue3·ioc·tsx
幸运小男神1 小时前
elementUI中table组件固定列时会渲染两次模板内容问题
前端·javascript·elementui