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了

源码地址

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

相关推荐
霍先生的虚拟宇宙网络10 分钟前
webp 网页如何录屏?
开发语言·前端·javascript
jessezappy30 分钟前
jQuery-Word-Export 使用记录及完整修正文件下载 jquery.wordexport.js
前端·word·jquery·filesaver·word-export
旧林8431 小时前
第八章 利用CSS制作导航菜单
前端·css
yngsqq1 小时前
c#使用高版本8.0步骤
java·前端·c#
Myli_ing2 小时前
考研倒计时-配色+1
前端·javascript·考研
余道各努力,千里自同风2 小时前
前端 vue 如何区分开发环境
前端·javascript·vue.js
软件小伟2 小时前
Vue3+element-plus 实现中英文切换(Vue-i18n组件的使用)
前端·javascript·vue.js
醉の虾2 小时前
Vue3 使用v-for 渲染列表数据后更新
前端·javascript·vue.js
张小小大智慧2 小时前
TypeScript 的发展与基本语法
前端·javascript·typescript
hummhumm3 小时前
第 22 章 - Go语言 测试与基准测试
java·大数据·开发语言·前端·python·golang·log4j