AOP之利器:ASM介绍

什么是ASM?

ASM是一个字节码操控框架。
它能被用来动态生成类,或者增强既有类的功能。
ASM可以直接产生二进制class文件,也可以在类被加载入Java虚拟机之前动态改变类的行为。

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

为什么要动态生成Java类?

动态生成Java类与AOP是密切相关的。
AOP的初衷是在于,软件设计世界中存在着这么一类代码,零散而又耦合
零散是由于一些公有的功能(比如log)分散在所有的模块之中,同时改变log的功能又会影响到所有的模块。
出现这样的缺陷,很大程度上是由于传统的面向对象编程注重以继承关系为代表的纵向关系,而对于拥有相同功能或者说方面(Aspect)的模块之间的横向关系不能很好的表达。

例如:目前有一个现有的银行管理系统,它包括Bank, Customer, Account, Invoice等对象,现在要加入一个安全检查模块,对已有类的所有操作之前都必须进行一次安全检查。
而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代码。

PSJava类的文件概述

为什么选择ASM?

最直接的改造Java类的方法,莫过于直接改写class文件。
Java规范详细说明了class文件格式,通过直接编辑字节码,确实是一个可以改变Java类的方法。
但是,这得要求使用者对Java class文件的格式了熟于心,我们得小心地推算出,想改造的函数相对于文件首部的偏移量,同时重新计算class文件的检验码,以通过Java虚拟机的安全机制。

看到这里不是有了一种写汇编的感觉了~~~

Java Instrument包

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

比较理想且流行的一种方法,即使用java.lang.ref.proxy。

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

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 毕竟是通过反射实现的,必须在效率上付出代价:有实验数据表明,调用反射比一般的函数开销至少要大 10 倍, 对于性能关键的应用,使用 proxy class 是需要精心考虑的,以避免反射成为整个应用的瓶颈;

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

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

小结

这里我们比较一下ASM和其他实现AOP的底层技术

AOP底层技术 功能 性能 面向接口编程 编程难度
直接改写class文件 完全控制类 无明显性能代价 不要求 高,要求对class文件结构和Java字节码有深入的了解
JDK Instrument 完全控制类 无论是否改写,每个类装入时都要执行 hook 程序 不要求 高,要求对 class 文件结构和 Java 字节码有深刻了解
JDK Proxy 只能改写 method 反射引入性能代价 要求
ASM 几乎能完全控制类 无明显性能代价 不要求 中,能操纵需要改写部分的 Java 字节码

ps:摘自IBM Developer

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,529评论 5 475
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,015评论 2 379
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,409评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,385评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,387评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,466评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,880评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,528评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,727评论 1 295
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,528评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,602评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,302评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,873评论 3 306
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,890评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,132评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,777评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,310评论 2 342

推荐阅读更多精彩内容