为了更好的理解AOP实践和体现AOP的优势。我们始终将OOP和AOP的比較贯穿到下文中。并在终于总结出AOP与OOP相比所拥有的长处,AOP的缺点以及AOP一般的使用场景。
1.1 问题空间到解空间的映射
在比較研究OOP和AOP实践之前。先让解决从理论上OOP和AOP解决这个问题的区别,也就是它们各自从问题空间到解空间的不同映射关系。
1.1.1“问题空间”和“解空间”的定义
在不同的文献中对其定义有着细微的区别,本文对其定义的例如以下:
问题空间(problemspace)是相对于解的领域内,对问题的分析和抽象所形成的领域。
解空间(solutionspace)是领域内对问题的实现部分[2]。
软件开发总是为了解决某一个或某一组特定的问题,从需求出发产生一个能满足需求的软件,就等于得到了问题的解,因此它能够粗略地抽象成有问题空间到解空间的映射的过程。那么需求分析自然属于问题空间。我们把系统分析,设计模型和程序归为解空间。问题空间到解空间的映射不同,这是AOP和OOP的根本差别。
1.1.2 OOP中的问题空间到解空间的映射
将问题空间向解空间映射时採用的是多维到一维的映射。OOP使我们能够把系统看做是一批相互合作的对象。类同意我们把实现细节隐藏在接口下。多态性为相关概念提供公共的行为和接口,并同意特定的组建在无需訪问基础实现的前提下改变特定的行为。但OOP技术将问题世界里的自然界映射成的基本单位是“类”。一个类实现一个接口后。那就不能动态地实现还有一个接口,已有类的行为在编译曾经就基本固定,要么是类内部定义的方法,要么是继承和实现接口继承过来的方法。
可是实际的编程工作中我们碰到了“交叉类”的情况。即横切关注点。
1.1.3AOP中的问题空间到解空间的映射
AOP可以比OOP更好的分离系统关注点,从而提供模块化的横切关注点。可以把一个复杂的系统看作是由多个关注点来组合实现的。
一个典型的系统可能会包含几个方面的关注点,如业务逻辑,性能,数据存储,日志和调度信息,授权,安全,线程,错误检查等。还有开发过程中的关注点,如易懂,易维护,易追查。易扩展等。这样就完毕了多维到多维的映射过程(如图4.1)
1.1.4针对的问题
OOP针对业务处理过程的实体及其属性和行为进行抽象封装。以获得更加清晰高效的逻辑单元划分。面向名词领域。关注的是纵向的。表示对象之间的泛华-特化的关系(通过继承来实现),其一般关注点的实现单元是类。
AOP则是针对业务处理过程中的切面进行提取。它所面对的是处理过程中的某一个步骤或者阶段,已获得逻辑过程中各部分之间低耦合性的隔离效果。
面向动词领域,关注的是横向的。就是逻辑过程中某个片段或切面。比方:日志管理,权限管理等。
它所关注的等多地是一些软件系统本身的东西。而不是面向对象中多关注的业务逻辑。用同一种松散的方式来减少系统之间的耦合度等问题,其模块化单位是切面。
2 AOP的开发步骤
AOP,从其本质上讲,就是用一种松散耦合的方式来实现各个独立的关注点。然后再组合这些实现来建立终于的系统。用它所建立的系统是松散耦合的。我们通过上面分析基于AOP问题空间到解空间以及2AOP简单介绍我们总结出AOP软件开发的一般步骤,如图4.2所看到的,须要下面三个步骤[18]:(详细样例见4.3)
(1) 关注点分解。通过分析用户需求,提取出模块级的核心业务关注点和系统将级的横切关注点。在这一步里,须要把核心关注点同系统级的横切关注点分开。横切关注点的划分通常满足下面几个条件:
l 关注点所实现的功能横跨了多个模块
l 关注点与核心业务相对独立,不属于同一问题域
l 关注点不属于系统的业务功能范畴
(2) 关注点实现。对于核心业务逻辑关注点,通过OOP的方法来实现,生成各个类。面对于横切关注点。要通过AOP的方法来实现,生成方面代码。
(3)关注点重组。重组的过程也叫织入或编织(织入的方法见),就是利用一定的重组规则。通过方面编织器把方面代码编织到基于00P实现的核心功能代码其中。以构建终于系统。
3 一个实际场景
我们在差点儿不论什么一个B/S模式的系统开发中,都会遇到登录这么一个模块。
非常easy。用户登录的过程,涉及到对用户的登录认证,看用户输入的username和password是否合法。
以下我们将用OOP和AOP两种方式来实现登录模块。在中实现方式的比較中,将得到OOP和AOP的差别。AOP开发的一般步骤,而且分析AOP技术的适用的场景以及AOP技术使用给我们系统开发带来的长处。
4 OOP实现
使用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(“验证username和password是否合法”) transaction.commit(); } }
4.1測试
使用Client来模拟client用户触发登录事件,代码例如以下: public class Client { //模拟client用户触发登录事件 @Test public void testLogin() { Transaction transaction = new Transaction(); Login login = new Login(transaction); login.login(); } }
4.2需求变更
上面是我们登录的这模块的一个简单的模拟实现,增加有一天因为公司的须要我们将对每一次用户的登录做日志记录,方便今后做安全审计。
当然软件系统的变化是无常。这样的如果也全然是有可能。
我们尽管能够通过科学的需求来尽量的避免这样的情况,可是全然避免是不可能的。好的。我们如今要在登录中增加日志处理的功能。
使用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(“验证username与password是否合法”); logger.logging(); transaction.commit(); } }
5 SpringAOP实现
我们依照2AOP开发步骤来使用Spring AOP来实现上面的样例。
5.1 关注点分解
通过分析我们能够得出在登录模块中,我们要实现的核心业务功能是登录。它是我们的核心业务功能。依照4.2AOP开发步骤横切关注点的划分通常满足下面几个条件。我们不难发现事务处理和日志处理并不属于核心业务功能,并且可能横跨其它的模块。所以事务处理和日志处理是横切关注点。详细实现例如以下:
5.2关注点实现
依照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("验证usernamepassword的合法性"); } }
5.3 关注点重组
实际上编织的过程。是由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>
5.4 測试
測试代码: @Test public void testLogin() { ApplicationContext context = new ClassPathXmlApplicationContext("cn/miao/login/aop/cglib/applicationContext.xml"); Login login = (Login) context.getBean("login"); login.login(); } 执行结果: 开启事务 验证usernamepassword的合法性 提交事务
5.5 需求变更
当然我们如果出现和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在处理相似切面上优势。
6 AspectJ实现
实现 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(); } } 执行结果: 开启事务 验证usernamepassword的合法性 提交事务
需求变更 添加切面Logger。 public aspect Logger { pointcut logging() : execution(* cn.miao.login.aop.aspectj.change.Login.login(..)); after() returning() : logging() { System.out.println("logging"); } } 測试client不须要做不论什么变动,在执行前须要又一次编译。执行结果例如以下: 开启事务 验证usernamepassword的合法性 logging 提交事务
7,AOP与OOP的差别
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]8 AOP的长处
8.1 代码可读性
代码集中易于理解 攻克了因为OOP 跨模块造成的代码混乱和代码分散。OOP同一时候实现几个关注点模糊了不同关注点的实现。使得关注点与事实上现之间的相应关系不明显。软件系统中的模块可能要同一时候兼顾几个方面的须要。举例来说:开发人员常常要同一时候考虑业务逻辑,性能。同步,日志和安全问题,兼顾各方面的须要导致相应关注点的实现元素同一时候出现。引起代码混乱,AOP能够把这样的需求作为独立的方面非常easy的实现,可读性好。在上面的应用样例中,我们非常easy发现OOP实现中。业务方法login()方法不仅有业务逻辑-登录,并且混杂了非常多事务处理和日志记录等功能代码。当这样的横切功能非常多时,代码就比較混乱了。
而在AOP实现(包含Spring AOP实现和AspectJ实现中)login方法中仅仅有登录的业务逻辑,事务处理和日志记录等横切功能都分散在对应的切面中,业务逻辑非常清晰,代码可读性自然非常好。
8.2 代码冗余度
在OOP中。因为横切关注点本来就涉及多个模块,其相关实现也就得遍布在这些模块里,如在一个使用了数据库的系统里。性能问题就会影响全部数据库的模块,这导致代码分散在各处。
而AOP模块化横切关注点,用最小的耦合处理每一个关注点。使得即使是横切关注点也是模块化的。这种实现产生的系统,其代码的冗余小。模块化的实现还得系统easy理解和维护。在AOP实现中横切面的代码都是公用的,横切功能的代码仅仅须要写一次。
而在OOP实现中,假设出现新的也业务逻辑,在新的业务逻辑中又存在横切功能(如日志等),则这些横切代码又要反复的写一遍。所以AOP实现比OOP实现代码冗余度低非常多。
8.3 代码扩展性
系统easy扩展 由于方面模块根本不知道横切关注点所以非常easy通过建立新的方面增加新的功能,另外,当往系统中增加新的模块时。已有的方面自己主动横切进来使系统易于扩展。设计师能够推迟为将来的需求作决定,由于他能够把这样的需求作为独立的方面非常easy地实现。
在上面的应用演示样例中,我们能够清楚的看到在需求变更的情况下。AOP表现出非常好的代码扩展性,仅仅须要增加新的切面。或者改动配置文件。而不用行OOP实现。要改动业务逻辑代码。AOP非常好的符合对扩展开放。对改动关闭的原则。
8.4 代码重用率
更好的代码重用性,AOP把每一个切面实现为独立的模块,模块之间是松散耦合的。
举例来说,上文中应用的样例中。Logger和Transaction切面都是作为独立的模块存在的。它们之间差点儿不存在耦合,松散耦合的实现通常意味着更好的代码重用性AOP 在使系统实现松散耦合这一点上比OOP做得更好 [2,16] 。
9 结论
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技术应用和研究