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.jar
、asm-util.jar
和asm-commons.jar
; - 其中,Tree API包括
asm-tree.jar
和asm-analysis.jar
。
从两者的关系来说,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.jar
、asm-util.jar
和asm-commons.jar
文件里包含的主要类(.class
)成员进行介绍。
asm.jar
在asm.jar
文件中,一共包含了30多个类,我们会介绍其中10个类。那么,剩下的20多个类,为什么不介绍呢?因为剩下的20多个主要起到“辅助”的作用,它们更多的倾向于是“幕后工作者”;而“登上舞台表演的”则是属于那10个类。
这三个类的作用,可以简单理解成这样:
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
文件的内部信息。
在asm-util.jar
当中,主要介绍CheckClassAdapter
类和TraceClassVisitor
类,也会简略的说明一下Printer
、ASMifier
和Textifier
类。
asm-commons.jar
asm-commons.jar
主要包含的是一些常用功能类。
在下图当中,可以看到asm-commons.jar
里面包含的具体类文件。
我们会介绍到其中的AdviceAdapter
、AnalyzerAdapter
、ClassRemapper
、GeneratorAdapter
、InstructionAdapter
、LocalVariableSorter
、SerialVersionUIDAdapter
和StaticInitMerger
类。
在“第四章”当中,介绍asm-commons.jar
里的内容。
另外,一个非常容易混淆的问题就是,asm-util.jar与asm-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}
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);
}
}