【聚杰网核心技术】JRockit JVM对AOP的支持
其语法风格与Java开发人员使用java.lang.reflect.Method.invoke(null/*static method*/, .../*args*/)对方法进行反射式调用一样。但是,利用JVM的AOP支持,底层的动作调用根本不涉及任何反射。
允许用户控制动作实例,就会产生有趣的用例。例如,可以实现一个简单的委托模式,在运行时用另一个实现替换整个动作实例,而不涉及JVM的内部组件。
注意,这将有助于(按照规定)实现AOP方面实例化模型,比如issingleton()、pertarget()、perthis()、percflow()等等,同时不会将JVM API限制在某些预定义的语义上。
在将预定注册到编织器实例之前,赋予它一个类型作为建议类型:before、instead-of、after-returning或after-throwing。
可以编写下面这样的代码来创建预定:
// Get a Weaver instance that will act as a// container for the subscription(s) we createWeaver w = WeaverFactory.getWeaver();// regular java.lang.reflect is used to refer// to the action method "simpleStaticAction()"Method staticActionMethod = SimpleAction.class.getDeclaredMethod( "simpleStaticAction", new Class[0]//no arguments );MethodSubscription ms = new MethodSubscription( .../* where to match*/, InsertionType.BEFORE, staticActionMethod);w.addSubscription(ms);
该代码示例假设用户使用静态动作方法实现。也可以使用实例方法编写这个示例,在这种情况下,应该传递给MethodSubscription一个包含类实例。
// Use of an action instance to refer to the// non static action method "simpleAction()"Method actionMethod = SimpleAction.class.getDeclaredMethod( "simpleAction", new Class[0]// no arguments );// Instantiate the action instanceSimpleAction actionInstance = new SimpleAction();MethodSubscription ms2 = new MethodSubscription( ...,// where to match, explained below InsertionType.BEFORE, actionMethod, actionInstance);w.addSubscription(ms2);
诸如within()和withincode()类型模式之类的AOP语义也通过该API的变体实现。
API细节:预定
如前面的代码示例所示,预定API依赖于java.lang.reflect.*对象模型和一些简单的抽象化(比如jrockit.ext.weaving.WMethod)来合并方法、构造函数和类的静态初始化器处理。
new MethodSubscription(...)调用的第一个参数必须是jrockit.ext.weaving.Filter实例,这个实例具有几个具体实现以便匹配方法、字段等等。
jrockit.ext.weaving.MethodFilter实例用作定义,JVM编织器实现根据它进行联结点阴影匹配(shadow matching)。jrockit.ext.weaving.MethodFilter允许根据以下各项进行过滤(还提供额外的结构支持within()/withincode()语义):
- 方法修饰符(比如使用java.lang.reflect.Modifier时的int)。
- Class<? extends java.lang.annotation.Annotation>,匹配方法运行时可见性注释。
- jrockit.ext.weaving.ClassFilter实例,匹配声明类型。
- jrockit.ext.weaving.StringFilter实例,匹配方法名。
- jrockit.ext.weaving.ClassFilter实例,匹配方法返回类型。
- jrockit.ext.weaving.UserDefinedFilter实例,用于实现更精细的匹配逻辑。
使用jrockit.ext.weaving.UserDefinedFilter回调机制来实现更高级的匹配方式(与Spring AOPorg.springframework.aop.MethodMatcher和org.springframework.aop.ClassFilter相似)。
所有这些结构都是可选的,如果遇到null,就表示“任意匹配”。
jrockit.ext.weaving.ClassFilter提供一种类似的方式:
- Class<? extends java.lang.annotation.Annotation>,匹配类运行时可见性注释。
- Class,匹配类类型。
- boolean值,表示是否匹配子类型。
因此,以下代码匹配所有名称以“bar”开头的方法调用。注意,在这个非常简单的例子中传递了好几个null值:
StringFilter sf = new StringFilter("bar", STARTSWITH);MethodFilter mf = new MethodFilter(0, null, null, sf, null, null);MethodSubscription ms = new MethodSubscription( mf, InsertionType.BEFORE, staticActionMethod);w.addSubscription(ms);作为更现实的例子,以下代码匹配所有三个EJB业务方法:
// Prepare the pointcut to match// @Stateless annotated classes business methodsMethodFilter ejbBizMethods = new MethodFilter( PUBLIC_NONSTATIC, // Method annotation does not matter null, new ClassFilter( // Declaring class, the java.lang.Class // for the EJB we are currently manipulating ejbClass, // no subtypes matching false, // class annotation Stateless.class ), // EJB methods matching is handled // in a UserDefinedFilter below instead null, // return type does not matter null, // custom Filter callback new UserDefinedFilter() { public boolean match( MethodFilter methodFilter, WMember member, WMethod within) { return !isEjbLifeCycleMethod(member); } });好处
使用JVM编织而不是字节码测试有几个好处。从较高的层面来看,编织作为JVM功能的自然扩展出现,因此在许多方面它不那么具有侵入性,并且为性能、可伸缩性和可用性各方面带来了许多好处。
关于字节码编织的问题(尤其是在加载时编织的情况下)的详细讨论,请参考本系列的第1部分。以下好处解决了所有这些问题。
- 不使用字节码测试,增强了可伸缩性
字节码没有被修改。在JVM内部组件中,仍然采用从字节码到可执行代码的常规编译管道。使用字节码测试时,需要分析字节码指令并用某些中间结构来表示它们,这样才能在测试框架(AOP编织器或基于字节码测试的产品)中操纵它们;而使用JVM编织不需要这么做。
编织器变得无所不在了。即使用户希望在启动时注册预定,这也不再是必须的。因为根本不需要分析字节码指令来寻找要截取的联结点,所以大大减少了应用程序的启动时间。这也提供了开发真正动态的系统的机会——动态意味着可以在任何时候部署方面和解除方面部署,而又不会由此引起额外的开销或复杂性。
- 不使用冗余的类型信息记录,降低了内存耗用并且提高了可伸缩性
因为不再进行字节码测试,因此与对象模型双重记录问题相关的问题就不会出现了。预定API依赖于java.lang.reflect.*模型,而这个模型已经以类似的方式向Java开发人员提供了此信息。
- 多个代理可以保持一致
因为所编织的类的字节码没有经过修改,所以不会因为两个不同的代理以不兼容的方式修改字节码(相互隐藏原始程序的属性),而造成冲突。预定的注册次序起到了优先权规则的作用。注意,如果类是可序列化的,那么不会为了在运行时执行所编织的建议而向其添加隐藏结构,所以常规的序列化将得到充分支持。而字节码测试技术通常需要确定序列化能力是否有所保留(例如,serialVersionUID字段的处理)。
- 支持截取反射式调用
通过使用JVM级方法调度,所有反射式调用(方法调用或者get或set字段)都可以被匹配,就像它们是常规调用,而且所有注册的动作都将被触发一样。这不需要任何额外的开销,也不涉及特定于实现的细节和复杂性。
未来的发展方向
尽管JVM编织很有帮助,而且解决了与字节码测试技术相关的可伸缩性和可用性问题,但是仍然必须解决一些缺陷才能使其完美地实现用例,这可能需要采用一些补充方法。
一些基于字节码测试的产品使用了细粒度更改,当前的JVM AOP API还无法实现这一特性。某些用例处理同步块,因此不同的锁定机制(如:分布式锁定)可以透明地注入常规的应用程序。这样的细粒度动作常常要求对同步块进行有条件执行,甚至完全删除同步块,并使用某个专用锁定API调用来替换它。可以在JVM中解决这样的特定需求,但是实际上不可能找到一个对每种用例都有效的高效解决方案。还有必要提醒一下的是,目前领先的AOP框架还不能将同步块公开为联结点。
在JVM级别上,无法轻松地实现AspectJ定义的某些细粒度语义。例如,AspectJ支持预初始化、初始化和构造函数执行切点。构造函数执行切点挑选出源代码中出现的构造函数,初始化切点挑选出获得已初始化实例的所有构造函数执行,包括this(...)构造函数委托。JVM难以把握这两者的差异。更具侵入性的代码内联策略可能会出现在哪些地方实际上也可能取决于编译器。
随着字节码测试逐渐流行起来,新的JVM API的引入肯定会遇到挑战。如果要开发一种同时适应两种JVM(支持新API的JVM,比如JRockit,以及不支持新API的JVM)的产品,那么成本会相当高。这个领域中的规范(如:JSR)可能有助于克服这种困难。
结束语
字节码测试技术目前已经在不同领域的Java平台上得到广泛使用,从面向方面软件开发到更特定于应用的解决方案(如:应用程序监控、持久性或分布式计算)。随着字节码测试的可用性和透明性的提高,加载时编织和部署时测试将会流行起来。
遗憾的是,这种技术没有为可伸缩性和可用性需求提供适当的支持。特别是随着这种技术的应用越来越广泛,以及对来自不同产品的不同测试代理的混合使用,这个问题会越来越严重。JVM编织和JVM对AOP的支持(比如在JRockit中所实现的)是解决这个问题的自然方法,可以促进革新和技术发展。JRockit团队所提出的Java API将JVM方法调度内部组件与用户定义的动作联系起来,仅依赖于java.lang.reflect API的预定优雅地填补了以前的鸿沟,并解决了主要的可伸缩性和可用性问题。
这种新的API要想获得广泛采用,需要对它进行认真的评估,并将它应用于真实的用例,比如AOP或者大型应用程序的运行时自适应。
参考资料
- JRockit JVM Support For AOP, Part 1(JRockit JVM对AOP的支持,第1部分),作者Jonas Bonér、Joakim Dahlstedt和Alexandre Vasseur(dev2dev,2005年8月)——即本系列的第1篇文章。
- JRockit Technology Center——第一个具有AOP支持的企业级JVM的站点。
- 新闻组:jrockit.developer.interest.aop。
- AspectWerkz——普通Java AOP框架。
- AspectJ——用Java实现AOP的事实标准。
Quick Start Guide to Enterprise AOP with Aspectwerkz 2.0,作者David Teare(dev2dev,2005年4月)。










