为了更好的理解AOP实践和体现AOP的优势,我们始终将OOP和AOP的比较贯穿到下文中。并在最终总结出AOP与OOP相比所拥有的优点,AOP的缺点以及AOP一般的使用场景。
在比较研究OOP和AOP实践之前,先让解决从理论上OOP和AOP解决问题的差别,也就是它们各自从问题空间到解空间的不同映射关系。
在不同的文献中对其定义有着细微的差别,本文对其定义的如下:
问题空间(problemspace)是相对于解的领域内,对问题的分析和抽象所形成的领域。
解空间(solutionspace)是领域内对问题的实现部分[2]。
软件开发总是为了解决某一个或某一组特定的问题,从需求出发产生一个能满足需求的软件,就等于得到了问题的解,因此它可以粗略地抽象成有问题空间到解空间的映射的过程。那么需求分析自然属于问题空间,我们把系统分析,设计模型和程序归为解空间。问题空间到解空间的映射不同,这是AOP和OOP的根本区别。
将问题空间向解空间映射时采用的是多维到一维的映射。OOP使我们可以把系统看做是一批相互合作的对象。类允许我们把实现细节隐藏在接口下。多态性为相关概念提供公共的行为和接口,并允许特定的组建在无需访问基础实现的前提下改变特定的行为。但OOP技术将问题世界里的自然界映射成的基本单位是“类”,一个类实现一个接口后,那就不能动态地实现另一个接口,已有类的行为在编译以前就基本固定,要么是类内部定义的方法,要么是继承和实现接口继承过来的方法。但是实际的编程工作中我们碰到了“交叉类”的情况,即横切关注点。
AOP能够比OOP更好的分离系统关注点,从而提供模块化的横切关注点。可以把一个复杂的系统看作是由多个关注点来组合实现的。一个典型的系统可能会包括几个方面的关注点,如业务逻辑,性能,数据存储,日志和调度信息,授权,安全,线程,错误检查等,还有开发过程中的关注点,如易懂,易维护,易追查,易扩展等,这样就完成了多维到多维的映射过程(如图4.1)
OOP针对业务处理过程的实体及其属性和行为进行抽象封装,以获得更加清晰高效的逻辑单元划分。面向名词领域,关注的是纵向的,表示对象之间的泛华-特化的关系(通过继承来实现),其一般关注点的实现单元是类。
AOP则是针对业务处理过程中的切面进行提取,它所面对的是处理过程中的某一个步骤或者阶段,已获得逻辑过程中各部分之间低耦合性的隔离效果。面向动词领域,关注的是横向的。就是逻辑过程中某个片段或切面,比如:日志管理,权限管理等。它所关注的等多地是一些软件系统本身的东西。而不是面向对象中多关注的业务逻辑。用同一种松散的方式来降低系统之间的耦合度等问题,其模块化单位是切面。
AOP,从其本质上讲,就是用一种松散耦合的方式来实现各个独立的关注点,然后再组合这些实现来建立最终的系统。用它所建立的系统是松散耦合的。我们通过上面分析基于AOP问题空间到解空间以及2AOP简介我们总结出AOP软件开发的一般步骤,如图4.2所示,需要以下三个步骤[18]:(具体例子见4.3)
(1) 关注点分解。通过分析用户需求,提取出模块级的核心业务关注点和系统将级的横切关注点。在这一步里,需要把核心关注点同系统级的横切关注点分开。横切关注点的划分通常满足以下几个条件:
l 关注点所实现的功能横跨了多个模块
l 关注点与核心业务相对独立,不属于同一问题域
l 关注点不属于系统的业务功能范畴
(2) 关注点实现。对于核心业务逻辑关注点,通过OOP的方法来实现,生成各个类;面对于横切关注点,要通过AOP的方法来实现,生成方面代码。
(3)关注点重组。重组的过程也叫织入或编织(织入的方法见),就是利用一定的重组规则,通过方面编织器把方面代码编织到基于00P实现的核心功能代码当中。以构建最终系统。
我们在几乎任何一个B/S模式的系统开发中,都会遇到登录这么一个模块。很简单,用户登录的过程,涉及到对用户的登录认证,看用户输入的用户名和密码是否合法。下面我们将用OOP和AOP两种方式来实现登录模块。在中实现方式的比较中,将得到OOP和AOP的区别,AOP开发的一般步骤,并且分析AOP技术的适用的场景以及AOP技术使用给我们系统开发带来的优点。
使用Transaction类来模拟事物管理,代码如下: public class Transaction { //模拟开启事务 public void beginTransaction() { System.out.println("开启事务"); } //模拟提交事务 public void commit(){ System.out.println("提交事务"); } } 使用Login类来模拟登录这个业务,代码如下: public class Login { private Transaction transaction; //模拟用户登录 public Login(Transaction transaction) { this.transaction = transaction; } //模拟登录这个业务操作 public void login() { transaction.beginTransaction(); System.out.println(“验证用户名和密码是否合法”) transaction.commit(); } }
使用Client来模拟客户端用户触发登录事件,代码如下: public class Client { //模拟客户端用户触发登录事件 @Test public void testLogin() { Transaction transaction = new Transaction(); Login login = new Login(transaction); login.login(); } }
上面是我们登录的这模块的一个简单的模拟实现,加入有一天由于公司的需要我们将对每一次用户的登录做日志记录,方便今后做安全审计。当然软件系统的变化是无常,这种假设也完全是有可能。我们虽然可以通过科学的需求来尽量的避免这种情况,但是完全避免是不可能的。好的,我们现在要在登录中加入日志处理的功能。
使用Logger类来模拟日志处理功能,代码如下: public class Logger { public void logging() { System.out.println("logging"); } } 但是我们很快发现Login类也必须做出改变如下: public class Login { private Transaction transaction; //增加logger Private Logger logger; //模拟用户登录 public Login(Logger logger,Transaction transaction) { this.logger = logger ; this.transaction = transaction; } //模拟登录这个业务操作 public void login(String username, String password) { transaction.beginTransaction(); System.out.println(“验证用户名和密码是否合法”); logger.logging(); transaction.commit(); } }
我们按照2AOP开发步骤来使用Spring AOP来实现上面的例子。
通过分析我们可以得出在登录模块中,我们要实现的核心业务功能是登录,它是我们的核心业务功能。按照4.2AOP开发步骤横切关注点的划分通常满足以下几个条件。我们不难发现事务处理和日志处理并不属于核心业务功能,而且可能横跨其他的模块。所以事务处理和日志处理是横切关注点。具体实现如下:
按照Spring AOP的语言规范,Transaction类来模拟事物管理切面,代码同3.4OOP实现的Transaction。Logger类来模拟日志处理切面,代码同3.4OOP实现的Logger。其中Transaction和Logger就是AOP中的切面,beginTransaction() ,commit()和logging()方法就是AOP中的通知。 核心业务关注点实现: public class Login { //模拟登录这个业务操作 public void login() { System.out.println("验证用户名密码的合法性"); } }
实际上编织的过程,是由Spring AOP框架来实现,我们只需要告诉Spring AOP框架,Spring AOP框架自动为我们做这些工作,而不用我们自己编写代码实现。我只需要在配置文件(.xml)中做配置即可。配置文件代码如下: <?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.5.xsd"> <!-- 1、引入AOP的命名空间 2、目标类 3、切面 4、拦截器 由spring内部实现 5、aop的配置 --> <bean id="transaction" class="cn.miao.login.aop.cglib.Transaction"></bean> <bean id="login" class="cn.miao.login.aop.cglib.Login"></bean> <!-- aop的配置 --> <aop:config> <!-- 切入点表达式 expression 确定哪个类可以生成代理对象 id 唯一标识 --> <aop:pointcut expression="execution(* cn.miao.login.aop.cglib.Login.*(..))" id="perform"/> <!-- 切面 --> <aop:aspect ref="transaction"> <!-- 前置通知 * 在目标方法执行之前 * --> <aop:before method="beginTransaction" pointcut-ref="perform"/> <aop:after method="commit" pointcut-ref="perform"/> </aop:aspect> </aop:config> </beans>
测试代码: @Test public void testLogin() { ApplicationContext context = new ClassPathXmlApplicationContext("cn/miao/login/aop/cglib/applicationContext.xml"); Login login = (Login) context.getBean("login"); login.login(); } 运行结果: 开启事务 验证用户名密码的合法性 提交事务
当然我们假设出现和4.4 OOP实现相同的变更,这时我们需要加入日志功能。这时我们只需要增加Logger类同OOP实现。并在配置文件中增加如下信息: <bean id="logger" class="cn.miao.login.aop.cglib.change.Logger"></bean> <aop:aspect ref="logger"> <!-- 前置通知 * 在目标方法执行之前 * --> <aop:after method="logging" pointcut-ref="perform"/> </aop:aspect> 是不是很方便,只需要扩张,而无需对代码进行变更。这就是AOP在处理类似切面上优势。
实现 Login类同4.4 Spring AOP实现。 事务处理类Transaction是一个切面如下: public aspect Transaction { //切入点 pointcut beginTransaction() : execution(* cn.miao.login.aop.aspectj.Login.login(..)); //前置通知 before() : beginTransaction() { System.out.println("开启事务"); } pointcut commit() : execution(* cn.miao.login.aop.aspectj.Login.login(..)); //后置通知 after() : commit() { System.out.println("提交事务"); } }
测试 /** * 在运行前需要先用ajc命令编译 */ public class Client{ public static void main(String []args) { Login login = new Login(); login.login(); } } 运行结果: 开启事务 验证用户名密码的合法性 提交事务
需求变更 增加切面Logger。 public aspect Logger { pointcut logging() : execution(* cn.miao.login.aop.aspectj.change.Login.login(..)); after() returning() : logging() { System.out.println("logging"); } } 测试客户端不需要做任何变动,在运行前需要重新编译。运行结果如下: 开启事务 验证用户名密码的合法性 logging 提交事务
AOP在00P的基础上提出了切面(Aspect)的概念。它与00P的区别首先表现在AOP引入了人们认识系统的一个全新的视角:方面。一个系统按照00P的视角可以按照功能“纵向”分割为一个个的对象。而AOP则从“横向”的角度将系统的一部分分割为一个个方面。
其次,在AOP里,每个对象并不知道自己是否被其他关注点 (方面)关注(横切),也不知道有多少关注点正在关注自己。所有这些信息都在方面里定义。而00P则相反,每个对象都知道自己是否需要添加关注点,需要添加多少关注点。也就是说,在AOP里,组合的流向是从横切关注点到一般关注点(对象本身)。而00P则相反。这是AOP和00P的主要区别。
第三,在00P里每个对象的功能在编译期或者在代码编写阶段就决定了。而在AOP中.方面的功能需要在执行“织入”之后才添加到对象之中。因此一个对象的功能最终确定的时间取决于方面织入的时间,可能在编译期,也可能在装载期或者运行期[16]。
虽然AOP在OOP之后提出,并且志在更好得解决00P所面临的问题。但是AOP与00P并不是相互竞争的两种技术。人们不是为了代替00P而提出AOP。事实上AOP与00P两者互相之间是一个很好的补充。AOP补充了00P的不足。事实上AOP也能
够补足其他的编程范型。像面向过程的编程。但是因为00P是现在的主流范型。所以大部分AOP工具都是对00工具的扩展。人们也更热衷于AOP对00P的补充[16]
代码集中易于理解 解决了由于OOP 跨模块造成的代码混乱和代码分散。OOP同时实现几个关注点模糊了不同关注点的实现,使得关注点与其实现之间的对应关系不明显。软件系统中的模块可能要同时兼顾几个方面的需要。举例来说:开发者经常要同时考虑业务逻辑,性能,同步,日志和安全问题,兼顾各方面的需要导致相应关注点的实现元素同时出现,引起代码混乱,AOP可以把这种需求作为独立的方面很容易的实现,可读性好。在上面的应用例子中,我们很容易发现OOP实现中,业务方法login()方法不仅有业务逻辑-登录,而且混杂了很多事务处理和日志记录等功能代码。当这种横切功能很多时,代码就比较混乱了。而在AOP实现(包括Spring AOP实现和AspectJ实现中)login方法中只有登录的业务逻辑,事务处理和日志记录等横切功能都分散在相应的切面中,业务逻辑很清晰,代码可读性自然很好。
在OOP中,由于横切关注点本来就涉及多个模块,其相关实现也就得遍布在这些模块里,如在一个使用了数据库的系统里,性能问题就会影响所有数据库的模块,这导致代码分散在各处。而AOP模块化横切关注点,用最小的耦合处理每个关注点,使得即使是横切关注点也是模块化的。这样的实现产生的系统,其代码的冗余小。模块化的实现还得系统容易理解和维护。在AOP实现中横切面的代码都是公用的,横切功能的代码只需要写一次。而在OOP实现中,如果出现新的也业务逻辑,在新的业务逻辑中又存在横切功能(如日志等),则这些横切代码又要重复的写一遍。所以AOP实现比OOP实现代码冗余度低很多。
系统容易扩展 由于方面模块根本不知道横切关注点所以很容易通过建立新的方面加入新的功能,另外,当往系统中加入新的模块时,已有的方面自动横切进来使系统易于扩展,设计师可以推迟为将来的需求作决定,因为他可以把这种需求作为独立的方面很容易地实现。在上面的应用示例中,我们可以清楚的看到在需求变更的情况下,AOP表现出很好的代码扩展性,只需要增加新的切面,或者修改配置文件,而不用行OOP实现,要修改业务逻辑代码。AOP很好的符合对扩展开放,对修改关闭的原则。
更好的代码重用性,AOP把每个切面实现为独立的模块,模块之间是松散耦合的。举例来说,上文中应用的例子中,Logger和Transaction切面都是作为独立的模块存在的,它们之间几乎不存在耦合,松散耦合的实现通常意味着更好的代码重用性AOP 在使系统实现松散耦合这一点上比OOP做得更好 [2,16] 。
AOP是在OOP的基础上提出来的,为软件系统中横切功能提供非常好的实现方案,弥补了OOP在解决横切问题中不足。深入讨论了AOP的基本概念,包括Advice, Pointcut等。研究了常用AOP编织时机,一个是AspectJ的静态编织,也就是编译时织入,优点是代码执行的效率更高,缺点是每次修改代码后要重新编译代码;另一个是Spring AOP的动态编织,也就是代码运行时编织,优点是修改代码后不需要使用专门的编译工具重新编译即可部署,缺点是代码优化度不高,执行效率稍微差些。
AOP语言的实现中主要研究SpringAOP的实现,第一个核心的模块是AopProxy代理对象的生成,使用的是jdk动态代理或者cglib动态技术来实现的。第二个核心模块是Spring AOP拦截器的调用,其实也就是实现了通知的调用。
AOP应用中通过一个简单的登录的业务逻辑,采用OOP实现,AOP实现(Spring AOP实现和AspectJ实现)三种实现方式,并且观察了三种实现方式在应对需求变更情况下的反应,发现了AOP技术与OOP技术相比在处理横切逻辑时,具有代码可读性好,冗余度低,扩展性好,重用率高这四个优点。
总之AOP技术以及随之而来的各种AOP语言出现,给我们软件系统的开发带来了更多思想武器和开发工具,如果善以研究和利用将为我们软件开发带来很多惊喜。
目前AOP技术应用和研究系列博文规划为六篇,目前已经完成,目录见下文。其中AOP技术应用和研究应用示例代码已经完成,我已经分享在了github上aop https://github.com/demiaowu/aop有任何错误或者疑问欢迎联系cfreestar@163.com。具体参考文献,参见文末,如果有未加注明的,请联系,我将及时修改或删除。
AOP技术应用和研究系列博客AOP技术应用和研究
原文地址:http://blog.csdn.net/demiaowu/article/details/40003501