9.1 Introduction 介绍

Aspect-Oriented Programming (面相切面编程 AOP) 用另外的一种编程架构的思考来补充 Object-Oriented Programming (面相对象编程OOP)。OOP 主要的模块单元是 class (类),而 AOP 是 aspect(切面)。切面使得诸如事务管理等跨越多个类型和对象的关注点模块化。(这样的关注点在 AOP 的字眼里往往被称为 crosscutting (横切关注点))

AOP 是 Spring 里面的主要的组件。虽然 Spring IoC 容器没有依赖 AOP,意味着你不想用的时候也无需使用 AOP,但 AOP 提供一个非常有用的中间件解决方案来作为 Spring IoC 的补充。

Spring 2.0 AOP

Spring 2.0 引入了一种更加简单并且更强大的方式来自定义切面,用户可以选择使用 [schema-based](9.3. Schema-based AOP support.md)(基于模式)的方式或者使用 [@AspectJ 注解样式](9.2. @AspectJ support.md)。这两种风格都完全支持 Advice(通知)类型和 AspectJ 的切入点语言,虽然实际上仍然使用 Spring AOP 进行 weaving (织入)。

本章主要讨论 Spring 2.0 对基于模式和基于 @AspectJ 的 AOP 支持。 Spring 2.0 完全保留了对 Spring 1.2 的向下兼容性,[下一章 10. Spring AOP APIs](10. Spring AOP APIs.md) 将讨论 Spring 1.2 API 所提供的底层的 AOP 支持。

Spring中所使用的AOP:

  • 提供声明式企业服务,特别是为了替代 EJB 声明式服务。最重要的服务是 [declarative transaction management](../IV. Data Access/12.5. Declarative transaction management.md)(声明性事务管理), 这个服务建立在Spring的抽象事务管理(transaction abstraction)之上。
  • 允许用户实现自定义的切面,用 AOP 来完善 OOP 的使用。

如果你只打算使用通用的声明式服务或者预先打包的声明式中间件服务,例如 pooling(缓冲池),你可以不直接使用 AOP ,可以忽略本章大部分内容

###9.1.1 AOP concepts 概念

首先让我们从定义一些重要的 AOP 概念开始。这些术语不是 Spring 特有的。 不幸的是,Spring 术语并不是特别的直观;如果 Spring 使用自己的术语,将会变得更加令人困惑。

  • Aspect(切面): 一个关注点的模块化,这个关注点可能会横切多个对象。事务管理是 企业级 Java 应用中一个关于横切关注点的很好的例子。 在 Spring AOP 中,切面可以使用通用类( [schema-based](9.3. Schema-based AOP support.md)基于模式的风格) 或者在普通类中以 @Aspect 注解([@AspectJ 注解样式](9.2. @AspectJ support.md))来实现。
  • Join point (连接点): 在程序执行过程中某个特定的点,比如某方法调用的时候或者处理异常的时候。 在 Spring AOP 中,一个连接点 总是 代表一个方法的执行。
  • Advice(通知): 在切面的某个特定的 join point(连接点)上执行的动作。通知有各种类型,其中包括“around”、“before”和“after”等通知。 通知的类型将在后面部分进行讨论。许多 AOP 框架,包括 Spring,都是以拦截器做通知模型,并维护一个以连接点为中心的拦截器链。
  • Pointcut(切入点): 匹配 join point(连接点)的断言。通知和一个切入点表达式关联,并在满足这个切入点的连接点上运行(例如,当执行某个特定名称的方法时)。 切入点表达式如何和连接点匹配是 AOP 的核心:Spring 默认使用 AspectJ 切入点语法。
  • Introduction(引入): 声明额外的方法或者某个类型的字段。 Spring 允许引入新的接口(以及一个对应的实现)到任何被 advise(通知)的对象。 例如,你可以使用一个引入来使 bean 实现 IsModified 接口,以便简化缓存机制。(在 AspectJ 社区 ,Introduction也被称为 inter-type declaration(内部类型声明))
  • Target object(目标对象): 被一个或者多个 aspect(切面)所advise(通知)的对象。也有人把它叫做 advised (被通知) 对象。 既然 Spring AOP 是通过运行时代理实现的,这个对象永远是一个 proxied(被代理) 对象。
  • AOP proxy(AOP代理): AOP 框架创建的对象,用来实现 aspect contract(切面契约)(包括通知方法执行等功能)。 在 Spring 中,AOP 代理可以是 JDK 动态代理或者 CGLIB 代理。
  • Weaving(织入): 把 aspect(切面)连接到其它的应用程序类型或者对象上,并创建一个被 advised (被通知)的对象。 这些可以在编译时(例如使用 AspectJ 编译器),类加载时和运行时完成。 Spring 和其他纯 Java AOP 框架一样,在运行时完成织入。

通知的类型:

  • Before advice(前置通知): 在某 join point(连接点)之前执行的通知,但这个通知不能阻止连接点前的执行(除非它抛出一个异常)。
  • After returning advice(返回后通知): 在某 join point(连接点)正常完成后执行的通知:例如,一个方法没有抛出任何异常,正常返回。
  • After throwing advice(抛出异常后通知): 在方法抛出异常退出时执行的通知。
  • After (finally) advice(最后通知): 当某join point(连接点)退出的时候执行的通知(不论是正常返回还是异常退出)。
  • 环绕通知(Around Advice): 包围一个join point(连接点)的通知,如方法调用。这是最强大的一种通知类型。 环绕通知可以在方法调用前后完成自定义的行为。它也会选择是否继续执行连接点或直接返回它们自己的返回值或抛出异常来结束执行。

