AOP(Aspect Oriented Programming,面向切面编程)并不是要代替OOP(Object Oriented Programming,面向对象编程),而是一种对OOP的补充。OOP模块化的关键单元是类,而在AOP中,模块化的单元是切面(Aspect)。切面可以实现跨多个类型和对象的事务管理、日志等方面的模块化。
AOP编程的目标与OOP并没有什么不同,都是为了减少重复和专注于业务。相比之下,OOP是婉约派的选择,用继承和组合的方式,编制一套类和对象的体系;而AOP是豪放派的选择,大手一挥,凡是某包、某类、某命名方法,一并同样处理。也就是说,OOP是“绣花针”,而AOP是“砍柴刀”。
Spring框架的关键组件之一是AOP框架。虽然Spring IoC容器不依赖于AOP,但在Spring应用中,经常会使用AOP来简化编程。在Spring框架中使用AOP主要有以下优势。
(1)提供声明式企业服务,特别是可以作为EJB声明式服务的替代品。
(2)允许用户实现自定义切面。在某些不适合用OOP编程的场景中,采用AOP来补充。
(3)可以对业务逻辑的各个部分进行隔离,从而使业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高开发的效率。
要使用Spring AOP,需要添加spring-aop模块。
AOP概念并非Spring AOP所特有的,以下概念同样适用于其他AOP框架,如AspectJ。
(1)Aspect(切面):将关注点进行模块化。某些关注点可能会横跨多个对象,如事务管理,它是Java企业级应用中一个关于横切关注点很好的例子。在Spring AOP中可以使用常规类(基于模式的方法)或@Aspect注解的常规类来实现切面。
(2)Join Point(连接点):在程序执行过程中某个特定的点,如某方法调用或处理异常时。在Spring AOP中,一个连接点总是代表一个方法的执行。
(3)Advice(通知):在切面的某个特定连接点上执行的动作。通知有各种类型,包括“around”“before”和“after”等。许多AOP框架,包括Spring,都是以拦截器来实现通知模型的,并维护一个以连接点为中心的拦截器链。
(4)Pointcut(切入点):匹配连接点的断言。通知和一个切入点表达式关联,并在满足这个切入点的连接点上运行(如当执行某个特定名称的方法时)。切入点表达式如何和连接点匹配是AOP的核心,Spring默认使用AspectJ切入点语法。
(5)Introduction(引入):声明额外的方法或某个类型的字段。Spring允许引入新的接口(及一个对应的实现)到任何被通知的对象。例如,可以使用一个引入来使bean实现IsModified接口,以简化缓存机制。在AspectJ社区,Introduction也被称为Inter-type Declaration(内部类型声明)。(6)Target Object(目标对象):被一个或多个切面所通知的对象,也有人把它称为Advised(被通知)对象。既然Spring AOP是通过运行时代理实现的,那这个对象永远是一个Proxied(被代理)对象。
(7)AOP Proxy(AOP代理):AOP框架创建的对象,用来实现Aspect Contract(切面契约),包括通知方法执行等功能。在Spring中,AOP代理可以是JDK动态代理或CGLIB代理。
(8)Weaving(织入):把切面连接到其他的应用程序类型或对象上,并创建一个Advised(被通知)对象。这些可以在编译时(如使用AspectJ编译器编译)、类加载时和运行时完成。
Spring与其他纯Java AOP框架一样,在运行时完成织入。其中Advice(通知)的类型主要有以下几种。
(1)Before Advice(前置通知):在某连接点之前执行的通知,但这个通知不能阻止连接点前的执行(除非它抛出一个异常)。
(2)After Returning Advice(返回后通知):在某连接点正常完成后执行的通知,如果一个方法没有抛出任何异常,则正常返回。
(3)After Throwing Advice(抛出异常后通知):在方法抛出异常退出时执行的通知。
(4)After(finally)Advice(最后通知):当某连接点退出时执行的通知(无论是正常返回还是异常退出)。
(5)Around Advice(环绕通知):包围一个连接点的通知,如方法调用。这是最强大的一种通知类型。环绕通知可以在方法调用前后完成自定义的行为,它也会选择是否继续执行连接点,或者直接返回自己的返回值或抛出异常来结束执行。Around Advice是最常用的一种通知类型。与AspectJ一样,Spring提供所有类型的通知,但最好使用简单的通知类型来实现需要的功能。例如,如果只是需要用一个方法的返回值来更新缓存,虽然使用环绕通知也能完成同样的事情,但最好使用After Returning通知。用最合适的通知类型可以使编程模型变得简单,并且能够避免很多潜在的错误。例如,如果不调用Join Point(用于Around Advice)的proceed()方法,就不会有调用的问题。
在Spring 2.0中,所有的通知参数都是静态类型的,因此可以使用合适的类型(如一个方法执行后的返回值类型)作为通知的参数,而不是使用一个对象数组。切入点和连接点匹配的概念是AOP的关键,这使得AOP不同于其他仅仅提供拦截功能的旧技术。切入点使得通知可独立于OO(Object Oriented,面向对象)层次。例如,一个提供声明式事务管理的Around Advice可以被应用到一组横跨多个对象的方法上(如服务层的所有业务操作)。
Spring AOP用纯Java实现,不需要专门的编译过程。Spring AOP不需要控制类装载器层次,因此它适用于Servlet容器或应用服务器。
Spring目前仅支持方法调用作为连接点。虽然可以在不影响Spring AOP核心API的情况下加入对成员变量拦截器的支持,但Spring并没有实现成员变量拦截器。如果需要通知对成员变量的访问和更新连接点,可以考虑其他语言,如AspectJ。
Spring实现AOP的方法与其他的框架不同。Spring并不是要尝试提供最完整的AOP实现(尽管Spring AOP有这个能力),相反,它侧重于提供一种AOP实现和Spring IoC容器的整合,用于解决企业级开发中的常见问题。因此,Spring AOP通常都和Spring IoC容器一起使用。Aspect使用普通的bean定义语法,与其他AOP实现相比,这是一个显著的区别。有些任务是使用Spring AOP无法轻松或高效完成的,如通知一个细粒度的对象。这时,使用AspectJ是最好的选择。对于大多数在企业级Java应用中遇到的问题,Spring AOP都能提供一个非常好的解决方案。
Spring AOP从来没有打算通过提供一种全面的AOP解决方案来取代AspectJ,它们之间的关系应该是互补的,而不是竞争的。Spring可以无缝地整合Spring AOP、IoC和AspectJ,使所有的AOP应用完全融入基于Spring的应用体系,这样的集成不会影响Spring AOP API或AOP Alliance API。
Spring AOP保留了向下兼容性,这体现了Spring框架的核心原则—非侵袭性,即Spring框架并不强迫在业务或领域模型中引入框架特定的类和接口。
Spring AOP默认使用标准的JDK动态代理作为AOP的代理,这样任何接口(或接口的set方法)都可以被代理。
Spring AOP也支持使用CGLIB代理,当需要代理类(而不是代理接口)时,使用CGLIB代理是很有必要的。如果一个业务对象并没有实现一个接口,就会默认使用CGLIB。面向接口编程也是一个最佳实践,业务对象通常都会实现一个或多个接口。此外,在需要通知一个未在接口中声明的方法的情况下,或者需要传递一个代理对象作为一种具体类型的方法的情况下,还可以强制地使用CGLIB。
@AspectJ是用于切面的常规Java类注解。AspectJ项目引入了@AspectJ风格作为AspectJ 5版本的一部分。Spring使用与AspectJ 5相同的用于切入点解析和匹配的注解,但AOP运行时仍然是纯粹的Spring AOP,并不依赖于AspectJ编译器。
1.启用@AspectJ
可以通过XML或Java配置来启用@AspectJ支持。不管在任何情况下,都要确保AspectJ的aspectjweaver.jar库在应用程序的类路径中。这个库可在AspectJ发布的lib目录中或通过Maven的中央库得到,其配置如下。
2.创建应用
下面用一个简单有趣的例子来演示Spring AOP的用法。此例演绎了一段“武松打虎”的故事情节—武松(Fighter)在山里等着老虎(Tiger)出现,只要发现老虎出来,就打老虎。源码可以在aop-aspect目录下找到。
aop-aspect应用的pom.xml文件定义如下。
3.定义业务模型
首先定义老虎类:
老虎类只有一个walk()方法,只要老虎出来,就会触发这个方法。
4.定义切面和配置
那么武松要做什么呢?他主要关注老虎的动向,等着老虎出来活动。所以在Fighter类中定义一个@Pointcut。同时,在该切入点前后都可以执行相关的方法,定义foundBefore()和foundAfter():
相应的Spring配置如下。
5.定义主应用
主应用定义如下。
6.运行应用
运行应用,可以看到控制台最终输出如下内容。
Spring提供基于XML的AOP支持,并提供新的“aop”命名空间。
在Spring配置中,所有的aspect和advisor元素都必须放置在元素中(应用程序上下文配置中可以有多个元素)。一个元素可以包含pointcut、advisor和aspect 3个元素(注意这些元素必须按照这个顺序声明)。
1.声明一个pointcut
pointcut可以在元素中声明,从而使pointcut定义可以在几个aspect和advice之间共享。
以下声明代表了服务层中任何业务服务都能执行的切入点的定义。
2.声明advice
Spring的advice与@AspectJ风格是一致的,它们具有完全相同的语义。
以下是一个示例。
一个aspect就是在Spring应用程序上下文中定义的一个普通的Java对象。状态和行为被捕获到对象的字段和方法中,pointcut和advice被捕获到XML中。
使用元素声明一个aspect,并使用ref属性引用辅助bean:
本小节基于aop-aspect示例进行改造,形成一个新的基于XML的AOP实战例子。新的应用源码可以在aop-aspect-xml目录下找到。
1.定义业务模型
之前定义的老虎(Tiger)类保持不变。老虎类只有一个walk()方法,只要老虎出来,就会触发这个方法:
之前所定义的武松(Fighter)类保持不变,稍作调整,去除注解,变成一个单纯的POJO:
2.定义切面和配置
所有AOP的配置都在相应的Spring的XML配置中:
3.定义主应用
主应用保持不变,其代码如下。
4.运行应用
运行应用,可以看到控制台最终输出如下内容。