我们还是用上面的例子,给
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 字节码 |
本文介绍如何使用ASM3.0框架实现在Account类的operation方法前加入securitycheck功能,包括通过ClassAdapter和MethodAdapter动态修改字节码的过程。此外,还讨论了如何将动态生成的类作为原始类的子类。
636

被折叠的 条评论
为什么被折叠?



