[20][05][01] ASM介绍

1. ASM 是什么

ASM 是一个操作 Java 字节码的类库

  • ASM 所操作的对象是字节码 (ByteCode) 数据
  • ASM 处理字节码 (ByteCode) 的方式是"拆分-修改-合并"
  1. 将.class 文件拆分成多个部分
  2. 对某一个部分的信息进行修改
  3. 将多个部分重新组织成一个新的.class 文件

xx

2. ASM 能够做什么

父类: 修改成一个新的父类
接口: 添加一个新的接口, 删除已有的接口
字段: 添加一个新的字段, 删除已有的字段
方法: 添加一个新的方法, 删除已有的方法, 修改已有的方法

public class HelloWorld extends Object implements Cloneable {
    public int intValue;
    public String strValue;

    public int add(int a, int b) {
        return a + b;
    }

    public int sub(int a, int b) {
        return a - b;
    }

    @Override
    public Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
}

xx

3. 为什么要学习 ASM

ASM 就是一处位于“Java 语言的世界”边界上的一扇大门, 通过这扇大门, 我们可以前往“字节码的世界”, 在“字节码的世界”里, 我们会看到不一样的“风景”, 能够解决不一样的“问题”

xx

ASM 往往在一些框架的底层起着重要的作用, 介绍两个关于 ASM 的应用场景

  • Spring
  • JDK

3.1 Spring 当中的 ASM

Spring 框架的 AOP 是依赖于 ASM 的. 具体来说, Spring 的 AOP, 可以通过 JDK 的动态代理来实现, 也可以通过 CGLIB 实现. 其中, CGLib (Code Generation Library) 是在 ASM 的基础上构建起来的, 所以, Spring AOP 是间接的使用了 ASM

3.2 JDK 当中的 ASM

JDK 当中的 Lambda 表达式, 允许把方法作为参数进行传递, 它能够使代码变的更加简洁紧凑, Lambda 表达式的调用是通过 ASM 来实现的

在 rt.jar 文件的 jdk.internal.org.objectweb.asm 包当中, 就包含了 JDK 内置的 ASM 代码. 在 JDK 8 版本当中, 它所使用的 ASM 5.0 版本

跟踪 Lambda 表达式的编码实现, 就会找到 InnerClassLambdaMetafactory.spinInnerClass() 方法. 在这个方法当中, 我们就会看到: JDK 会使用 jdk.internal.org.objectweb.asm.ClassWriter 来生成一个类, 将 lambda 表达式的代码包装起来

// 第一步, 找到这个方法
LambdaMetafactory.metafactory()
        // 第二步, 找到这个方法
---> InnerClassLambdaMetafactory.buildCallSite()
            // 第三步, 找到这个方法
        ---> InnerClassLambdaMetafactory.spinInnerClass()

4. ASM 的两个组成部分

从组成结构上来说, ASM 分成两部分

  • Core API 包括 asm.jar, asm-util.jar 和 asm-commons.jar
  • Tree API 包括 asm-tree.jar 和 asm-analysis.jar

xx

从两者的关系来说, Core API 是基础, 而 Tree API 是在 Core API 的这个基础上构建起来的

4.1 Core API

ASM Core API 包括 asm.jar, asm-util.jar 和 asm-commons.jar

4.1.1 asm.jar

在 asm.jar 文件中, 一共包含了 30 多个类, 重点其中 10 个类, 剩下的 20 多个类是辅助的作用

从无到有生成一个新的类, 其中会涉及到 ClassVisitor, ClassWriter, FieldVisitor, FieldWriter, MethodVisitor, MethodWriter, Label 和 Opcodes 类

修改已有的类, 使之内容发生改变, 其中会涉及到 Cla***eader 和 Type 类

在这 10 个类当中, 最重要的是三个类 Cla***eader, ClassVisitor 和 ClassWriter 类, 这三个类的关系如下

xx

