Spring in Action(Spring 实战)的第四章第一节(4.1 What is aspect-oriented programming)讲述了AOP原理及Spring对AOP的支持。有关AOP的主要思想和优点,可以参看这篇笔记《Spring in Action》第四版第一章《将 Spring 付诸实践》读书笔记(一)。本文将讲解AOP的各个组成部分、使用方式,以及Spring对AOP的支持。
最近在看美剧《西部世界》,我就结合书中的例子用西部世界的机器人来举例吧。
AOP各个部分的概念和作用(Defining AOP terminology)
如图所示,在程序执行过程中,在其中执行某些特定任务的过程前后,需要执行一些操作。join point代表要执行额外动作的场景,advice则定义哪个时间点(任务执行前还是执行后)要做哪些额外操作,但advice还不知道要关注哪些场景。pointcut告诉每一个advice要执行操作的场景。这样就完整地定义了在哪些场景下、场景执行的哪个时间点下执行哪些动作了。这样听起来有些抽象。举个例子吧。
美剧《西部世界》里的游乐场,给很多机器人编写了脚本,让它们按照具体进行行动。假设其中一个观众机器人去剧场看一场表演。好了,观众到了剧场,在表演开始前,她要关闭手机。并且在表演开始前,她要坐下。在表演结束后,她要鼓掌。如果在表演过程中,表演由于设备故障无法继续,她要喊退票(^_^)。如果我们只把advice告诉这位机器人,那么她的知识是,她知道自己有四种任务,在某些事情开始前,她要做关闭手机和坐下两件事,在某些事情开始前,她要在坐下。某些事情结束后,她要鼓掌,如果在某些事情进行过程中,出了异常,她要喊退票。她不知道是某些事情到底是哪些事情。这时候,再告诉她,这些事情都是指一场表演。那她就知道该在什么时候做什么了。advice和pointcut合起来就是aspect,构成了所有需要知道的信息。
接下来解释weaving这个术语,weaving是将aspect应用到一个具体的对象的join point上。这个动作由spring等框架在目标对象的以下生命周期完成:
- 编译时应用。在编译目标类的时候应用aspect,这需要一个特殊的编译器,AspectJ的weaving编译器就是通过这种方式应用aspect的。
- 加载类的时候,在将目标类加载到JVM的时候应用aspect,这需要一个特殊的ClassLoader,用来在目标类被引入应用程序之前,先访问目标类的二进制代码。AspectJ 5的load-time weaving(LTW)支持通过这种方式应用aspect。
- 运行时应用。 在应用程序执行的某个时候应用aspect,特别地,AOP的容器动态生成目标对象的代理对象,以应用aspect。这也是Spring的方式。
Spring对AOP的支持
不同框架对AOP有不同程度的支持。Spring对AOP的支持包含四方面:
- Spring基于代理的经典的AOP
- 使用XML配置将纯POJO转化为aspect
- 使用Spring的@Aspect标签,创建aspect
- Spring不创建aspect,只注入(引用)AspectJ框架的aspect
前面三种都是Spring自己的AOP实现,特点是基于动态代理,而且只能作用在方法级别。
其中第一种,Spring基于代理的经典的AOP已经过时了,这里不再描述。
第二种方式,是使用Spring的aop包,将纯POJO转化成aspect,再应用到目标类上。这种方式需要使用XML配置,但能够很容易地将POJO转化为aspect。
第三种方式,Spring借用AspectJ的aspect来实现标签驱动的AOP.底层使用的仍然是Spring基于代理的AOP,但是编程模型更像AspectJ。这种方式的好处是不用使用XML配置。
第四种方式,Spring并不负责创建aspect的bean,而是由AspectJ创建bean,Spring注入(引用)bean。
Spring的AOP框架的要点
Spring的advice是用Java写的
其中,pointcuts除了用java的标签之外,也可以用xml配置,但java开发人员对此都很熟悉。与Spring不同的是,AspectJ框架使用的不是纯粹的java,这样,虽然功能更强大,但也增加了学习成本。
Spring在运行时应用aspect
Spring在运行时,通过代理类,将aspect应用到Spring管理的bean中。当调用一个类的方法时,代理类解析该方法调用,再执行面向方面的逻辑代码,最后,执行被调用的原始类的方法。也就是说,代理在执行目标类的方法之前,执行了额外的面向方面的业务逻辑。
只有当应用程序需要时,Spring才会生成代理类的实例。如果使用ApplicationContext,只有在从BeanFactory加载完了所有的bean之后,才会创建代理用的bean。
由于Spring在运行时创建代理,所以不需要使用特殊的编译器,将aspect应用到join point中。
Spring只支持方法级别的join point
Spring不支持将aspect应用到field级别,所以不能用来更新字段。Spring不支持构造函数,所以在bean初始化的时候,无法使用AOP。大部分情况下,Spring对AOP的支持是够用的,如果不够用,可以使用上面说第四种方式,增强功能。