`
julyboxer
  • 浏览: 223983 次
  • 性别: Icon_minigender_1
  • 来自: 上海
社区版块
存档分类
最新评论

AOP 的利器:ASM 3.0 介绍

    博客分类:
  • java
阅读更多

2007 年 7 月 25 日

随着 AOP(Aspect Oriented Programming)的发展,代码动态生成已然成为 Java 世界中不可或缺的一环。本文将介绍一种小巧轻便的 Java 字节码操控框架 ASM,它能方便地生成和改造 Java 代码。著名的框架,如 Hibernate 和 Spring 在底层都用到了 ASM。比起传统的 Java 字节码操控框架,BCEL 或者 SERP,它具有更符合现代软件模式的编程模型和更迅捷的性能。

本 文主要分为四个部分:首先将 ASM 和其他 Java 类生成方案作对比,然后大致介绍 Java 类文件的组织,最后针对最新的 ASM 3.0,描述其编程框架,并给出一个使用 ASM 进行 AOP 的例子,介绍调整函数内容,生成派生类,以及静态和动态生成类的方法。

<!----><!----> <!---->

引言

什么是 ASM?

ASM 是一个 Java 字节码操控框架。它能被用来动态生成类或者增强既有类的功能。ASM 可以直接产生二进制 class 文件,也可以在类被加载入 Java 虚拟机之前动态改变类行为。Java class 被存储在严格格式定义的 .class 文件里,这些类文件拥有足够的元数据来解析类中的所有元素:类名称、方法、属性以及 Java 字节码(指令)。ASM 从类文件中读入信息后,能够改变类行为,分析类信息,甚至能够根据用户要求生成新类。

与 BCEL 和 SERL 不同,ASM 提供了更为现代的编程模型。对于 ASM 来说,Java class 被描述为一棵树;使用 “Visitor” 模式遍历整个二进制结构;事件驱动的处理方式使得用户只需要关注于对其编程有意义的部分,而不必了解 Java 类文件格式的所有细节:ASM 框架提供了默认的 “response taker”处理这一切。

为什么要动态生成 Java 类?

动 态生成 Java 类与 AOP 密切相关的。AOP 的初衷在于软件设计世界中存在这么一类代码,零散而又耦合:零散是由于一些公有的功能(诸如著名的 log 例子)分散在所有模块之中;同时改变 log 功能又会影响到所有的模块。出现这样的缺陷,很大程度上是由于传统的 面向对象编程注重以继承关系为代表的“纵向”关系,而对于拥有相同功能或者说方面 (Aspect)的模块之间的“横向”关系不能很好地表达。例如,目前有一个既有的银行管理系统,包括 Bank、Customer、Account、Invoice 等对象,现在要加入一个安全检查模块, 对已有类的所有操作之前都必须进行一次安全检查。


图 1. ASM – AOP
图 1. ASM – AOP

然 而 Bank、Customer、Account、Invoice 是代表不同的事务,派生自不同的父类,很难在高层上加入关于 Security Checker 的共有功能。对于没有多继承的 Java 来说,更是如此。传统的解决方案是使用 Decorator 模式,它可以在一定程度上改善耦合,而功能仍旧是分散的 —— 每个需要 Security Checker 的类都必须要派生一个 Decorator,每个需要 Security Checker 的方法都要被包装(wrap)。下面我们以 Account 类为例看一下 Decorator:

首先,我们有一个 SecurityChecker 类,其静态方法 checkSecurity 执行安全检查功能:

public class SecurityChecker {
	public static void checkSecurity() {
		System.out.println("SecurityChecker.checkSecurity ...");
		//TODO real security check
	}	
}
     

 

另一个是 Account 类:

public class Account {
	public void operation() {
		System.out.println("operation...");
		//TODO real operation
	}
}
     

 

若想对 operation 加入对 SecurityCheck.checkSecurity() 调用,标准的 Decorator 需要先定义一个 Account 类的接口:

public interface Account {
	void operation(); 
}
     

 

然后把原来的 Account 类定义为一个实现类:

public class AccountImpl extends Account{
	public void operation() {
		System.out.println("operation...");
		//TODO real operation
	}
} 
     

 

定义一个 Account 类的 Decorator,并包装 operation 方法:

public class AccountWithSecurityCheck implements Account {	
	private  Account account;
	public AccountWithSecurityCheck (Account account) {
		this.account = account;
	}
	public void operation() {
		SecurityChecker.checkSecurity();
		account.operation();
	}
}
     

 

在 这个简单的例子里,改造一个类的一个方法还好,如果是变动整个模块,Decorator 很快就会演化成另一个噩梦。动态改变 Java 类就是要解决 AOP 的问题,提供一种得到系统支持的可编程的方法,自动化地生成或者增强 Java 代码。这种技术已经广泛应用于最新的 Java 框架内,如 Hibernate,Spring 等。

为什么选择 ASM?

最 直接的改造 Java 类的方法莫过于直接改写 class 文件。Java 规范详细说明了class 文件的格式,直接编辑字节码确实可以改变 Java 类的行为。直到今天,还有一些 Java 高手们使用最原始的工具,如 UltraEdit 这样的编辑器对 class 文件动手术。是的,这是最直接的方法,但是要求使用者对 Java class 文件的格式了熟于心:小心地推算出想改造的函数相对文件首部的偏移量,同时重新计算 class 文件的校验码以通过 Java 虚拟机的安全机制。

Java 5 中提供的 Instrument 包也可以提供类似的功能:启动时往 Java 虚拟机中挂上一个用户定义的 hook 程序,可以在装入特定类的时候改变特定类的字节码,从而改变该类的行为。但是其缺点也是明显的:

  • Instrument 包是在整个虚拟机上挂了一个钩子程序,每次装入一个新类的时候,都必须执行一遍这段程序,即使这个类不需要改变。
  • 直接改变字节码事实上类似于直接改写 class 文件,无论是调用 ClassFileTransformer. transform(ClassLoader loader, String className, Class classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) ,还是 Instrument.redefineClasses(ClassDefinition[] definitions) ,都必须提供新 Java 类的字节码。也就是说,同直接改写 class 文件一样,使用 Instrument 也必须了解想改造的方法相对类首部的偏移量,才能在适当的位置上插入新的代码。

尽管 Instrument 可以改造类,但事实上,Instrument 更适用于监控和控制虚拟机的行为。

一种比较理想且流行的方法是使用 java.lang.ref.proxy 。我们仍旧使用上面的例子,给 Account 类加上 checkSecurity 功能:

首先,Proxy 编程是面向接口的。下面我们会看到,Proxy 并不负责实例化对象,和 Decorator 模式一样,要把 Account 定义成一个接口,然后在 AccountImpl 里实现 Account 接口,接着实现一个 InvocationHandler Account 方法被调用的时候,虚拟机都会实际调用这个 InvocationHandlerinvoke 方法:

class SecurityProxyInvocationHandler implements InvocationHandler {
	private Object proxyedObject;
	public SecurityProxyInvocationHandler(Object o) {
		proxyedObject = o;
	}
		
	public Object invoke(Object object, Method method, Object[] arguments)
		throws Throwable {			
		if (object instanceof Account && method.getName().equals("opertaion")) {
			SecurityChecker.checkSecurity();
		}
		return method.invoke(proxyedObject, arguments);
	}
}	
	

 

最后,在应用程序中指定 InvocationHandler 生成代理对象:

public static void main(String[] args) {
	Account account = (Account) Proxy.newProxyInstance(
		Account.class.getClassLoader(),
		new Class[] { Account.class },
		new SecurityProxyInvocationHandler(new AccountImpl())
	);
	account.function();
}
	

 

其不足之处在于:

  • Proxy 是面向接口的,所有使用 Proxy 的对象都必须定义一个接口,而且用这些对象的代码也必须是对接口编程的:Proxy 生成的对象是接口一致的而不是对象一致的:例子中 Proxy.newProxyInstance 生成的是实现 Account 接口的对象而不是 AccountImpl 的子类。这对于软件架构设计,尤其对于既有软件系统是有一定掣肘的。
  • Proxy 毕竟是通过反射实现的,必须在效率上付出代价:有实验数据表明,调用反射比一般的函数开销至少要大 10 倍。而且,从程序实现上可以看出,对 proxy class 的所有方法调用都要通过使用反射的 invoke 方法。因此,对于性能关键的应用,使用 proxy class 是需要精心考虑的,以避免反射成为整个应用的瓶颈。

ASM 能够通过改造既有类,直接生成需要的代码。增强的代码是硬编码在新生成的类文件内部的,没有反射带来性能上的付出。同时,ASM 与 Proxy 编程不同,不需要为增强代码而新定义一个接口,生成的代码可以覆盖原来的类,或者是原始类的子类。它是一个普通的 Java 类而不是 proxy 类,甚至可以在应用程序的类框架中拥有自己的位置,派生自己的子类。

相比于其他流行的 Java 字节码操纵工具,ASM 更小更快。ASM 具有类似于 BCEL 或者 SERP 的功能,而只有 33k 大小,而后者分别有 350k 和 150k。同时,同样类转换的负载,如果 ASM 是 60% 的话,BCEL 需要 700%,而 SERP 需要 1100% 或者更多。

ASM 已经被广泛应用于一系列 Java 项目:AspectWerkz、AspectJ、BEA WebLogic、IBM AUS、OracleBerkleyDB、Oracle TopLink、Terracotta、RIFE、EclipseME、Proactive、Speedo、Fractal、EasyBeans、 BeanShell、Groovy、Jamaica、CGLIB、dynaop、Cobertura、JDBCPersistence、JiP、 SonarJ、Substance L&F、Retrotranslator 等。Hibernate 和 Spring 也通过 cglib,另一个更高层一些的自动代码生成工具使用了 ASM。

 




回页首


Java 类文件概述

所 谓 Java 类文件,就是通常用 javac 编译器产生的 .class 文件。这些文件具有严格定义的格式。为了更好的理解 ASM,首先对 Java 类文件格式作一点简单的介绍。Java 源文件经过 javac 编译器编译之后,将会生成对应的二进制文件(如下图所示)。每个合法的 Java 类文件都具备精确的定义,而正是这种精确的定义,才使得 Java 虚拟机得以正确读取和解释所有的 Java 类文件。


图 2. ASM – Javac 流程
图 2. ASM – Javac 流程

Java 类文件是 8 位字节的二进制流。数据项按顺序存储在 class 文件中,相邻的项之间没有间隔,这使得 class 文件变得紧凑,减少存储空间。在 Java 类文件中包含了许多大小不同的项,由于每一项的结构都有严格规定,这使得 class 文件能够从头到尾被顺利地解析。下面让我们来看一下 Java 类文件的内部结构,以便对此有个大致的认识。

例如,一个最简单的 Hello World 程序:

public class HelloWorld {
	public static void main(String[] args) {
		System.out.println("Hello world");
	}
}

 

经过 javac 编译后,得到的类文件大致是:


图 3. ASM – Java 类文件
图 3. ASM – Java 类文件

从上图中可以看到,一个 Java 类文件大致可以归为 10 个项:

  • Magic: 该 项存放了一个 Java 类文件的魔数(magic number)和版本信息。一个 Java 类文件的前 4 个字节被称为它的魔数。每个正确的 Java 类文件都是以 0xCAFEBABE 开头的,这样保证了 Java 虚拟机能很轻松的分辨出 Java 文件和非 Java 文件。
  • Version: 该项存放了 Java 类文件的版本信息,它对于一个 Java 文件具有重要的意义。因为 Java 技术一直在发展,所以类文件的格式也处在不断变化之中。类文件的版本信息让虚拟机知道如何去读取并处理该类文件。
  • Constant Pool: 该 项存放了类中各种文字字符串、类名、方法名和接口名称、final 变量以及对外部类的引用信息等常量。虚拟机必须为每一个被装载的类维护一个常量池,常量池中存储了相应类型所用到的所有类型、字段和方法的符号引用,因此 它在 Java 的动态链接中起到了核心的作用。常量池的大小平均占到了整个类大小的 60% 左右。
  • Access_flag: 该项指明了该文件中定义的是类还是接口(一个 class 文件中只能有一个类或接口),同时还指名了类或接口的访问标志,如 public,private, abstract 等信息。
  • This Class: 指向表示该类全限定名称的字符串常量的指针。
  • Super Class: 指向表示父类全限定名称的字符串常量的指针。
  • Interfaces: 一个指针数组,存放了该类或父类实现的所有接口名称的字符串常量的指针。以上三项所指向的常量,特别是前两项,在我们用 ASM 从已有类派生新类时一般需要修改:将类名称改为子类名称;将父类改为派生前的类名称;如果有必要,增加新的实现接口。
  • Fields: 该项对类或接口中声明的字段进行了细致的描述。需要注意的是,fields 列表中仅列出了本类或接口中的字段,并不包括从超类和父接口继承而来的字段。
  • Methods: 该 项对类或接口中声明的方法进行了细致的描述。例如方法的名称、参数和返回值类型等。需要注意的是,methods 列表里仅存放了本类或本接口中的方法,并不包括从超类和父接口继承而来的方法。使用 ASM 进行 AOP 编程,通常是通过调整 Method 中的指令来实现的。
  • Class attributes: 该项存放了在该文件中类或接口所定义的属性的基本信息。

事 实上,使用 ASM 动态生成类,不需要像早年的 class hacker 一样,熟知 class 文件的每一段,以及它们的功能、长度、偏移量以及编码方式。ASM 会给我们照顾好这一切的,我们只要告诉 ASM 要改动什么就可以了 —— 当然,我们首先得知道要改什么:对类文件格式了解的越多,我们就能更好地使用 ASM 这个利器。

 




回页首


ASM 3.0 编程框架

ASM 通过树这种数据结构来表示复杂的字节码结构,并利用 Push 模型来对树进行遍历,在遍历过程中对字节码进行修改。所谓的 Push 模型类似于简单的 Visitor 设计模式,因为需要处理字节码结构是固定的,所以不需要专门抽象出一种 Vistable 接口,而只需要提供 Visitor 接口。所谓 Visitor 模式和 Iterator 模式有点类似,它们都被用来遍历一些复杂的数据结构。Visitor 相当于用户派出的代表,深入到算法内部,由算法安排访问行程。Visitor 代表可以更换,但对算法流程无法干涉,因此是被动的,这也是它和 Iterator 模式由用户主动调遣算法方式的最大的区别。

在 ASM 中,提供了一个 ClassReader 类,这个类可以直接由字节数组或由 class 文件间接的获得字节码数据,它能正确的分析字节码,构建出抽象的树在内存中表示字节码。它会调用 accept 方法,这个方法接受一个实现了 ClassVisitor 接口的对象实例作为参数,然后依次调用 ClassVisitor 接口的各个方法。字节码空间上的偏移被转换成 visit 事件时间上调用的先后,所谓 visit 事件是指对各种不同 visit 函数的调用,ClassReader 知道如何调用各种 visit 函数。在这个过程中用户无法对操作进行干涉,所以遍历的算法是确定的,用户可以做的是提供不同的 Visitor 来对字节码树进行不同的修改。ClassVisitor 会产生一些子过程,比如 visitMethod 会返回一个实现 MethordVisitor 接口的实例,visitField 会返回一个实现 FieldVisitor 接口的实例,完成子过程后控制返回到父过程,继续访问下一节点。因此对于 ClassReader 来说,其内部顺序访问是有一定要求的。实际上用户还可以不通过 ClassReader 类,自行手工控制这个流程,只要按照一定的顺序,各个 visit 事件被先后正确的调用,最后就能生成可以被正确加载的字节码。当然获得更大灵活性的同时也加大了调整字节码的复杂度。

各个 ClassVisitor 通过职责链 (Chain-of-responsibility) 模式,可以非常简单的封装对字节码的各种修改,而无须关注字节码的字节偏移,因为这些实现细节对于用户都被隐藏了,用户要做的只是覆写相应的 visit 函数。

ClassAdaptor 类实现了 ClassVisitor 接口所定义的所有函数,当新建一个 ClassAdaptor 对象的时候,需要传入一个实现了 ClassVisitor 接口的对象,作为职责链中的下一个访问者 (Visitor),这些函数的默认实现就是简单的把调用委派给这个对象,然后依次传递下去形成职责链。当用户需要对字节码进行调整时,只需从 ClassAdaptor 类派生出一个子类,覆写需要修改的方法,完成相应功能后再把调用传递下去。这样,用户无需考虑字节偏移,就可以很方便的控制字节码。

每个 ClassAdaptor 类的派生类可以仅封装单一功能,比如删除某函数、修改字段可见性等等,然后再加入到职责链中,这样耦合更小,重用的概率也更大,但代价是产生很多小对象, 而且职责链的层次太长的话也会加大系统调用的开销,用户需要在低耦合和高效率之间作出权衡。用户可以通过控制职责链中 visit 事件的过程,对类文件进行如下操作:

  1. 删除类的字段、方法、指令:只需在职责链传递过程中中断委派,不访问相应的 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);
    	}
    }
             

  2. 修改类、字段、方法的名字或修饰符:在职责链传递过程中替换调用参数。

    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);
        }
    }
             

  3. 增加新的类、方法、字段

ASM 的最终的目的是生成可以被正常装载的 class 文件,因此其框架结构为客户提供了一个生成字节码的工具类 —— ClassWriter 。它实现了 ClassVisitor 接口,而且含有一个 toByteArray() 函数,返回生成的字节码的字节流,将字节流写回文件即可生产调整后的 class 文件。一般它都作为职责链的终点,把所有 visit 事件的先后调用(时间上的先后),最终转换成字节码的位置的调整(空间上的前后),如下例:

ClassWriter  classWriter = new ClassWriter(ClassWriter.COMPUTE_MAXS);
ClassAdaptor delLoginClassAdaptor = new DelLoginClassAdapter(classWriter);
ClassAdaptor accessClassAdaptor = new AccessClassAdaptor(delLoginClassAdaptor);
	
ClassReader classReader = new ClassReader(strFileName);
classReader.accept(classAdapter, ClassReader.SKIP_DEBUG);
         

 

综上所述,ASM 的时序图如下:


图 4. ASM – 时序图
图 4. ASM – 时序图




回页首


使用 ASM3.0 进行 AOP 编程

我们还是用上面的例子,给 Account 类加上 security check 的功能。与 proxy 编程不同,ASM 不需要将 Account 声明成接口,Account 可以仍旧是一个实现类。ASM 将直接在 Account 类上动手术,给 Account 类的 operation 方法首部加上对 SecurityChecker.checkSecurity 的调用。

首先,我们将从 ClassAdapter 继承一个类。ClassAdapter 是 ASM 框架提供的一个默认类,负责沟通 ClassReaderClassWriter 。如果想要改变 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);
|-------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;
	}
}   

 

下一步就是定义一个继承自 MethodAdapterAddSecurityCheckMethodAdapter ,在“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"); 插入了安全检查功能。

最后,我们将集成上面定义的 ClassAdapterClassReaderClassWriter 产生修改后的 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 类本身的,从此 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());
|-------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 字节码


参考资料

分享到:
评论

相关推荐

    AOP 的利器 ASM 3.0

    ### AOP 的利器 ASM 3.0 #### 引言 随着面向切面编程(Aspect-Oriented Programming,简称AOP)技术的发展,代码的动态生成已成为Java领域中不可或缺的一部分。本文将详细介绍一种轻量级的Java字节码操控框架——...

    cglib-3.0.rar

    CGlib是基于ASM库来工作的,ASM是一个Java字节码操控和分析框架,它可以直接生成和解析.class文件,而CGlib则在这个基础上提供了一层更高级的API,使得开发者可以更容易地操作字节码,实现动态代理或AOP(面向切面...

    基于Python的天气预测与可视化(完整源码+说明文档+数据)

    基于Python的天气预测与可视化(完整源码+说明文档+数据),个人经导师指导并认可通过的高分设计项目,评审分99分,代码完整确保可以运行,小白也可以亲自搞定,主要针对计算机相关专业的正在做大作业的学生和需要项目实战练习的学习者,可作为毕业设计、课程设计、期末大作业,代码资料完整,下载可用。 基于Python的天气预测与可视化(完整源码+说明文档+数据)基于Python的天气预测与可视化(完整源码+说明文档+数据)基于Python的天气预测与可视化(完整源码+说明文档+数据)基于Python的天气预测与可视化(完整源码+说明文档+数据)基于Python的天气预测与可视化(完整源码+说明文档+数据)基于Python的天气预测与可视化(完整源码+说明文档+数据)基于Python的天气预测与可视化(完整源码+说明文档+数据)基于Python的天气预测与可视化(完整源码+说明文档+数据)基于Python的天气预测与可视化(完整源码+说明文档+数据)基于Python的天气预测与可视化(完整源码+说明文档+数据)基于Python的天气预测与可视化(完整源码+说明文档+数据)基于Python的天气预测与可视化(完整源码+说明文档+数据)基于Python的天气预测与可视化(完整源码+说明文档+数据)基于Python的天气预测与可视化(完整源码+说明文档+数据)基于Python的天气预测与可视化(完整源码+说明文档+数据)基于Python的天气预测与可视化(完整源码+说明文档+数据)基于Python的天气预测与可视化(完整源码+说明文档+数据)基于Python的天气预测与可视化(完整源码+说明文档+数据)基于Python的天气预测与可视化(完整源码+说明文档+数据)基于Python的天气预测与可视化(完整源码+说明文档+数据)基于Python的天气预测与可视化(完整源码+说明文档+数据)基

    超表面设计中MIM结构的FDTD仿真:基于磁偶极子共振的高效光束偏折实现

    内容概要:本文详细介绍了利用MIM(金属-介质-金属)结构进行梯度相位超表面的设计与仿真的全过程。首先,通过Au-MgF2-Au三明治结构,利用磁偶极子共振实现高效的相位控制。接着,通过FDTD仿真工具,编写参数扫描脚本来优化纳米柱尺寸,从而实现广泛的相位覆盖。然后,通过近远场变换计算异常反射效率,验证了高达85%以上的反射效率。此外,还探讨了宽带性能验证的方法以及梯度相位阵列的设计思路。最后,提供了实用的代码片段和注意事项,帮助读者理解和复现实验结果。 适合人群:从事超表面研究、光束控制、电磁仿真领域的科研人员和技术开发者。 使用场景及目标:适用于希望深入了解MIM结构在超表面设计中的应用,掌握FDTD仿真技巧,以及探索高效光束偏折机制的研究人员。目标是通过详细的步骤指导,使读者能够成功复现并优化类似实验。 其他说明:文章不仅提供了理论背景,还包括大量具体的代码实现和实践经验分享,有助于读者更好地理解和应用所学知识。

    基于主从博弈的MATLAB实现:共享储能与综合能源微网优化运行

    内容概要:本文探讨了利用主从博弈理论解决共享储能与综合能源微网之间的利益冲突。通过MATLAB和YALMIP+Cplex工具,构建了微网运营商、用户聚合商和共享储能服务商三者之间的博弈模型。主要内容包括系统架构介绍、核心代码解析、求解策略以及仿真结果分析。文中详细展示了如何通过Stackelberg模型实现三方利益的最大化,并提供了完整的代码实现和详细的注释。 适合人群:从事能源互联网项目的研发人员、对博弈论及其应用感兴趣的学者和技术爱好者。 使用场景及目标:适用于希望深入了解能源系统优化、主从博弈理论及其MATLAB实现的研究人员和工程师。目标是掌握如何通过编程手段解决复杂系统中的多主体利益协调问题。 其他说明:文章不仅介绍了理论背景,还提供了具体的代码实现细节,如参数初始化、目标函数构建、约束条件处理等。此外,还包括了仿真结果的可视化展示,帮助读者更好地理解模型的实际效果。

    FPGA图像处理领域的直方图统计与均衡化技术及其Matlab验证

    内容概要:本文深入探讨了基于FPGA平台实现直方图统计与均衡化的全过程,涵盖直方图统计、累积直方图计算和均衡化处理三大核心步骤。文中不仅提供了详细的Verilog代码实现,还介绍了关键的设计思路和技术难点,如双端口BRAM的应用、流水线控制、除法器资源优化等。此外,通过Matlab代码进行了结果验证,确保FPGA实现的准确性。 适合人群:从事FPGA开发、图像处理、计算机视觉等相关领域的工程师和技术爱好者。 使用场景及目标:适用于需要高性能、低延迟图像处理的应用场景,如实时视频处理、医学图像处理、卫星图像增强等。目标是掌握FPGA实现直方图均衡化的技术细节,提高图像对比度和清晰度。 其他说明:文章强调了FPGA相较于CPU和GPU在并行处理和硬件加速方面的优势,并提供了丰富的代码实例和测试结果,帮助读者更好地理解和应用这一技术。

    基于LSTM的高速公路车辆换道轨迹预测:数据处理、模型设计与性能评估

    内容概要:本文详细介绍了利用LSTM模型进行高速公路车辆换道轨迹预测的研究过程。首先,作者使用来自I-80和US-101高速公路的实际换道轨迹数据,这些数据包括横向和纵向的速度、加速度以及轨迹坐标等特征。通过对数据进行预处理,如标准化、划分训练集和测试集等步骤,确保了数据的质量。然后,设计并实现了包含两层LSTM和一层全连接层的神经网络模型,采用Adam优化器进行训练,并通过交叉熵损失函数评估模型性能。实验结果显示,模型在测试集上的准确率达到85%,表明LSTM模型能够有效捕捉车辆换道的行为模式。 适合人群:从事自动驾驶技术研发的专业人士,尤其是对深度学习应用于交通预测感兴趣的工程师和技术研究人员。 使用场景及目标:本研究旨在提高自动驾驶系统的安全性与效率,具体应用场景包括但不限于城市快速路、高速公路等复杂路况下车辆换道行为的提前预测,从而辅助驾驶员或自动驾驶系统做出更好的决策。 其他说明:尽管目前模型已经取得了较好的成绩,但仍存在改进空间,例如可以通过引入更多类型的传感器数据(如摄像头图像)、优化现有模型结构等方式进一步提升预测精度。此外,考虑到实际应用中的实时性和鲁棒性要求,后续还需针对硬件平台进行针对性优化。

    个人资料-1111相关内容

    个人资料-111相关内容

    汽车碰撞仿真CAE:基于HyperWorks与LS-DYNA的全流程解析及实战技巧

    内容概要:本文详细介绍了使用HyperWorks和LS-DYNA进行汽车碰撞仿真的方法和技术要点。从网格划分、材料属性设置、连接装配到最后的分析计算和结果处理,每个环节都配有具体的代码示例和注意事项。文中不仅涵盖了正碰、侧碰、偏置碰等多种类型的碰撞分析,还包括了座椅安全带约束等特殊部件的建模技巧。此外,作者分享了许多实践经验,如网格尺寸的选择、材料参数的设定以及求解器设置的最佳实践,帮助读者避免常见的陷阱并提高仿真效率。 适合人群:从事汽车工程领域的工程师、研究人员以及对汽车碰撞仿真感兴趣的初学者。 使用场景及目标:适用于需要掌握汽车碰撞仿真完整流程的专业人士,旨在提升其在实际项目中的应用能力,确保仿真结果的准确性和可靠性。 其他说明:附赠的源代码进一步增强了学习效果,使读者能够快速上手并在实践中不断优化自己的技能。

    MATLAB/Simulink中四分之一车被动悬架双质量模型的构建与分析

    内容概要:本文详细介绍了如何在MATLAB/Simulink环境中搭建四分之一车被动悬架双质量(二自由度)模型。该模型主要用于研究车辆悬架系统在垂直方向上的动态特性,特别是面对路面不平度时的表现。文中不仅提供了具体的建模步骤,包括输入模块、模型主体搭建和输出模块的设计,还给出了详细的参数配置方法和仿真分析技巧。此外,文章还探讨了如何通过调整悬架系统的参数(如阻尼系数)来优化车辆的乘坐舒适性和行驶安全性。 适合人群:从事汽车动力学研究的专业人士、高校相关专业的学生以及对车辆悬架系统感兴趣的工程师。 使用场景及目标:①用于教学目的,帮助学生理解车辆悬架系统的理论知识;②用于科研实验,验证不同的悬架设计方案;③为企业产品研发提供技术支持,改进现有产品的性能。 其他说明:文中提供的代码片段和建模思路有助于读者快速上手并掌握Simulink建模技能。同时,强调了实际应用中的注意事项,如选择合适的求解器、处理代数环等问题。

    MATLAB实现语音数据特征提取与分类全流程解析

    内容概要:本文详细介绍了使用MATLAB进行语音数据处理的完整流程,涵盖从音频文件读取、特征提取(特别是梅尔倒谱系数MFCC)、分类器构建(支持向量机SVM)到最后的性能评估(混淆矩阵)。作者分享了许多实用技巧,如避免常见错误、优化特征提取参数以及提高分类准确性的方法。文中提供了大量具体代码示例,帮助读者快速理解和应用相关技术。 适合人群:对语音信号处理感兴趣的初学者或有一定经验的研究人员和技术爱好者。 使用场景及目标:适用于希望深入了解语音识别系统内部机制的人群,尤其是希望通过MATLAB平台实现简单而有效的语音分类任务的学习者。主要目的是掌握如何利用MATLAB工具箱完成从原始音频到分类结果可视化的全过程。 其他说明:除了介绍基本概念外,还强调了一些实践经验,例如预处理步骤的重要性、选择合适的滤波器数目、尝试不同的分类器配置等。此外,作者鼓励读者根据实际情况调整参数设置,以获得更好的实验效果。

    基于python+yolov5和deepsort实现的行人或车辆跟踪计数系统+源码+项目文档+演示视频(毕业设计&课程设计&项目开发)

    基于python+yolov5和deepsort实现的行人或车辆跟踪计数系统+源码+项目文档+演示视频,适合毕业设计、课程设计、项目开发。项目源码已经过严格测试,可以放心参考并在此基础上延申使用,详情见md文档 项目运行环境:win10,pycharm,python3.6+ 主要需要的包:pytorch >= 1.7.0,opencv 运行main.py即可开始追踪检测,可以在控制台运行 基于python+yolov5和deepsort实现的行人或车辆跟踪计数系统+源码+项目文档+演示视频,适合毕业设计、课程设计、项目开发。项目源码已经过严格测试,可以放心参考并在此基础上延申使用,详情见md文档 项目运行环境:win10,pycharm,python3.6+ 主要需要的包:pytorch >= 1.7.0,opencv 运行main.py即可开始追踪检测,可以在控制台运行~

    超表面全息技术中MIM结构的高效几何相位与FDTD仿真解析

    内容概要:本文详细介绍了金-氟化镁-金(MIM)结构在超表面全息领域的应用及其高效性能。首先探讨了MIM结构中磁偶极子模式的优势,特别是其低辐射损耗的特点。接着讨论了几何相位的应用,展示了纳米柱旋转角度与相位延迟之间的线性关系,并解决了相位误差的问题。随后介绍了改进的GS算法,提高了迭代收敛速度。最后,通过FDTD仿真验证了MIM结构的高效率,提供了详细的仿真参数设置和优化技巧。 适合人群:从事超表面研究、光学工程、纳米技术和FDTD仿真的研究人员和技术人员。 使用场景及目标:适用于希望深入了解MIM结构在超表面全息中的应用,以及希望通过FDTD仿真进行相关研究的专业人士。目标是提高超表面全息的转换效率,探索新的应用场景如涡旋光生成和偏振加密全息。 其他说明:文中提供了大量具体的代码片段和参数设置,帮助读者更好地理解和复现实验结果。此外,还提到了一些常见的仿真陷阱和解决方案,有助于避免常见错误并提升仿真准确性。

    【金融科技领域】信用飞利用大数据与AI实现用户信用成长及资产增值:个性化金融解决方案设计

    内容概要:文章介绍了金融科技公司信用飞如何通过关注用户信用成长,利用先进技术和专业服务为用户量身定制金融解决方案,从而实现用户资产的稳健增值。首先,信用飞通过多维度数据分析,全面了解用户的信用状况和需求,为不同信用水平的用户提供个性化服务。其次,建立了动态信用评估体系,实时监测并调整用户信用服务策略,帮助用户持续提升信用。再者,根据不同用户的需求,提供包括信用消费、理财投资、融资借贷等在内的多样化金融服务。最后,借助大数据、人工智能、区块链等技术手段,确保金融服务的安全可靠和高效便捷,持续陪伴用户实现信用与财富的双重增长。 适合人群:对个人信用管理有一定需求,希望通过科学金融规划实现资产稳健增值的个人及小微企业主。 使用场景及目标:①希望提升个人或企业信用评级的用户;②寻求合适金融产品和服务以优化财务管理的人群;③需要安全可靠的融资渠道支持业务发展的创业者和中小企业。 阅读建议:本文详细阐述了信用飞如何通过技术创新和个性化服务助力用户信用成长及资产增值,建议读者重点关注文中提到的技术应用和服务特色,结合自身情况思考如何更好地利用此类金融科技服务来优化个人或企业的财务状况。

    少儿编程scratch项目源代码文件案例素材-AI战争.zip

    少儿编程scratch项目源代码文件案例素材-AI战争.zip

    工业自动化中出口设备1200线体程序的PLC通讯与V90-FB284协同控制开源指南

    内容概要:本文详细介绍了出口设备1200线体程序的配置与优化方法,涵盖PLC通讯控制、V90模块配置以及工艺对象与FB284的协同控制。文章强调了开源特性的优势,使得用户可以自由扩展和优化控制系统。主要内容包括:1) 出口设备1200线体程序的核心地位及其复杂控制逻辑;2) 多个PLC设备的通讯协作,确保数据可靠传输;3) V90模块的具体配置步骤,确保各模块稳定运行;4) 工艺对象与FB284的协同控制,避免逻辑冲突;5) 开源带来的便利性,便于用户进行功能扩展和学习;6) 实际应用中的优化措施,提高系统的运行效率。 适合人群:从事工业自动化领域的工程师和技术人员,尤其是那些希望深入了解PLC通讯控制和V90伺服配置的人。 使用场景及目标:适用于需要配置和优化出口设备1200线体程序的实际工程项目,帮助用户掌握PLC通讯、V90配置及工艺对象与FB284协同控制的方法,从而提升生产线的效率和稳定性。 其他说明:文章提供了大量实用的代码片段和调试技巧,有助于读者更好地理解和实施相关配置。同时,文中提到的一些具体案例和经验分享也为实际操作提供了宝贵的参考。

    前端面试与vue源码讲解

    前端面试与vue源码讲解

    少儿编程scratch项目源代码文件案例素材-green vs blue.zip

    少儿编程scratch项目源代码文件案例素材-green vs blue.zip

    博世汽车电驱仿真模型:同步与异步电机FOC控制及弱磁优化

    内容概要:本文详细介绍了博世汽车电驱仿真模型中同步电机和异步电机的FOC(磁场定向控制)技术及其优化方法。主要内容涵盖相电流波形生成、弱磁控制、正反转切换、滑差补偿以及铁损计算等方面的技术细节。通过MATLAB、Python和C等多种编程语言实现了对电机控制的精确模拟,展示了如何通过数学方法和智能算法提高电机性能,减少电流畸变和转矩脉动。文中特别强调了弱磁控制在高速区的应用,通过动态查表法自动调整d轴电流分量,有效解决了电压极限椭圆的问题。此外,还提到了一些创新性的技术应用,如相位预判机制、动态滑差补偿和自适应耦合系数计算等。 适合人群:从事电机控制、电动汽车研究及相关领域的工程师和技术人员。 使用场景及目标:适用于希望深入了解同步电机和异步电机FOC控制原理及其实现方法的研究人员和工程师。目标是掌握先进的电机控制技术和优化方法,应用于实际项目中,提高系统性能和可靠性。 其他说明:文章不仅提供了详细的理论解释,还附有具体的代码实现,便于读者理解和实践。同时,文中提到的一些创新性技术可以为相关领域的研究提供新的思路和方法。

    少儿编程scratch项目源代码文件案例素材-RPG游戏引擎5.5c.zip

    少儿编程scratch项目源代码文件案例素材-RPG游戏引擎5.5c.zip

Global site tag (gtag.js) - Google Analytics