ASM是一款十分优秀的字节码处理框架,在很多地方都有用到,像fastJson序列化、反序列化时,针对用户定义的class类型去生成新的类。像在Spring的aop的动态代理中,用到了ASM做类的代理。有了asm,可以帮助我们解决很多问题,确实是值得深入了解的一个优秀武器。
ASM的设计模式
在了解ASM之前,先来说一下ASM的整体设计模式-访问者模式(visitor)。
这种模式用的比较少,但是思路上并不复杂。简单来说,就是定义若干元素类和若干访问者类,在访问者类定义了改变了元素类的执行算法。这也就意味着元素的内部方法会随着访问者改变而改变。上个例子:
//访问者的接口,这里定义了对两种元素的访问方法
//也可以用抽象类,在子类中重写方法
public interface Visitor {
void visit(MyElement1 element);
void visit(MyElement2 element);
}
//第一个visitor类,MyVisitor1。定义了对两种元素的执行方法。
public class MyVisitor1 implements Visitor {
@Override
public void visit(MyElement1 element) {
log.info("this is MyVisitor1, visit MyElement1");
}
@Override
public void visit(MyElement2 element) {
log.info("this is MyVisitor1, visit MyElement2");
}
}
//第二个visitor类,MyVisitor2。定义了对两种元素的执行方法。
public class MyVisitor2 implements Visitor {
@Override
public void visit(MyElement1 element) {
log.info("this is MyVisitor2, visit MyElement1");
}
@Override
public void visit(MyElement2 element) {
log.info("this is MyVisitor2, visit MyElement2");
}
}
然后定义元素类:
//元素接口,定义了一个接收方法。
public interface Element {
void accept(Visitor visitor);
}
//第一个元素类MyElement1,定义一个accept方法
public class MyElement1 implements Element {
public void accept(Visitor visitor) {
log.info(visitor.getClass().getCanonicalName()+"访问MyElement1");
visitor.visit(this);
}
}
//第二个元素类MyElement2,定义一个accept方法
public class MyElement2 implements Element {
public void accept(Visitor visitor) {
log.info(visitor.getClass().getCanonicalName()+"访问MyElement2");
visitor.visit(this);
}
}
调用可以有很多方式,简单的比如直接生成对象,然后调用accept方法:
Visitor visitor = new MyVisitor2();
Element element = new MyElement1();
element.accpet(visitor);
总体来说,访问者模式的优缺点都很明显:
(1)解耦元素和执行者,自定义访问者实现对元素的动态执行。
(2)但是出现新的元素时,所有的访问者类都需要增加相应的访问方法。
所以访问者模式并不适合元素类频繁变动的场景,对于大多数的业务代码场景来说,不变的元素类是很少的,在具体使用过程中要评估这种场景。但是ASM的元素类是class字节码类,这个是不变的,所以天然适合这种设计模式。
ASM的逻辑
铺垫完之后,以ClassReader为例,来看看ASM的执行逻辑:
ClassReader和ClassWriter一起可以实现对class文件的字节码做自定义的操作之后,再动态生成一个新的class文件。
先看ClassReader类中的accept方法:(参考下面的Class文件格式)
public void accept(final ClassVisitor classVisitor,
final Attribute[] attrs, final int flags) {
//u代表当前class文件的读取位置,head代表access_flags的位置。此时把header赋值给u,代表从头开始读class文件
int u = header; // current offset in the class file
//定义一个char数组用来读取字符串
char[] c = new char[maxStringLength]; // buffer used to read strings
//定义一个上下文
Context context = new Context();
context.attrs = attrs;
context.flags = flags;
context.buffer = c;
// reads the class declaration
int access = readUnsignedShort(u);
//access_flags是两个字节,name是读取的类名称
String name = readClass(u + 2, c);
//superClass是读取的父类名称
String superClass = readClass(u + 4, c);
//读取interfaces集合,指针u一致往后移动
String[] interfaces = new String[readUnsignedShort(u + 6)];
u += 8;
for (int i = 0; i < interfaces.length; ++i) {
interfaces[i] = readClass(u, c);
u += 2;
}
// reads the class attributes
String signature = null;
String sourceFile = null;
String sourceDebug = null;
String enclosingOwner = null;
String enclosingName = null;
String enclosingDesc = null;
String moduleMainClass = null;
int anns = 0;
int ianns = 0;
int tanns = 0;
int itanns = 0;
int innerClasses = 0;
int module = 0;
int packages = 0;
Attribute attributes = null;
//得到这个类的属性信息结构的起始索引
u = getAttributes();
for (int i = readUnsignedShort(u); i > 0; --i) {
//遍历每一个属性,执行赋值或其他操作。
String attrName = readUTF8(u + 2, c);
// tests are sorted in decreasing frequency order
// (based on frequencies observed on typical classes)
if ("SourceFile".equals(attrName)) {
sourceFile = readUTF8(u + 8, c);
}
//省略。。。
else {
Attribute attr = readAttribute(attrs, attrName, u + 8,
readInt(u + 4), c, -1, null);
if (attr != null) {
attr.next = attributes;
attributes = attr;
}
}
u += 6 + readInt(u + 4);
}
//访问者模式,开始调用访问者的不同访问方法,将属性全部转发出去
// visits the class declaration
//访问:类的申明
//items中存放的常量池每项的开始索引加1,加1是跳过常量池项标记,截取方便一点。
classVisitor.visit(readInt(items[1] - 7), access, name, signature,
superClass, interfaces);
// visits the source and debug info
//读取资源信息
if ((flags & SKIP_DEBUG) == 0
&& (sourceFile != null || sourceDebug != null)) {
classVisitor.visitSource(sourceFile, sourceDebug);
}
// visits the module info and associated attributes
if (module != 0) {
readModule(classVisitor, context, module,
moduleMainClass, packages);
}
// visits the outer class
if (enclosingOwner != null) {
classVisitor.visitOuterClass(enclosingOwner, enclosingName,
enclosingDesc);
}
// visits the class annotations and type annotations
if (ANNOTATIONS && anns != 0) {
for (int i = readUnsignedShort(anns), v = anns + 2; i > 0; --i) {
v = readAnnotationValues(v + 2, c, true,
classVisitor.visitAnnotation(readUTF8(v, c), true));
}
}
if (ANNOTATIONS && ianns != 0) {
for (int i = readUnsignedShort(ianns), v = ianns + 2; i > 0; --i) {
v = readAnnotationValues(v + 2, c, true,
classVisitor.visitAnnotation(readUTF8(v, c), false));
}
}
if (ANNOTATIONS && tanns != 0) {
for (int i = readUnsignedShort(tanns), v = tanns + 2; i > 0; --i) {
v = readAnnotationTarget(context, v);
v = readAnnotationValues(v + 2, c, true,
classVisitor.visitTypeAnnotation(context.typeRef,
context.typePath, readUTF8(v, c), true));
}
}
if (ANNOTATIONS && itanns != 0) {
for (int i = readUnsignedShort(itanns), v = itanns + 2; i > 0; --i) {
v = readAnnotationTarget(context, v);
v = readAnnotationValues(v + 2, c, true,
classVisitor.visitTypeAnnotation(context.typeRef,
context.typePath, readUTF8(v, c), false));
}
}
// visits the attributes
while (attributes != null) {
Attribute attr = attributes.next;
attributes.next = null;
classVisitor.visitAttribute(attributes);
attributes = attr;
}
// visits the inner classes
if (innerClasses != 0) {
int v = innerClasses + 2;
for (int i = readUnsignedShort(innerClasses); i > 0; --i) {
classVisitor.visitInnerClass(readClass(v, c),
readClass(v + 2, c), readUTF8(v + 4, c),
readUnsignedShort(v + 6));
v += 8;
}
}
// visits the fields and methods
u = header + 10 + 2 * interfaces.length;
for (int i = readUnsignedShort(u - 2); i > 0; --i) {
u = readField(classVisitor, context, u);
}
u += 2;
for (int i = readUnsignedShort(u - 2); i > 0; --i) {
u = readMethod(classVisitor, context, u);
}
// visits the end of the class
classVisitor.visitEnd();
}
总的来说,在accept方法中,就是从access_flag后开始读取,按照class属性的顺序一个个解析出来。 在后面再转发到visitor的方法里面。
提下Class文件结构:
回忆一下,Class文件是一组8位字节为基础单位的二进制流,各个数据项严格按照顺序紧凑的排列,中间是没有添加任何分隔符。
public class ClassFile {
//魔数,确定这个文件是否为一个能被虚拟机接收,为固定值。
public final int magic;
//次版本号
public final int minor_version;
//主版本号
public final int major_version;
//常量池
public final ConstantPool constant_pool;
//访问标志
public final AccessFlags access_flags;
//类索引
public final int this_class;
//父类索引
public final int super_class;
//接口索引集合
public final int[] interfaces;
//字段表集合
public final Field[] fields;
//方法表集合
public final Method[] methods;
//属性表集合
public final Attributes attributes;
。。。
}
再看下观察者模式的”访问者“,ClassWriter:
//这里的访问者没有定义接口,而是继承父类的方式,这样不用实现所有方法。
public class ClassWriter extends ClassVisitor {
。。。
//针对Class文件各个属性,不同的visit方法。
//默认的visit方法,把元素类中的属性做赋值操作,全部放倒自己的字段里面。
@Override
public final void visit(final int version, final int access,
final String name, final String signature, final String superName,
final String[] interfaces) {
this.version = version;
this.access = access;
this.name = newClass(name);
thisName = name;
if (ClassReader.SIGNATURES && signature != null) {
this.signature = newUTF8(signature);
}
this.superName = superName == null ? 0 : newClass(superName);
if (interfaces != null && interfaces.length > 0) {
interfaceCount = interfaces.length;
this.interfaces = new int[interfaceCount];
for (int i = 0; i < interfaceCount; ++i) {
this.interfaces[i] = newClass(interfaces[i]);
}
}
}
@Override
public final void visitSource(final String file, final String debug) {
if (file != null) {
sourceFile = newUTF8(file);
}
if (debug != null) {
sourceDebug = new ByteVector().encodeUTF8(debug, 0,
Integer.MAX_VALUE);
}
}
。。。
}
可以看出来,ClassWriter这个”访问者“,实际上做的事就是持有元素类ClassReader中分析出来的Class文件中的各个属性。那么我们就可以仿照着这种方式对class文件做我们想做的事了,比如说加个字段,改变方法等等,感觉还是很激动的有没有,可以直接改字节码了。
总结:
(1)ASM是一款十分优秀的字节码处理框架,可以对Class文件做我们想要的改动。
(2)ASM天然的适合访问者模式,因为Class文件的格式是固定的,各个数据项严格按照顺序紧凑的排列。所以不用担心扩展的问题。
(3)ClassReader做为“元素类”,ClassWriter做为”访问者“,在ClassReader的accept方法中,就是从access_flag后开始读取,按照class属性的顺序一个个解析出来。 在后面再转发到ClassWriter的不同visit方法里面。
(4)灵活的运用ASM,可以解决很多问题。另外,基于此库可以做很多有意思的事。