AOP 的利器:ASM 3.0 介绍(5)

我们还是用上面的例子,给 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 字节码
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值