1.AOP概念
AOP是Aspect Oriented Programming的简写,中文通常译作面向方面编程,其核心内容就是所谓的“横切关注点”。
我们知道,使用面向对象方法构建软件系统,我们可以利用OO的特性,很好的解决纵向的问题,因为,OO的核心概念,如继承等,都是纵向结构的。但是,在软件系统中,往往有很多模块,或者很多类共享某个行为,或者说,某个行为存在于软件的各个部分中,这个行为可以看作是“横向”存在于软件之中,他所关注的是软件的各个部分的一些共有的行为,而且,在很多情况下,这种行为不属于业务逻辑的一部分。例如,操作日志的记录,这种操作并不是业务逻辑调用的必须部分,但是,我们却往往不得在代码中显式进行调用,并承担由此带来的后果(例如,当日志记录的接口发生变化时,不得不对调用代码进行修改)。这种问题,使用传统的OO方法是很难解决的。AOP的目标,便是要将这些“横切关注点”与业务逻辑代码相分离,从而得到更好的软件结构以及性能、稳定性等方面的好处。
AOP包含以下主要概念:
Ø Aspect方面:一个关注点的模块化,这个关注点实现可能另外横切多个对象。事务管理是J2EE应用中横切关注点中一个很好的例子。
Ø Joinpoint连接点:程序执行过程中明确的点,如方法的调 用或特定的异常被抛出。
Ø Advice通知:在特定的连接点AOP框架执行的动作。各种类型的通知包括“around”、“before”和“throws”通知。通知类型将在下面讨论。许多AOP框架都是以拦截器做通知模型,维护一个“围绕”连接点的拦截器链。
Ø Pointcut切入点:指定一个通知将被引发的一系列连接点 的集合。AOP框架必须允许开发者指定切入点:例如,使用正则表达式。
Ø Introduction引入:添加方法或字段到通知化类。
Ø IsModified接口,来简化缓存。
Ø Target object目标对象:包含连接点的对象。也被用来 引用通知化或代理化对象。
Ø AOP proxy AOP代理: AOP框架创建的对象,包含通知。
Ø Weaving织入:组装方面创建通知化对象。这可以在编译时完成(例如使用AspectJ编译器),也可以在运行时完成。Spring和其他一些纯Java AOP框架, 使用运行时织入。
各种通知类型包括:
Ø Around通知: 包围一个连接点的通知,如方法调用。这是最 强大的通知。Aroud通知在方法调用前后完成自定义的行为。它们负责选择继续执行连接点或直接返回它们自己的返回值或抛出异常来短路执行。
Ø Before通知: 在一个连接点之前执行的通知,但这个通知 不能阻止流程继续执行到连接点(除非它抛出一个异常)。
Ø Throws通知: 在方法抛出异常时执行的通知。
Ø After returning通知: 在连接点正常完成后执行的通知, 例如,如果一个方法正常返回,没有抛出异常。
Around通知是最通用的通知类型。大部分基于拦截器的AOP框架如Nanning和JBoss4只提供 Around通知。
AOP,给我们的软件设计带来了一个新的视角和软件架构方法。使用AOP,我们可以专注于业务逻辑代码的编写,而将诸如日志记录、安全检测等系统功能交由AOP框架,在运行时刻自动耦合进来。
通常,我们可以在如下情景中使用AOP技术:
Ø Authentication 权限
Ø Caching 缓存
Ø Context passing 内容传递
Ø Error handling 错误处理
Ø Lazy loading 懒加载
Ø Debugging 调试
Ø logging, tracing, profiling and monitoring 记录跟踪 优化 校准
Ø Performance optimization 性能优化
Ø Persistence 持久化
Ø Resource pooling 资源池
Ø Synchronization 同步
Ø Transactions 事务
2.AOP的使用
2.1.使用AOP实现松散耦合
考虑如下情况:对于应用软件系统来说,权限控制是一个常见的例子。为了得到好的程序结构,通常使用OO的方法,将权限校验过程封装在一个类中,这个类包含了一个校验权限的代码,例如:
public class Security{ public bool CheckRight(User currentUser , Model accessModel , OperationType operation) { ……//校验权限 }}
然后,在业务逻辑过程中进行如下调用:
public class BusinessClass{ public void BusinessMethod() { Security s = new Security(); if (!s. CheckRight(……)) { return ; } ……//执行业务逻辑 }}
这种做法在OO设计中,是常见的做法。但是,这种做法会带来以下一些问题:
1、不清晰的业务逻辑:从某种意义上来说,权限校验过程并不是业务逻辑执行的一部分,这个工作是属于系统的,但是,在这种情况下,我们不得不把系统的权限校验过程和业务逻辑执行过程掺杂在一起,造成代码的混乱。
2、代码浪费:使用这种方法,我们必须所有的业务逻辑代码中用Security类,使得同样校验的代码充斥在整个软件中,显然不是很好的现象。
3、紧耦合:使用这种方法,我们必须在业务逻辑代码中显式引用Security类,这就造成了业务逻辑代码同Security类的紧耦合,这意味着,当Security发生变化时,例如,当系统进化时,需要对CheckRight的方法进行改动时,可能会影响到所有引用代码。下面所有的问题都是因此而来。
4、不易扩展:在这里,我们只是在业务逻辑中添加了权限校验,哪一天,当我们需要添加额外的功能,例如日志记录功能的时候,我们不得不同样在所有的业务逻辑代码中添加这个功能。
5、不灵活:有的时候,由于某些特定的需要,我们需要暂时禁止,或者添加某项功能,采用传统的如上述的做法,我们不得不采用修改源代码的方式来实现。
为了解决这些问题,我们通常会采用诸如设计模式等方式,对上面的方案进行改进,这往往需要很高的技巧。利用AOP,我们可以很方便的解决上述问题。
2.2.使用AOP组合两个业务逻辑
使用AOP,我们不仅仅可以用来分离系统功能和业务逻辑,就象上面我们做的那样,也可以用来耦合不同的业务逻辑,得到更加灵活的软件结构。下面,我们通过一个具体的案例,来看看怎么通过AOP,组合两个业务逻辑过程。
我们假设有如下一个场景:
我们设计了一个ERP系统,其中,库存管理系统需要同财务系统相交互,例如,当某个库存商品报废的时候,需要有相应的财务处理过程。因此,我们通常需要在库存商品报废的业务逻辑中引用相关的财务处理逻辑。这必然会造成两个部分的耦合。当然,为了使两个部分尽量耦合程度降低,我们通常会使用Façade等设计模式来进行解耦。
由于某些原因,我们需要将库存管理系统单独出售,这就需要我们需要从库存商品报废的业务逻辑中将引用的相关的财务处理逻辑去除,这意味着我们需要修改原有的代码。为了解决这个问题,即可以随时将财务处理逻辑添加或者从库存商品报废的业务逻辑中删除,我们可以采用一些方法,例如,设置一些开关参数,在库存商品报废的业务逻辑中,根据这些开关参数的值,来判断是否需要执行财务处理逻辑。
问题是,这仍旧不是理想的解决方案。采用这种方式,你必须事先知道所有需要设置的开关参数,并且,在业务逻辑代码中添加相应的判断。当为系统增加一个类似的需要灵活处理的部分时,开发人员不得不添加相应的参数,并且修改相应的代码(添加相应的判断代码)。修改代码总是不好的事情,因为按照软件工程的要求,当有新的需求是,尽量不要修改原来的代码,而是新增相应的代码。但是,在这种情况下,你做不到。
使用AOP,我们可以通过一种更加自然的方式来实现这个目标。基本方法如下:
首先,编写相关的库存商品报废业务逻辑,不需要添加任何其他的内容,并且,把这个逻辑的代码设置为可AOP的。
其次,按照正常的方式,编写财务处理逻辑。
从上面的例子可以看出,采用AOP的好处是,我们可以独立的编写各个业务逻辑,使得系统各个部分之间的耦合度降到最低,然后,可以在系统中根据需要随时组合两个逻辑,而不用修改原来的任何代码。
3.AOP的实现
应该认识到,完全的AOP实现,需要开发语言的支持。因为对于AOP的研究,还正在进行之中,目前的开发语言,都还没有完全支持AOP的,但是,我们可以利用现有的一些语言功能,来实现AOP的部分功能。
实现AOP的关键,是拦截正常的方法调用,将我们需要额外附加的功能透明的“织入”到这些方法中,以完成一些额外的要求。从总体方法上来说,织入的方法有两大类:静态织入和动态织入。
静态织入方法,一般都是需要扩展编译器的功能,将需要织入的代码,通过修改字节码(Java)或者IL代码(.Net)的方法,直接添加到相应的被织入点;或者,我们需要为原来语言添加新的语法结构,从语法上支持AOP。AspectJ就是采用的这种方式。使用这种方式来实现AOP,其优点是代码执行的效率高,缺点是实现者需要对虚拟机有很深的了解,才能够做到对字节码修改。由于织入方法是静态的,当需要添加新的织入方法时,往往需要重新编译,或者说运行字节码增强器重新执行静态织入的方法。当然,在.Net平台上,我们也可以使用Emit提供的强大功能来实现这一点。另外,字节码增强器带来了很大的不透明性,程序员很难直观的调试增强后的字节码,因此很多程序员总是在心理上抵制这种字节码增强器。
动态织入的方法,具体实现方式就有很多选择了。在Java平台上,可以使用Proxy模式,或者定制ClassLoader来实现AOP功能。在.Net平台上,要实现AOP的动态织入,归纳起来,可以采用以下几种方法:
- 使用ContextAttribute和ContextBoundObject来对对象的方法进行拦截。关于ContextAttribute的具体使用方法,读者可以参考MSDN等相关资料。
- 使用Emit来,在运行时刻动态构建被织入代码后的类,当程序调用被织入类时,实际上调用的是被修改后的类。LOOM使用的就是这种方式,但是,个人认为,LOOM目前的实现非常生硬,其可扩展性和灵活性都不是很好。
- 使用Proxy模式。
所谓Proxy,就是“为其他对象提供一种代理以控制对这个对象的访问”。可以用下面的图来表示Proxy模式: