ASM:(2)组成部分


原文:https://lsieun.github.io/java-asm-01/asm-components.html

ASM的组成部分

本文的整体逻辑:ASM由两个组成部分(Core API和Tree API),每个组成部分由多个.jar文件组成,每个.jar文件里包含多个具体的类(.class)文件。

ASM的两个组成部分

从组成结构上来说,ASM分成两部分,一部分为Core API,另一部分为Tree API。

  • 其中,Core API包括asm.jarasm-util.jarasm-commons.jar
  • 其中,Tree API包括asm-tree.jarasm-analysis.jar

image-20220407143634627

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

从ASM API演进的历史来讲,先有Core API,后有Tree API。最初,在2002年,Eric Bruneton等发表了一篇文章,即《ASM: a code manipulation tool to implement adaptable systems》。在这篇文章当中,最早提出了ASM的设计思路。当时,ASM只包含13个类文件,Jar包的大小只有21KB。这13个类文件,就是现在所说的Core API的雏形,但当时并没有提出Core API这样的概念。随着时代的变化,人们对于修改Java字节码提出更多的需求。为了满足人们的需求,ASM就需要添加新的类。类的数量变多了,代码的管理也就变得困难起来。为了更好的管理ASM的代码,就将这些类(按照功能的不同)分配到不同的Jar包当中,这样就逐渐衍生出Core API和Tree API的概念。

Core API概览

ASM Core API概览,就是对asm.jarasm-util.jarasm-commons.jar文件里包含的主要类(.class)成员进行介绍。

asm.jar

asm.jar文件中,一共包含了30多个类,我们会介绍其中10个类。那么,剩下的20多个类,为什么不介绍呢?因为剩下的20多个主要起到“辅助”的作用,它们更多的倾向于是“幕后工作者”;而“登上舞台表演的”则是属于那10个类。

image-20220407144020636

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

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

asm-util.jar

asm-util.jar主要包含的是一些工具类

在下图当中,可以看到asm-util.jar里面包含的具体类文件。这些类主要分成两种类型:Check开头和Trace开头。

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

image-20220407144140361

asm-util.jar当中,主要介绍CheckClassAdapter类和TraceClassVisitor类,也会简略的说明一下PrinterASMifierTextifier类。

asm-commons.jar

asm-commons.jar主要包含的是一些常用功能类

在下图当中,可以看到asm-commons.jar里面包含的具体类文件。

image-20220407144810807

我们会介绍到其中的AdviceAdapterAnalyzerAdapterClassRemapperGeneratorAdapterInstructionAdapterLocalVariableSorterSerialVersionUIDAdapterStaticInitMerger类。

在“第四章”当中,介绍asm-commons.jar里的内容。

另外,一个非常容易混淆的问题就是,asm-util.jarasm-commons.jar有什么区别呢?

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

asm开发环境

pom.xml:

    
        UTF-8
        1.8
        ${java.version}
        ${java.version}
        9.0
    

    
        
            org.ow2.asm
            asm
            ${asm.version}
        
        
            org.ow2.asm
            asm-commons
            ${asm.version}
        
        
            org.ow2.asm
            asm-util
            ${asm.version}
        
        
            org.ow2.asm
            asm-tree
            ${asm.version}
        
        
            org.ow2.asm
            asm-analysis
            ${asm.version}
        
        
            commons-io
            commons-io
            2.11.0
        
    

    
        
            
            
                org.apache.maven.plugins
                maven-compiler-plugin
                3.8.1
                
                    ${java.version}
                    ${java.version}
                    true
                    
                        -g
                        -parameters
                    
                
            
        
    

使用asm

我们的预期目标:生成一个HelloWorld类。

package sample;

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

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

编码实现:

package com.example;

import org.objectweb.asm.*;

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, "", "()V", null, null);
            mv1.visitCode();
            mv1.visitVarInsn(ALOAD, 0);
            mv1.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "", "()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();
    }
}

验证结果

package com.example;

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);
    }
}
package com.example;

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);
    }
}

image-20220407150318324

asm