这三个类的作用, 可以简单理解成这样

  • Cla***eader 类, 负责读取.class 文件里的内容, 然后拆分成各个不同的部分
  • ClassVisitor 类, 负责对.class 文件中某一部分里的信息进行修改
  • ClassWriter 类, 负责将各个不同的部分重新组合成一个完整的.class 文件

4.1.2 asm-util.jar

asm-util.jar 主要包含的是一些工具类, 在下图当中可以看到 asm-util.jar 里面包含的具体类文件, 这些类主要分成两种类型

  • 以 Check 开头的类, 主要负责检查 (Check) 生成的.class 文件内容是否正确
  • 以 Trace 开头的类, 主要负责将.class 文件的内容打印成文字输出, 根据输出的文字信息, 可以探索或追踪 (Trace) .class 文件的内部信息

xx

4.1.3 asm-commons.jar

asm-commons.jar 主要包含的是一些常用功能类, 在下图当中, 可以看到 asm-commons.jar 里面包含的具体类文件

xx

asm-util.jar 与 asm-commons.jar 有什么区别呢?

  • 在 asm-util.jar 里, 它提供的是通用性的功能, 没有特别明确的应用场景
  • 在 asm-commons.jar 里, 它提供的功能, 都是为解决某一种特定场景中出现的问题而提出的解决思路

5. 搭建 ASM 开发环境

5.1 pom 文件信息

修改 pom.xml
新建一个 maven 项目, 取名为 asm-maven, 修改其中的 pom.xml 文件, 添加 ASM 的 Jar 包依赖. 打开 pom.xml 文件, 并添加如下内容

<properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <java.version>1.8</java.version>
    <maven.compiler.source>${java.version}</maven.compiler.source>
    <maven.compiler.target>${java.version}</maven.compiler.target>
    <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>

<build>
    <plugins>
        <!-- Java Compiler -->
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-compiler-plugin</artifactId>
            <version>3.8.1</version>
            <configuration>
                <source>${java.version}</source>
                <target>${java.version}</target>
                <fork>true</fork>
                <compilerArgs>
                    <arg>-g</arg>
                    <arg>-parameters</arg>
                </compilerArgs>
            </configuration>
        </plugin>
    </plugins>
</build>

5.2 使用 ASM

对 ASM 的使用有一个初步的认识, 为了验证 ASM 的开发环境是能够正常使用的

5.2.1 预期目标

我们的预期目标是, 生成一个 HelloWorld 类, 它对应的 Java 代码如下

public class HelloWorld {
    @Override
    public String toString() {
        return "This is a HelloWorld object.";
    }
}

注意, 我们不需要去写这样一个 sample/HelloWorld.java 文件, 只是生成的 HelloWorld 类和这里的 Java 代码是一样的效果

5.2.2 编码实现

public class HelloWorldDump implements Opcodes {

    public static byte[] dump() {
        ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES);

        cw.visit(V1_8, ACC_PUBLIC | ACC_SUPER, "sample/HelloWorld", 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();
        }
        {
            MethodVisitor mv2 = cw.visitMethod(ACC_PUBLIC, "toString", "()Ljava/lang/String;", null, null);
            mv2.visitCode();
            mv2.visitLdcInsn("This is a HelloWorld object.");
            mv2.visitInsn(ARETURN);
            mv2.visitMaxs(1, 1);
            mv2.visitEnd();
        }
        cw.visitEnd();

        return cw.toByteArray();
    }
}

5.2.3 验证结果

public class MyClassLoader extends ClassLoader {
    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        if ("sample.HelloWorld".equals(name)) {
            byte[] bytes = HelloWorldDump.dump();
            Class<?> clazz = defineClass(name, bytes, 0, bytes.length);
            return clazz;
        }

        throw new ClassNotFoundException("Class Not Found: " + name);
    }
}

public class HelloWorldRun {
    public static void main(String[] args) throws Exception {
        MyClassLoader classLoader = new MyClassLoader();
        Class<?> clazz = classLoader.loadClass("sample.HelloWorld");
        Object instance = clazz.newInstance();
        System.out.println(instance);
    }
}

运行之后的输出结果 This is a HelloWorld object

  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值