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了

源码地址

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

相关推荐
无我Code1 分钟前
前端-2025年末个人总结
前端·年终总结
文刀竹肃18 分钟前
DVWA -SQL Injection-通关教程-完结
前端·数据库·sql·安全·网络安全·oracle
LYFlied23 分钟前
【每日算法】LeetCode 84. 柱状图中最大的矩形
前端·算法·leetcode·面试·职场和发展
Bigger25 分钟前
Tauri(21)——窗口缩放后的”失焦惊魂”,游戏控制权丢失了
前端·macos·app
Bigger44 分钟前
Tauri (20)——为什么 NSPanel 窗口不能用官方 API 全屏?
前端·macos·app
bug总结1 小时前
前端开发中为什么要使用 URL().origin 提取接口根地址
开发语言·前端·javascript·vue.js·html
一招定胜负2 小时前
网络爬虫(第三部)
前端·javascript·爬虫
Shaneyxs2 小时前
从 0 到 1 实现CloudBase云开发 + 低代码全栈开发活动管理小程序(13)
前端
半山烟雨半山青2 小时前
微信内容emoji表情包编辑器 + vue3 + ts + WrchatEmogi Editor
前端·javascript·vue.js
码途潇潇2 小时前
Vue 事件机制全面解析:原生事件、自定义事件与 DOM 冒泡完全讲透
前端·javascript