一、什么是 AOP
说到面向对象这个概念,大家都已经耳熟能详了,无论是Java,.Net,PHP等等都是基于面向对象的思想进行开发的语言或者说是平台. 那我们是否能够再思考,在面向对象这个基本思想的前提下再进一步的进行扩展和延伸呢,那么在这里就会提出AOP这个概念或者说是架构思想。
AOP:Aspect Oriented Programming,意为面向切面/局部的程序设计,它是面向对象的程序设计的一种延伸。其核心内容就是所谓的“横切关注点”。
二、AOP的作用
AOP利用一种称为“横切”的技术,剖解开封装的对象内部,并将那些影响了多个类的公共行为封装到一个可重用模块,并将其名为“Aspect”,即方面。所谓 “方面”,简单地说,就是将那些与业务无关,却为业务模块所共同调用的逻辑或责任封装起来,便于减少系统的重复代码,降低模块间的耦合度,并有利于未来的可操作性和可维护性。
例如,操作日志的记录,这种操作并不是业务逻辑调用的必须部分,但是,我们却往往不得在代码中显式进行调用,并承担由此带来的后果(例如,当日志记录的接口发生变化时,不得不对调用代码进行修改)。
这种问题,使用传统的OO方法是很难解决的。
AOP的目标,便是要将这些“横切关注点”与业务逻辑代码相分离,下图1.1就非常清晰的表明了这种横向结构:
图 1.1
三、AOP的应用场景
# Authentication 权限
# Caching 缓存
# Transactions 事务
# Error handling 错误处理
# Context passing 内容传递
# Lazy loading 懒加载
# Debugging 调试
# logging, tracing, profiling and monitoring 记录跟踪 优化 校准
# Performance optimization 性能优化
# Persistence 持久化
# Resource pooling 资源池
# Synchronization 同步
四、AOP的实现原理
对于应用软件系统来说,日志操作和权限控制都是非常常见的例子。
为了得到好的程序结构,通常使用OO的方法,将日志记录过程或者权限控制封装在一个类中,这个类包含了日志写入或权限验证的代码。
例如下面的代码段:
public class BusinessLogic { private readonly LogOperation log = new LogOperation();
public void SomeOperation() { //验证安全性;Securtity关注点; //执行前记录日志;Logging关注点; log.WriteLog(); DoSomething(); //保存逻辑运算后的数据;Persistence关注点; //执行结束记录日志;Logging关注点; log.WriteLog(); } }
public class LogOperation { public void WriteLog() { //日志写入操作 } }
这种做法在OO设计中,是常见的做法。但是,这种做法会带来以下一些问题:
1、不清晰的业务逻辑:从某种意义上来说,日志记录过程并不是业务逻辑执行的一部分,这个工作是属于系统的,但是,在这种情况下,我们不得不把系统的日志记录过程和业务逻辑执行过程掺杂在一起,造成代码的混乱。
2、代码浪费:使用这种方法,我们必须所有的业务逻辑代码中用LogOperation类,使得同样校验的代码充斥在整个软件中,显然不是很好的现象。
3、紧耦合:使用这种方法,我们必须在业务逻辑代码中显式引用LogOperation类,这就造成了业务逻辑代码同LogOperation类的紧耦合,这意味着,当LogOperation发生变化时,例如,当系统进化时,需要对WriteLog的方法进行改动时,可能会影响到所有引用代码。下面所有的问题都是因此而来。
4、不易扩展:在这里,我们只是在业务逻辑中添加了日志记录过程,哪一天,当我们需要添加额外的功能,例如日志记录功能的时候,我们不得不同样在所有的业务逻辑代码中添加这个功能。
5、不灵活:有的时候,由于某些特定的需要,我们需要暂时禁止,或者添加某项功能,采用传统的如上述的做法,我们不得不采用修改源代码的方式来实现。
为了解决这些问题,我们通常会采用诸如设计模式等方式,对上面的方案进行改进,这往往需要很高的技巧。利用AOP,我们可以很方便的解决上述问题。
下面用图1.2来说明一下AOP实现原理:
图 1.2
在一个复杂的系统,它由许多关注点组合实现,如业务逻辑、性能,数据存储、日志和调度信息、授权、安全、线程、错误检查等,还有开发过程中的关注点,如易懂、易维护、易追查、易扩展等,图1.3演示了由不同模块实现的一批关注点组成一个系统。
图 1.3
五、AOP的具体实现
在介绍Spring中Aop具体实现之前,我们先要了解下Aop的一些基本概念.
基本概念
要想了解AOP,首先得了解几个重要的基本概念:
- 切面(Aspect):一个关注点的模块化,这个关注点实现可能另外横切多个对象。比如说事务管理就是J2EE应用中一个很好的横切关注点例子。切面用Spring的Advisor或拦截器实现。
- 连接点(Joinpoint):程序执行过程中明确的点,如方法的调用或特定的异常被抛出。
- 通知(Advice):在特定的连接点,AOP框架执行的动作。各种类型的通知包括“around”、“before”和“throws”通知。通知类型将在下面讨论。许多AOP框架包括Spring都是以拦截器做通知模型,维护一个“围绕”连接点的拦截器链。
- 切入点(Pointcut):指定一个通知将被引发的一系列连接点的集合。AOP框架必须允许开发者指定切入点,例如,使用正则表达式。
- 目标对象(Target Object):包含连接点的对象,也被称作被通知或被代理对象。
- AOP代理(AOP Proxy):AOP框架创建的对象,包含通知。在Spring中,AOP代理可以是JDK动态代理或CGLIB代理。
- 编织(Weaving):组装方面来创建一个被通知对象。这可以在编译时完成(例如使用AspectJ编译器),也可以在运行时完成。Spring和其他纯Java AOP框架一样,在运行时完成织入。
各种通知(Advice)类型
为了符合各种流程处理,通知类型提供了5种,可以对目标方法进行全方位处理:
- Before advice:在某连接点(JoinPoint)之前执行的通知,但这个通知不能阻止连接点前的执行。ApplicationContext中在<aop:aspect>里面使用<aop:before>元素进行声明。
- After advice:当某连接点退出的时候执行的通知(不论是正常返回还是异常退出)。ApplicationContext中在<aop:aspect>里面使用<aop:after>元素进行声明。
- After returnadvice:在某连接点正常完成后执行的通知,不包括抛出异常的情况。ApplicationContext中在<aop:aspect>里面使用<aop:after-returning>元素进行声明。
- Around advice:包围一个连接点的通知,类似Web中Servlet规范中的Filter的doFilter方法。可以在方法的调用前后完成自定义的行为,也可以选择不执行。ApplicationContext中在<aop:aspect>里面使用<aop:around>元素进行声明。
- Afterthrowing advice:在方法抛出异常退出时执行的通知。ApplicationContext中在<aop:aspect>里面使用<aop:after-throwing>元素进行声明。
图1.4给出了几种通知类型更新清晰的表述
图 1.4
Aop的Annotation实现
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/beans/spring-aop-3.0.xsd">
<!-- 启动对@AspectJ注解的支持 –>
<aop:aspectj-autoproxy/>
<bean class="org.springframeword.aop.aspectj.annotation.AnnotationAwareAspectJAutoProxyCreator" />
</beans>
//定义一个切面 @Aspect public class BeforeAdviceTest { //匹配 com.wicresoft.app.service.impl 包下所有类的所有方法作为切入点 @Before("execution(* com.wicresoft.app.service.impl.*.*(..))") public void authorith(){ System.out.println("模拟进行权限检查。"); } }
上面使用@Before Annotation 时,直接指定了切入点表达式,指定匹配 com.wicresoft.app.service.impl包下所有类的所有方法执行作为切入点。
(3)定义 AfterReturning 增强处理。
// 定义一个切面 @Aspect public class AfterReturningAdviceTest { // 匹配 com.wicresoft.app.service.impl 包下所有类的所有方法作为切入点 @AfterReturning(returning="rvt", pointcut="execution(* com.wicresoft.app.service.impl.*.*(..))") public void log(Object rvt) { System.out.println("模拟目标方法返回值:" + rvt); System.out.println("模拟记录日志功能..."); } }
(4)定义 AfterThrowing 增强处理。
// 定义一个切面 @Aspect public class AfterThrowingAdviceTest { // 匹配 com.wicresoft.app.service.impl 包下所有类的所有方法作为切入点 @AfterThrowing(throwing="ex", pointcut="execution(* com.wicresoft.app.service.impl.*.*(..))") public void doRecoverActions(Throwable ex) { System.out.println("目标方法中抛出的异常:" + ex); System.out.println("模拟抛出异常后的增强处理..."); } }
(5)定义 After 增强处理
// 定义一个切面 @Aspect public class AfterAdviceTest { // 匹配 com.wicresoft.app.service.impl 包下所有类的所有方法作为切入点 @After("execution(* com.wicresoft.app.service.impl.*.*(..))") public void release() { System.out.println("模拟方法结束后的释放资源..."); } }
After 增强处理与AfterReturning 增强处理有点相似,但也有区别:
- AfterReturning 增强处理处理只有在目标方法成功完成后才会被织入。
- After 增强处理不管目标方法如何结束(保存成功完成和遇到异常中止两种情况),它都会被织入。
(6)Around 增强处理
Around 增强处理近似等于 Before 增强处理和 AfterReturning 增强处理的总和。它可改变执行目标方法的参数值,也可改变目标方法之后的返回值。
// 定义一个切面 @Aspect public class AroundAdviceTest { // 匹配 com.wicresoft.app.service.impl 包下所有类的所有方法作为切入点 @Around("execution(* com.wicresoft.app.service.impl.*.*(..))") public Object processTx(ProceedingJoinPoint jp) throws java.lang.Throwable { System.out.println("执行目标方法之前,模拟开始事物..."); // 执行目标方法,并保存目标方法执行后的返回值 Object rvt = jp.proceed(new String[]{"被改变的参数"}); System.out.println("执行目标方法之前,模拟结束事物..."); return rvt + "新增的内容"; } }
小结
AOP 广泛应用于处理一些具有横切性质的系统级服务,AOP 的出现是对 OOP 的良好补充,它使得开发者能用更优雅的方式处理具有横切性质的服务。当然这个需要结合实际项目,是否需要在架构中使用AOP的架构思想去使系统更加的完善,开发更加便捷,清晰。