【聚杰网核心技术】JRockit JVM对AOP的支持
关于这些代理的先后次序有一个问题;在联结点(或切点)级别上没有控制次序的细粒度配置方法。
某些其他情况可能导致更加无法预测的结果。例如,当一个字段访问被截取时,这往往意味着字段获取(field get)字节码指令被移动到一个新添加的方法,并且被替换为对这个新方法的调用。因此,下一个编织器将在代码中的另一个位置(在那个新添加的方法中)看到一个字段访问,而它自己的匹配机制和配置可能不匹配这个位置。
总之,主要问题如下:
- 代理看到哪些字节码?问题是,正常情况下,被编织的字节码是从类装载管道获得的,但是建立类数据库所依赖的字节码是从硬盘读取的。当涉及多个代理时,硬盘上的字节码不再是正在执行的字节码了;因为某个代理可能已经修改了字节码,这意味着第二个代理看到的是错误的字节码视图。当使用HotSwap API时,也会发生这种情况。
- 当代理A撤消或者改变它的编织操作时,可能会出现问题。如果另一个代理B在代理A之后已经执行了修改,那么代理B可能已经重新构造了字节码,导致字节码看起来完全不一样了(尽管其功能是一样的),在此情况下,代理A就不知道该怎么做了。
截取反射式调用是不可能的
当前的编织方式只能测试(至少是部分地)可静态确定的执行流。请考虑以下代码示例,它在给定的实例foo上调用方法void doA()。
public void invokeA(Object foo) throws Throwable { Method mA = foo.getClass().getDeclaredMethod("doA",
new Class[0]); mA.invoke(foo, new Object[0]);}在现代的代码库中常常使用这种反射式访问来创建实例、调用方法或者访问字段。
从字节码的角度来看,对方法void doA()的调用是看不到的。编织器只看到对java.lang.reflect API的调用。还没有简单且高效的办法可以对通过反射执行的调用进行编织。目前,这对于如何执行编织以及如何实现AOP是很重要的限制。最好的办法是,开发人员使用执行端切点来代替。显然,从JVM的角度来看,存在一个对doA()方法的方法调度,尽管这在源代码或字节码中没有出现。已经证明,JVM编织是以高效的方式解决这个问题的唯一编织机制。
其他问题
某些人对字节码测试持怀疑态度,尤其是在动态执行的情况下(在装载时或运行时)。对于动态修改代码,存在着一种不应低估的情绪化影响,尤其是在与某种盲目的革命性新技术(比如AOP或服务的透明式插入)结合使用时。在涉及多个代理时可能发生的混乱将增加人们的怀疑。
另一个潜在的问题是Java规范中对类文件规定的64Kb边界。方法体的字节码指令总长度被限制为64Kb。在编织已经很大的类文件(例如,将JSP文件编译为servlet时产生的类文件)时,这可能会导致问题。在处理这个类时,可能会突破64Kb的限制,这就会导致运行时错误。
提议的解决方案
对于上面讨论的大多数问题,JVM编织是自然的解决方案。为了理解其原因,我们将查看两个示例。这些示例说明,JVM已经做了执行编织所需的大多数工作:当类被装载时,JVM读取字节码以便建立为java.lang.reflect.* API服务所需的数据。另一个例子是方法调度。现代的JVM将方法或代码块的字节码编译为更高级而且更高效的构造和执行流(在可以应用代码内联的地方进行代码内联)。由于HotSwap API的需要,JRockit JVM(可能还包括其他JVM)还会记录哪个方法调用了其他方法,这样如果在运行时重新定义某个类,那么在所有期望的位置(内联的或非内联的),类中定义的方法体仍然可以被热交换。
因此,不必为了编织进一个建议调用而修改字节码,比如说在特定的方法调用之前。JVM实际上可以掌握关于这个建议调用的知识,它会在任何匹配的联结点上对此建议进行调度,然后再调度实际的方法。
由于不接触字节码,可以预期到直接的好处,比如:
- 不会由于字节码测试而导致启动开销。
- 对于在任何位置、任何时间、以线性开销添加和删除建议的完全的运行时支持。
- 对建议的反射式调用的隐式支持。
- 不需要占用额外的内存来将类模型复制到某些框架特有的结构。
本系列的第二篇文章将详细描述提议的JRockit JVM对AOP的支持。
以下代码示例作了总结性说明。它在调用sayHello()方法之前对静态方法advice()进行调度:
public class Hello { // -- the sample method to intercept public void sayHello() { System.out.println("Hello World"); } // -- using the JRockit JVM support for AOP static void weave() throws Throwable { // match on method name StringFilter methodName = new StringFilter( "sayHello", StringFilter.Type.EXACT ); // match on callee type ClassFilter klass = new ClassFilter( Hello.class, false, null ); // advice is a regular method dispatch Method advice = Aspect.class.getDeclaredMethod( "advice", new Class[0] ); // get a JRockit weaver and subscribe the // advice to the join point picked out by the filter Weaver w = WeaverFactory.createWeaver(); w.addSubscription(new MethodSubscription( new MethodFilter( 0, null, klass, methodName, null, null ), MethodSubscription.InsertionType.BEFORE, advice )); } // -- sample code static void test() { new Hello().sayHello(); } public static void main(String a[]) throws Throwable { weave(); test(); } // -- the sample aspect public static class Aspect { public static void advice() { System.out.println("About to say:"); } }}结束语
在Java社区中已经开始流行使用字节码测试来实现中间件领域中的高级技术,比如AOP或者透明式服务插入。但是,几个关键的限制妨碍了字节码测试,而且它的广泛使用将导致更多的问题,影响可伸缩性和可用性。
因为字节码测试在某种程度上已经成了在AOP中实现编织的标准方式,所以本文中描述的限制和问题将会妨碍它(可能已经妨碍它了)。
我们相信,JVM对AOP的支持是这些问题的自然解决方案。我们将要提供一个已经在JRockit JVM中实现的基于订阅的API,它与JVM方法调度组件紧密集成。本系列中的下一篇文章将更详细地讲解这个API,并且解释如何解决每个问题。
其他资料
- JRockit Technology Center – 第一个具有AOP支持的企业级JVM站点
- AspectWerkz – 普通Java AOP框架
- AspectJ – 用Java 实现AOP的事实标准
- AOSD – 面向方面的软件开发
- Quick Start Guide to Enterprise AOP with Aspectwerkz 2.0 - David Teare写的一篇文章 (dev2dev,2005年4月)
前一篇文章介绍了面向方面编程和关注点分离的概念,解释了这种概念如何在方面构造的帮助下增强软件的模块化,以及如何使用它来补充面向对象编程。方面代表模块化的单元,并且由切点(何处)、建议(什么)以及类型间声明(在这个新的方面补充对象模型)组成。有许多技术可以将关注点编织进应用程序,在当今的Java领域中,最常用的技术是字节码测试,在AspectWerkz和AspectJ(从1.1版开始)中实现了这种技术。
但是,这种AOP实现方式具有几个缺点,本系列的第1篇文章对此进行了详细解释。尽管在字节码测试领域还有很大的发展余地(包括Java 5中的JVMTI/JSR-163测试代理规范和高效字节码操作库,比如ObjectWeb ASM),但字节码测试代价不菲。此外,已经证明,使用字节码测试实现AOP是不完善的。例如,如果不采用非常特殊且效率低下的解决方案,就无法通过切点匹配反射式方法调用或get和set字段。总的来说,所有基于字节码测试的产品都受到字节码测试技术相关问题的影响,而且随着这种技术的普及,问题将逐渐增加。
所有这些缺点促使JRockit团队提出了JVM对AOP的支持。其目标是尽可能全面地实现当前的AOP语义,同时不把JVM限制在某个特定的面向方面框架的语言细节和编程模型上。
本文通过具体的代码示例介绍该API,然后描述其好处及未来的发展方向。
我们的动机
让我们快速地回顾引入JVM的AOP支持的技术动机。