环绕通知是最常用的一种通知类型。跟 AspectJ 一样,Spring 提供所有类型的通知,我们推荐你使用尽量简单的通知类型来实现需要的功能。 例如,如果你只是需要用一个方法的返回值来更新缓存,虽然使用环绕通知也能完成同样的事情, 但是你最好使用 After returning 通知而不是环绕通知。 用最合适的通知类型可以使得编程模型变得简单,并且能够避免很多潜在的错误。 比如,你不需要调用 JoinPoint(用于Around Advice)的 proceed() 方法,就不会有调用的问题。

在Spring 2.0中,所有的通知参数都是静态类型,因此你可以使用合适的类型(例如一个方法执行后的返回值类型)作为通知的参数而不是使用一个对象数组。

pointcut(切入点)和 join point(连接点)匹配的概念是 AOP 的关键,这使得 AOP 不同于其它仅仅提供拦截功能的旧技术。 切入点使得 advice (通知)可独立于 OO 层次。 例如,一个提供声明式事务管理的around 通知可以被应用到一组横跨多个对象中的方法上(例如服务层的所有业务操作)。

###9.1.2 Spring AOP capabilities and goals 功能和目标

Spring AOP 用纯 Java 实现。它不需要专门的编译过程。Spring AOP 不需要控制类装载器层次,因此它适用于 Servlet 容器或应用服务器。

Spring 目前仅支持使用方法调用作为join point(连接点)(在 Spring bean 上通知方法的执行)。 虽然可以在不影响到 Spring AOP核心 API 的情况下加入对成员变量拦截器支持,但 Spring 并没有实现成员变量拦截器。 如果你需要通知对成员变量的访问和更新连接点,可以考虑其它语言,例如 AspectJ。

Spring 实现 AOP 的方法跟其他的框架不同。Spring 并不是要尝试提供最完整的 AOP 实现(尽管 Spring AOP 有这个能力), 相反的,它其实侧重于提供一种 AOP 实现和 Spring IoC 容器的整合,用于帮助解决在企业级开发中的常见问题。

因此,Spring AOP 通常都和 Spring IoC 容器一起使用。 Aspect 使用普通的bean 定义语法(尽管 Spring 提供了强大的“autoproxying(自动代理)”功能): 与其他 AOP 实现相比这是一个显著的区别。有些事使用 Spring AOP 是无法轻松或者高效的完成的,比如说通知一个细粒度的对象。 这种时候,使用 AspectJ 是最好的选择。不过经验告诉我们: 于大多数在企业级 Java 应用中遇到的问题,只要适合 AOP 来解决的,Spring AOP 都没有问题,Spring AOP 提供了一个非常好的解决方案。

Spring AOP 从来没有打算通过提供一种全面的 AOP 解决方案来取代AspectJ。 我们相信无论是 proxy-based(基于代理 )的框架比如说Spring AOP 亦或是 full-blown 的框架比如说是 AspectJ 都是很有价值的,他们之间的关系应该是互补而不是竞争的关系。 Spring 可以无缝的整合 Spring AOP,IoC 和 AspectJ,使得所有的 AOP 应用完全融入基于 Spring 的应用体系。 这样的集成不会影响 Spring AOP API 或者AOP Alliance API;Spring AOP保留了向下兼容性。[下一章 10. Spring AOP APIs](10. Spring AOP APIs.md)会详细讨论 Spring AOP API。

一个 Spring Framework 的核心原则是 non-invasiveness(非侵袭性);这意味着你不应该在您的业务/域模型被迫引入框架特定的类和接口。然而,在一些地方,Spring Framework 可以让你选择引入 Spring Framework 特定的依赖关系到你的代码,给你这样选择的理由是因为在某些情况下它可能是更容易读或编写一些特定功能。Spring Framework (几乎)总是给你的选择:你可以自由的做出明智的决定,选择最适合您的特定用例或场景。

这样的选择与本章有关的是 AOP 框架(和 AOP 类型)选择。你可以选择AspectJ 和/或 Spring AOP ,你也可以选择 @AspectJ 注解式的方法或Spring 的 XML 配置方式。事实上,本章以介绍 @AspectJ 风格为先不应该被视为 Spring 团队倾向于 @AspectJ 的方法胜过在 Spring 的XML 配置方式。

见[9.4. Choosing which AOP declaration style to use](9.4. Choosing which AOP declaration style to use.md)里面有更完整的每种风格的使用原因探讨。

###9.1.3 AOP Proxies 代理

Spring缺省使用标准 JDK dynamic proxies(动态代理)来作为AOP的代理。这样任何接口(或者接口的 set)都可以被代理。

Spring 也支持使用 CGLIB 代理. 对于需要代理类而不是代理接口的时候CGLIB 代理是很有必要的。 如果一个业务对象并没有实现一个接口,默认就会使用 CGLIB。 此外,面向接口编程 也是一个最佳实践,业务对象通常都会实现一个或多个接口。此外,还可以强制的使用 CGLIB,在那些(希望是罕见的)在你需要通知一个未在接口中声明的方法的情况下,或者当你需要传递一个代理对象作为一种具体类型到方法的情况下。

一个重要的事实是,Spring AOP 是 proxy-based (基于代理)。见第9.6.1,“理解 AOP 代理”理解这个含义


书籍推荐