ASM 通过树这种数据结构来表示复杂的字节码结构,并利用 Push 模型来对树进行遍历,在遍历过程中对字节码进行修改。所谓的 Push 模型类似于简单的 Visitor 设计模式,因为需要处理字节码结构是固定的,所以不需要专门抽象出一种 Vistable 接口,而只需要提供 Visitor 接口。所谓 Visitor 模式和 Iterator 模式有点类似,它们都被用来遍历一些复杂的数据结构。Visitor 相当于用户派出的代表,深入到算法内部,由算法安排访问行程。Visitor 代表可以更换,但对算法流程无法干涉,因此是被动的,这也是它和 Iterator 模式由用户主动调遣算法方式的最大的区别。
在 ASM 中,提供了一个
Cla***eader 类,这个类可以直接由字节数组或由 class 文件间接的获得字节码数据,它能正确的分析字节码,构建出抽象的树在内存中表示字节码。它会调用 accept 方法,这个方法接受一个实现了 ClassVisitor 接口的对象实例作为参数,然后依次调用 ClassVisitor 接口的各个方法。字节码空间上的偏移被转换成 visit 事件时间上调用的先后,所谓 visit 事件是指对各种不同 visit 函数的调用,Cla***eader 知道如何调用各种 visit 函数。在这个过程中用户无法对操作进行干涉,所以遍历的算法是确定的,用户可以做的是提供不同的 Visitor 来对字节码树进行不同的修改。ClassVisitor 会产生一些子过程,比如 visitMethod 会返回一个实现 MethordVisitor 接口的实例,visitField 会返回一个实现 FieldVisitor 接口的实例,完成子过程后控制返回到父过程,继续访问下一节点。因此对于 Cla***eader 来说,其内部顺序访问是有一定要求的。实际上用户还可以不通过 Cla***eader 类,自行手工控制这个流程,只要按照一定的顺序,各个 visit 事件被先后正确的调用,最后就能生成可以被正确加载的字节码。当然获得更大灵活性的同时也加大了调整字节码的复杂度。各个
ClassVisitor 通过职责链 (Chain-of-responsibility) 模式,可以非常简单的封装对字节码的各种修改,而无须关注字节码的字节偏移,因为这些实现细节对于用户都被隐藏了,用户要做的只是覆写相应的 visit 函数。ClassAdaptor 类实现了 ClassVisitor 接口所定义的所有函数,当新建一个 ClassAdaptor 对象的时候,需要传入一个实现了 ClassVisitor 接口的对象,作为职责链中的下一个访问者 (Visitor),这些函数的默认实现就是简单的把调用委派给这个对象,然后依次传递下去形成职责链。当用户需要对字节码进行调整时,只需从 ClassAdaptor 类派生出一个子类,覆写需要修改的方法,完成相应功能后再把调用传递下去。这样,用户无需考虑字节偏移,就可以很方便的控制字节码。每个
ClassAdaptor 类的派生类可以仅封装单一功能,比如删除某函数、修改字段可见性等等,然后再加入到职责链中,这样耦合更小,重用的概率也更大,但代价是产生很多小对象,而且职责链的层次太长的话也会加大系统调用的开销,用户需要在低耦合和高效率之间作出权衡。用户可以通过控制职责链中 visit 事件的过程,对类文件进行如下操作:-
删除类的字段、方法、指令:只需在职责链传递过程中中断委派,不访问相应的 visit 方法即可,比如删除方法时只需直接返回
null,而不是返回由visitMethod方法返回的MethodVisitor对象。class DelLoginClassAdapter extends ClassAdapter { public DelLoginClassAdapter(ClassVisitor cv) { super(cv); } public MethodVisitor visitMethod(final int access, final String name, final String desc, final String signature, final String[] exceptions) { if (name.equals("login")) { return null; } return cv.visitMethod(access, name, desc, signature, exceptions); } }
-
修改类、字段、方法的名字或修饰符:在职责链传递过程中替换调用参数。
class AccessClassAdapter extends ClassAdapter { public AccessClassAdapter(ClassVisitor cv) { super(cv); } public FieldVisitor visitField(final int access, final String name, final String desc, final String signature, final Object value) { int privateAccess = Opcodes.ACC_PRIVATE; return cv.visitField(privateAccess, name, desc, signature, value); } }
-
增加新的类、方法、字段
ASM 的最终的目的是生成可以被正常装载的 class 文件,因此其框架结构为客户提供了一个生成字节码的工具类 ——
ClassWriter。它实现了 ClassVisitor 接口,而且含有一个 toByteArray() 函数,返回生成的字节码的字节流,将字节流写回文件即可生产调整后的 class 文件。一般它都作为职责链的终点,把所有 visit 事件的先后调用(时间上的先后),最终转换成字节码的位置的调整(空间上的前后),如下例:ClassWriter classWriter = new ClassWriter(ClassWriter.COMPUTE_MAXS);
ClassAdaptor delLoginClassAdaptor = new DelLoginClassAdapter(classWriter);
ClassAdaptor accessClassAdaptor = new AccessClassAdaptor(delLoginClassAdaptor);
Cla***eader cla***eader = new Cla***eader(strFileName);
cla***eader.accept(classAdapter, Cla***eader.SKIP_DEBUG);
|
综上所述,ASM 的时序图如下:
图 4. ASM – 时序图
|
|
|
我们还是用上面的例子,给
Account 类加上 security check 的功能。与 proxy 编程不同,ASM 不需要将 Account 声明成接口,Account 可以仍旧是一个实现类。ASM 将直接在 Account 类上动手术,给 Account 类的 operation 方法首部加上对 SecurityChecker.checkSecurity 的调用。首先,我们将从
ClassAdapter 继承一个类。ClassAdapter 是 ASM 框架提供的一个默认类,负责沟通 Cla***eader 和 ClassWriter。如果想要改变 Cla***eader 处读入的类,然后从 ClassWriter 处输出,可以重写相应的 ClassAdapter 函数。这里,为了改变 Account 类的 operation 方法,我们将重写 visitMethdod 方法。class AddSecurityCheckClassAdapter extends ClassAdapter{
public AddSecurityCheckClassAdapter(ClassVisitor cv) {
//Responsechain 的下一个 ClassVisitor,这里我们将传入 ClassWriter,
//负责改写后代码的输出
super(cv);
}
//重写 visitMethod,访问到 "operation" 方法时,
//给出自定义 MethodVisitor,实际改写方法内容
public MethodVisitor visitMethod(final int access, final String name,
final String desc, final String signature, final String[] exceptions) {
MethodVisitor mv = cv.visitMethod(access, name, desc, signature,exceptions);
|-------10--------20--------30--------40--------50--------60--------70--------80--------9|
|-------- XML error: The previous line is longer than the max of 90 characters ---------|
MethodVisitor wrappedMv = mv;
if (mv != null) {
//对于 "operation" 方法
if (name.equals("operation")) {
//使用自定义 MethodVisitor,实际改写方法内容
wrappedMv = new AddSecurityCheckMethodAdapter(mv);
}
}
return wrappedMv;
}
}
|
下一步就是定义一个继承自
MethodAdapter 的 AddSecurityCheckMethodAdapter,在“operation”方法首部插入对 SecurityChecker.checkSecurity() 的调用。class AddSecurityCheckMethodAdapter extends MethodAdapter {
public AddSecurityCheckMethodAdapter(MethodVisitor mv) {
super(mv);
}
public void visitCode() {
visitMethodInsn(Opcodes.INVOKESTATIC, "SecurityChecker",
"checkSecurity", "()V");
}
}
|
其中,
Cla***eader 读到每个方法的首部时调用 visitCode(),在这个重写方法里,我们用visitMethodInsn(Opcodes.INVOKESTATIC, "SecurityChecker","checkSecurity", "()V"); 插入了安全检查功能。最后,我们将集成上面定义的
ClassAdapter,Cla***eader和ClassWriter 产生修改后的 Account 类文件:import java.io.File;
import java.io.FileOutputStream;
import org.objectweb.asm.*;
public class Generator{
public static void main() throws Exception {
Cla***eader cr = new Cla***eader("Account");
ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS);
ClassAdapter classAdapter = new AddSecurityCheckClassAdapter(cw);
cr.accept(classAdapter, Cla***eader.SKIP_DEBUG);
byte[] data = cw.toByteArray();
File file = new File("Account.class");
FileOutputStream fout = new FileOutputStream(file);
fout.write(data);
fout.close();
}
}
|
执行完这段程序后,我们会得到一个新的 Account.class 文件,如果我们使用下面代码:
public class Main {
public static void main(String[] args) {
Account account = new Account();
account.operation();
}
}
|
使用这个 Account,我们会得到下面的输出:
SecurityChecker.checkSecurity ... operation... |
也就是说,在
Account 原来的 operation 内容执行之前,进行了 SecurityChecker.checkSecurity() 检查。上面给出的例子是直接改造
Account 类本身的,从此 Account 类的 operation 方法必须进行 checkSecurity 检查。但事实上,我们有时仍希望保留原来的 Account 类,因此把生成类定义为原始类的子类是更符合 AOP 原则的做法。下面介绍如何将改造后的类定义为 Account 的子类 Account$EnhancedByASM。其中主要有两项工作:- 改变 Class Description, 将其命名为
Account$EnhancedByASM,将其父类指定为Account。 - 改变构造函数,将其中对父类构造函数的调用转换为对
Account构造函数的调用。
在
AddSecurityCheckClassAdapter 类中,将重写 visit 方法:public void visit(final int version, final int access, final String name,
final String signature, final String superName,
final String[] interfaces) {
String enhancedName = name + "$EnhancedByASM"; //改变类命名
enhancedSuperName = name; //改变父类,这里是”Account”
super.visit(version, access, enhancedName, signature,
enhancedSuperName, interfaces);
}
|
改进
visitMethod 方法,增加对构造函数的处理:public MethodVisitor visitMethod(final int access, final String name,
final String desc, final String signature, final String[] exceptions) {
MethodVisitor mv = cv.visitMethod(access, name, desc, signature, exceptions);
MethodVisitor wrappedMv = mv;
if (mv != null) {
if (name.equals("operation")) {
wrappedMv = new AddSecurityCheckMethodAdapter(mv);
} else if (name.equals("<init>")) {
wrappedMv = new ChangeToChildConstructorMethodAdapter(mv,
enhancedSuperName);
}
}
return wrappedMv;
}
|
这里
ChangeToChildConstructorMethodAdapter 将负责把 Account 的构造函数改造成其子类 Account$EnhancedByASM 的构造函数:class ChangeToChildConstructorMethodAdapter extends MethodAdapter {
private String superClassName;
public ChangeToChildConstructorMethodAdapter(MethodVisitor mv,
String superClassName) {
super(mv);
this.superClassName = superClassName;
}
public void visitMethodInsn(int opcode, String owner, String name,
String desc) {
//调用父类的构造函数时
if (opcode == Opcodes.INVOKESPECIAL && name.equals("<init>")) {
owner = superClassName;
}
super.visitMethodInsn(opcode, owner, name, desc);//改写父类为superClassName
}
}
|
最后演示一下如何在运行时产生并装入产生的
Account$EnhancedByASM。 我们定义一个 Util 类,作为一个类工厂负责产生有安全检查的 Account 类:public class SecureAccountGenerator {
private static AccountGeneratorClassLoader classLoader =
new AccountGeneratorClassLoade();
private static Class secureAccountClass;
public Account generateSecureAccount() throws ClassFormatError,
InstantiationException, IllegalAccessException {
if (null == secureAccountClass) {
Cla***eader cr = new Cla***eader("Account");
ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS);
ClassAdapter classAdapter = new AddSecurityCheckClassAdapter(cw);
cr.accept(classAdapter, Cla***eader.SKIP_DEBUG);
byte[] data = cw.toByteArray();
secureAccountClass = classLoader.defineClassFromClassFile(
"Account$EnhancedByASM",data);
}
return (Account) secureAccountClass.newInstance();
}
private static class AccountGeneratorClassLoader extends ClassLoader {
public Class defineClassFromClassFile(String className,
byte[] classFile) throws ClassFormatError {
return defineClass("Account$EnhancedByASM", classFile, 0, classFile.length());
|-------10--------20--------30--------40--------50--------60--------70--------80--------9|
|-------- XML error: The previous line is longer than the max of 90 characters ---------|
}
}
}
|
静态方法
SecureAccountGenerator.generateSecureAccount() 在运行时动态生成一个加上了安全检查的 Account 子类。著名的 Hibernate 和 Spring 框架,就是使用这种技术实现了 AOP 的“无损注入”。|
|
|
最后,我们比较一下 ASM 和其他实现 AOP 的底层技术:
表 1. AOP 底层技术比较
| AOP 底层技术 | 功能 | 性能 | 面向接口编程 | 编程难度 |
|---|---|---|---|---|
| 直接改写 class 文件 | 完全控制类 | 无明显性能代价 | 不要求 | 高,要求对 class 文件结构和 Java 字节码有深刻了解 |
| JDK Instrument | 完全控制类 | 无论是否改写,每个类装入时都要执行hook程序 | 不要求 | 高,要求对 class 文件结构和 Java 字节码有深刻了解 |
| JDK Proxy | 只能改写 method | 反射引入性能代价 | 要求 | 低 |
| ASM | 几乎能完全控制类 | 无明显性能代价 | 不要求 | 中,能操纵需要改写部分的 Java 字节码 |
转载于:https://blog.51cto.com/77857/149645