关键词:ASM 字节码
在《学习动态代理前先看看-class字节码》中提到,我们是直接可以面向.class文件进行编码的,但是编写一个字节码内容是十分复杂的,好在有框架提供了功能。比如:ASM。ASM是一个通用的Java字节码操作和分析框架。它可用于修改现有的类或直接以二进制形式动态生成类。(关于ASM详细内容,请戳ASM官网)
ASM提供了两种API用来生成或者修改已经编译好的类,core API 和 tree API。其中core API是基于事件的(event based),tree API是基于对象的(object based )。
在基于事件的模型中,一个类用一个事件序列所表示,序列中的一个事件表示类的一个元素,比如头信息,一个属性、一个方法声明以及指令等。在基于对象的模型中,一个类用一个对象树表示,每个对象表示类的一部分,比如类本身,一个属性,一个方法等。比较两种模型来说,基于事件的模型速度更快,占用内存更少,但是使用起来比较困难。而基于对象的模型使用起来相对简单一些。
█core API
主要是三个类:ClassVisitor、ClassReader、ClassWriter。
ClassVisitor:抽象类,ClassVisitor类将它接收到的所有方法调用委托给另一个ClassVisitor实例。它可以被看作是一个事件过滤器。
ClassReader:负责解析一个已经编译好的类。可以看做是一个事件生成者。其中的accept方法接收一个ClassVisitor,用于调用ClassVisitor的方法。
ClassWriter:继承了ClassVisitor,可以直接以二进制形式构建已编译的类。可以看做是一个事件消费者。通过方法toByteArray将编译好的类输出为字节数组。
ClassReader结合ClassVisitor的使用:
①引入Maven依赖
<dependency>
<groupId>org.ow2.asm</groupId>
<artifactId>asm</artifactId>
<version>9.0</version>
</dependency>
<dependency>
<groupId>org.ow2.asm</groupId>
<artifactId>asm-util</artifactId>
<version>9.0</version>
</dependency>
②编写一个类继承ClassVisitor,并重写所有方法:
import org.objectweb.asm.*;
public class MyClassVisitor extends ClassVisitor {
public MyClassVisitor() {
super(589824);
}
@Override
public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
System.out.println("visit version="+version+" access="+access+" name="+name+" signature="+signature+" superName"+superName+" interfaces="+interfaces);
}
@Override
public void visitSource(String source, String debug) {
System.out.println("visitSource source="+source+" debug="+debug);
}
@Override
public ModuleVisitor visitModule(String name, int access, String version) {
System.out.println("visitModule name="+name+" access="+access+" version="+version);
return null;
}
@Override
public void visitNestHost(String nestHost) {
System.out.println("visitNestHost nestHost="+nestHost);
}
@Override
public void visitOuterClass(String owner, String name, String descriptor) {
System.out.println("visitOuterClass owner="+owner+" name="+name+" descriptor="+descriptor);
}
@Override
public AnnotationVisitor visitAnnotation(String descriptor, boolean visible) {
System.out.println("visitAnnotation descriptor="+descriptor+" visible="+visible);
return null;
}
@Override
public AnnotationVisitor visitTypeAnnotation(int typeRef, TypePath typePath, String descriptor, boolean visible) {
System.out.println("visitTypeAnnotation typeRef="+typeRef+" descriptor="+descriptor+" visible="+visible);
return null;
}
@Override
public void visitAttribute(Attribute attribute) {
System.out.println("visitAttribute "+attribute.type);
}
@Override
public void visitNestMember(String nestMember) {
System.out.println("visitNestMember "+nestMember);
}
@Override
public void visitPermittedSubclass(String permittedSubclass) {
System.out.println("visitPermittedSubclass "+permittedSubclass);
}
@Override
public void visitInnerClass(String name, String outerName, String innerName, int access) {
System.out.println("visitInnerClass name="+name+" outerName="+outerName+" innerName="+innerName+" access="+access);
}
@Override
public RecordComponentVisitor visitRecordComponent(String name, String descriptor, String signature) {
System.out.println("visitRecordComponent name="+name+" descriptor="+descriptor+" signature="+signature);
return null;
}
@Override
public FieldVisitor visitField(int access, String name, String descriptor, String signature, Object value) {
System.out.println("visitField access="+access+" name="+name+" descriptor="+descriptor+" signature="+signature+" value="+value);
return null;
}
@Override
public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) {
System.out.println("visitMethod access="+access+" name="+name+" descriptor="+descriptor+" signature="+signature+" exceptions="+exceptions);
return null;
}
@Override
public void visitEnd() {
System.out.println("visitEnd");
}
}
③编写两个类,Dog和Animal
package read;
public class Dog extends Animal{
private String name;
public void say() {
System.out.println("a dog");
}
public String getName() {
return name;
}
private void setName() {
name = "大黄";
}
}
package read;
public class Animal {
}
④运行代码
public static void main(String[] args) {
try {
MyClassVisitor myClassVisitor = new MyClassVisitor();
// 此处传参ArrayList的全限定名,构造方法内部会调用ClassLoader.getSystemResourceAsStream加载类信息
ClassReader classReader = new ClassReader("read.Dog");
classReader.accept(myClassVisitor, 0);
} catch (Exception e) {
e.printStackTrace();
}
}
⑤查看控制台输出结果
visit version=52 access=33 name=read/Dog signature=null superNameread/Animal interfaces=[Ljava.lang.String;@61baa894
visitSource source=Dog.java debug=null
visitField access=2 name=name descriptor=Ljava/lang/String; signature=null value=null
visitMethod access=1 name=<init> descriptor=()V signature=null exceptions=null
visitMethod access=1 name=say descriptor=()V signature=null exceptions=null
visitMethod access=1 name=getName descriptor=()Ljava/lang/String; signature=null exceptions=null
visitMethod access=2 name=setName descriptor=()V signature=null exceptions=null
visitEnd
从输出的内容可以发现,字节码也是有语法的,在java源码的语法与字节码的语法有一套对应的关系。比如:void 对应 ()V,Ljava/lang/String;对应String。
ClassWriter结合ClassVisitor的使用:
①通过ClassWriter生成一个上面的Dog类。可结合上面的控制台输出的内容传入相关参数:
public static void main(String[] args) throws Exception{
ClassWriter classWriter = new ClassWriter(0);
// 写 public class read.Dog extends read.Animal
classWriter.visit(52,33, "read/Dog", null, "read/Animal", null);
// 写属性 name
classWriter.visitField(2, "name", "Ljava/lang/String", null, null);
// 依次写方法
// 构造方法
classWriter.visitMethod(1, "<init>", "()V", null ,null);
// say方法
classWriter.visitMethod(1, "say", "()V", null ,null);
// getName方法
classWriter.visitMethod(1, "getName", "()Ljava/lang/String;", null ,null);
// setName方法
classWriter.visitMethod(2, "setName", "()V", null ,null);
classWriter.visitEnd();
// 获取字节码
byte[] bytes = classWriter.toByteArray();
// 将内容写到本地文件中
FileOutputStream fos = new FileOutputStream("D:\\Dog.class");
fos.write(bytes);
}
②用IDEA打开Dog.class
package read;
import Ljava.lang.String;
public class Dog extends Animal {
private String name;
public Dog() {
}
public void say() {
}
public java.lang.String getName() {
}
private void setName() {
}
}
Dog类的内容写的有问题,由于没有深入学习core api的使用,暂时只能到这个程度了。从使用的过程发现,确实难用。
█ tree API
tree API的核心是ClassNode,对应Class。ClassNode继承了ClassVisitor,可以使用ClassVisitor中的方法和功能。有ClassNode,自然也会有FieldNode对应类中的属性,MethodNode对应类中的方法等等。使用tree API编程更有面向对象的感觉。