在Spring 2.0中最激动人心的增强之一是关于Spring AOP,它变得更加便于使用而且更加强大,主要是通过复杂而成熟的AspectJ语言的支持功能来实现,而同时保留纯的基于代理的Java运行时。
我们一直坚信AOP(面向切面编程)很重要。为什么?因为它提供给我们一种新的思考程序结构的方法,能够解决很多纯OOP无法解决的问题——让我们能够在一个模块中实现某些需求,而不是以发散的方式实现。
为了理解这些好处,让我们考虑一些我们可以在需求中表达但无法直接用纯OO代码实现的情况。企业开发者使用一个通常的词汇表来让他们进行清楚的沟通。比如,像服务层,DAO层,Web层或者Web控制器这样的术语,这不需要什么解释。
许多需求是用这个词汇表中的术语来表达的。比如:
◆服务层应该是可以处理事务的。
◆当DAO操作失败时,SQLException或者其他特殊持久化技术的异常应该被翻译,以确保DAO接口不会有漏掉的抽象。
◆服务层对象不应该调用Web层,因为各层应该只依赖直接处在其下方的层。
◆由于并发相关操作的失败而导致失败的等幂业务服务可以重试。
虽然这些需求都是现实存在的,并来自于经验,但它们并不能用纯OOP来优雅地解决。为什么?主要有两个原因:
◆这些来自于我们词汇表的术语有意义,但它们并不是抽象。我们不能使用术语编程;我们需要抽象。
◆所有这些都是所谓横切关注点的例子。一个横切关注点,在用传统OO方法实现时,会分解成很多类和方法。比如,想象一下在跨DAO层遭遇特殊异常时要使用重试逻辑。这个关注点横切许多DAO方法,而且在传统的方式中会需要实现许多单独的修改。
AOP就是通过对横切关注点进行模块化,并让我们从普通的还可以编程的抽象的词汇表来表达术语,来解决这样问题的技术,这些抽象叫做切入点,我很快会再解释一些关于它们的细节。这种方法带来一些主要好处,比如:
◆因为减少了剪切粘贴风格的复制而减少代码行数。这在像异常转换和性能监测这样的try/catch/finally习惯用法中尤其有效。
◆在单个代码模块中捕捉这样需求的能力,提升可追踪能力。
◆在单个地方修补bug的能力,而不需要重新访问应用程序中许多位置。
◆确保横切关注点不混淆主要的业务逻辑——随着开发的进展,这很有可能成为危险之处。
◆开发者和团队之间更好的职责分离。比如,重试功能可以有单个开发者或者团队来编码,而不需要由许多开发者跨多个子系统进行编码。
因此AOP很重要,我们想提供***的解决方案。
Spring AOP无疑是最广泛使用的AOP技术,归功于以下优点:
◆采用成本几近为零。
◆提供正确的切入点,这才称得上是AOP而不仅仅是拦截。
◆提供一个支持许多使用方式的灵活的框架,可编程也可通过XML。
然而,在Spring 2.0之前,Spring中的AOP有一些缺点:
◆不写Java代码,只能表达简单的切入点。并没有一种切入点表达语言来以字符串形式,简洁表达复杂的切入点,虽然RegexpMethodPointcutAdvisor允许定义简单正规的基于表达的切入点。
◆当配置复杂AOP使用场景时,XML配置会变得很复杂。泛型元素被用来配置AOP类;虽然这对一致性来说很棒,对切面和类提供DI和其他服务,但它没有一个专门的配置方法来得简洁。
◆Spring AOP不适合通知细粒度的对象——对象需要由Spring管理或者通过编程被代理。
◆基于代理的方法的性能负载在少数案例中成为问题。
◆因为Spring AOP分离了代理和目标(被修饰或者被通知的对象),如果某个目标方法调用了目标上的方法,就不会使用到代理,意味着AOP通知并不适用。AOP使用基于代理的方法的正反面影响超出了本文的范围:有一些积极的因素(比如能够对同一个类的不同实例应用不同的通知),但主要还是消极的。
为了在Spring 2.0中增强这个重要领域,我们希望在它的优势上构建,同时解决缺点。
***的两个缺点也是最显著的。它们都跟切入点相关。后面的三个缺点在Spring用户的正常使用中很少发生,如果它们证明是的确有问题的,我们建议使用AspectJ。(就像你会看到的,这是Spring AOP直接的进步。)
XML配置扩展解决了关键的挑战之一。因为我们想要保持Spring模块的设计,我们过去不能在Spring DTD中提供特定于AOP的标签——因此在这种情况下需要依赖可以详细一点的通用配置。随着Spring 2.0的出现,这样的问题没有了,因为XML schema并不像DTD,它允许扩展。我们可以提供一个AOP命名空间,看起来能让Ioc容器识别AOP结构,但不会影响模块化。
AOP术语101:理解切入点和通知
让我们简要地修正一下某些AOP术语。如果你使用过AOP这些概念,可能对你来说很熟悉——这些概念是相同的,仅仅有一点不同,即更加优雅和强大的表达方式。
切入点是匹配规则。它在程序执行中确定应该应用某个切面的点的集合。这些点叫做连接点。在应用程序运行时,连接点随时会有,比如对象的实例化和方法的调用。在Spring AOP(所有版本)的案例中,唯一支持的连接点是公有方法的执行。
通知是可以被切面应用到连接点的行为。通知能在连接点之前或之后应用。通知的所有类型包括:
◆Before advice:在连接点之前调用的通知。比如,记录方法调用即将发生的日志。
◆After returning adive:如果在连接点的方法正常返回时调用的通知。
◆AfterThrowing advice(在Spring1.x中叫做Throws通知):如果连接点的方法抛出一个特殊的异常时调用的通知。
◆After advice:在连接点之后调用的通知,无论结果是什么。特别像Java中的finally。
◆Around advice:能够完全控制是否执行连接点的通知。比如,用来在事务中封装某个方法调用,或者记录方法的执行时间。
切面是结合切入点和通知成一个模块方案,解决特殊的横切问题。
如果这有点抽象,请不要担心:代码示例会很快解释清楚的。
对在Spring 2.0和AspectJ的环境中关于AOP基础的更深讨论,请参考Adrian在InfoQ上很棒的文章,"Simplifying Enterprise Applications with Spring 2.0 and AspectJ."
为什么会是AspectJ切入点表达式?
迄今为止,我们讨论过的概念都是基本的AOP概念,对于Spring AOP或者AspectJ而且这并不特别,在Spring1.x中已经是存在的。那么为什么我们选择在Spring 2.0中采用AspectJ呢?
如果我们需要一种切入点表达语言,那么选择就会很简单。AspectJ有个思路很好,严格定义和充足文档的切入点语言。它最近实现了一个当在Java 5上运行时,能对采用Java 5语法的编码全面检查。它不仅有很棒的参考材料,而且很多书籍和文章都对它进行了介绍。
我们不相信重新发明的轮子,而且定义我们自己的切入点表达语言是不合理的。进一步而言,自从AspectWerkz在2005年早期和冰岛AspectJ项目之后,很明显AspectJ是除了Spring 2.0之外唯一一个主流的AOP技术。因此关键的合并既是一种考虑也是一种技术优势。
【编辑推荐】