我们还是用上面的例子,给
Account
类加上 security check 的功能。与 proxy 编程不同,ASM 不需要将
Account
声明成接口,
Account
可以仍旧是一个实现类。ASM 将直接在
Account
类上动手术,给
Account
类的
operation
方法首部加上对
SecurityChecker.checkSecurity
的调用。
首先,我们将从
ClassAdapter
继承一个类。
ClassAdapter
是 ASM 框架提供的一个默认类,负责沟通
ClassReader
和
ClassWriter
。如果想要改变
ClassReader
处读入的类,然后从
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); 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"); } } |
其中,
ClassReader
读到每个方法的首部时调用
visitCode()
,在这个重写方法里,我们用
visitMethodInsn(Opcodes.INVOKESTATIC, "SecurityChecker","checkSecurity", "()V");
插入了安全检查功能。
最后,我们将集成上面定义的
ClassAdapter
,
ClassReader
和
ClassWriter
产生修改后的
Account
类文件:
import java.io.File; import java.io.FileOutputStream; import org.objectweb.asm.*; public class Generator{ public static void main() throws Exception { ClassReader cr = new ClassReader("Account"); ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS); ClassAdapter classAdapter = new AddSecurityCheckClassAdapter(cw); cr.accept(classAdapter, ClassReader.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) { ClassReader cr = new ClassReader("Account"); ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS); ClassAdapter classAdapter = new AddSecurityCheckClassAdapter(cw); cr.accept(classAdapter, ClassReader.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()); } } } |
静态方法
SecureAccountGenerator.generateSecureAccount()
在运行时动态生成一个加上了安全检查的
Account
子类。著名的 Hibernate 和 Spring 框架,就是使用这种技术实现了 AOP 的“无损注入”。
最后,我们比较一下 ASM 和其他实现 AOP 的底层技术:
表 1. AOP 底层技术比较
AOP 底层技术 | 功能 | 性能 | 面向接口编程 | 编程难度 |
---|---|---|---|---|
直接改写 class 文件 | 完全控制类 | 无明显性能代价 | 不要求 | 高,要求对 class 文件结构和 Java 字节码有深刻了解 |
JDK Instrument | 完全控制类 | 无论是否改写,每个类装入时都要执行hook程序 | 不要求 | 高,要求对 class 文件结构和 Java 字节码有深刻了解 |
JDK Proxy | 只能改写 method | 反射引入性能代价 | 要求 | 低 |
ASM | 几乎能完全控制类 | 无明显性能代价 | 不要求 | 中,能操纵需要改写部分的 Java 字节码 |