什么是AOP
面向切面的程序设计(Aspect-oriented programming ),简单来讲就是把程序重复的代码抽取出来,在需要执行的时候,使用动态代理的技术,在不修改源码的基础上,对我们的已有方法进行增强。
AOP作用及优势
作用:
- 在程序运行期间,不修改源码对已有方法进行增强。
优势:
- 减少重复代码
- 提高开发效率
- 维护方便
动态代理
回顾上一篇即可
Spring中的AOP
案例总结回顾
首先来整理一下整个案例的思路,最初我们在账户的业务层实现类中实现了对账户的增删改查,每个功能其实都是执行一条SQL语句,问题就是:这里事务被自动控制了。换言之,我们使用了connection对象的setAutoCommit(true) 此方式控制事务,如果我们每次都执行一条sql语句,没有问题,但是如果业务方法一次要执行多条sql语句,这种方式就无法实现功能了。例如在业务层实现类增加一个转账的方法:
1 |
|
这里就会出问题,转出账户钱少了,但是转入账户更新被打断并没有执行,所以转入账户钱没有增加,原因就是每执行一条SQL语句都去连接池获取一次,每次执行持久层方法都是独立事务,导致无法实现事务控制(不符合事务的一致性)。
为了解决上述问题,就加入了事务控制。让业务层来控制事务的提交和回滚。
1 | package com.gsynf.service.impl; |
1 | package com.gsynf.utils; |
1 | package com.gsynf.utils; |
通过对业务层改造,已经可以实现事务控制了,但是由于我们添加了事务控制,也产生了一个新的问题: 业务层方法变得臃肿了,里面充斥着很多重复代码。并且业务层方法和事务控制方法耦合了。 试想一下,如果我们此时提交,回滚,释放资源中任何一个方法名变更,都需要修改业务层的代码,况且这还只是一个业务层实现类,而实际的项目中这种业务层实现类可能有十几个甚至几十个。为了解决这个问题,引入了动态代理,详见上一篇关于动态代理的文章对具体案例的分析。当我们改造完成之后,业务层用于控制事务的重复代码就都可以删掉了。
细节与说明
在spring中,框架会根据目标类是否实现了接口来决定采用哪种动态代理的方式。
这里就是通过配置的方式,实现上述案例的功能。
术语
- Joinpoint(连接点):
- 所谓连接点是指那些被拦截到的点。在spring中,这些点指的是方法,因为spring只支持方法类型的连接点。
- 具体来讲,就是业务层接口中的方法,他们是业务方法和增强方法(事务控制)的连接点,可以使业务方法增加事务控制,形成完整的业务逻辑。
- Pointcut(切入点):
- 所谓切入点是指我们要对哪些Joinpoint进行拦截的定义。
- 具体来讲,业务层接口中每一个方法都是连接点,但是只有每一个被增强的方法才是切入点,不是所有方法都需要增强,如果有的方法不增强,它就不是切入点。
- 连接点是动态代理中的所有方法,切入点是其中被增强的方法。
- Advice(通知/增强):
- 所谓通知是指拦截到Joinpoint之后所要做的事情就是通知。
- 具体来讲,就是invoke方法拦截之后要做的事,这里就是TransactionManger,invoke拦截之后要进行的就是一系列的事务操作。
- 通知的类型:前置通知,后置通知,异常通知,最终通知,环绕通知。
- 具体来讲,找到明确调用业务层的method.invoke()方法,之前的就是前置通知,之后的就是后置通知,catch中的就是异常通知,finally中的就是最终通知。整个invoke方法在执行就是环绕通知。
- Introduction(引介):
- 引介是一种特殊的通知,在不修改类代码的前提下, Introduction可以在运行期为类动态地添加一些方法或Field。
- Target(目标对象):
- 代理的目标对象,也就是被代理对象。
- 具体来讲,就是
accountService
。
- Weaving(织入):
- 是指把增强应用到目标对象来创建新的代理对象的过程。
- 具体来讲,本来的对象
accountService
没有事务支持,为了事务支持加入了动态代理,返回了一个代理对象(IAccountService) Proxy.newProxyInstance
,在其中加入了事务控制,整个这个过程就是Weaving。 - spring采用动态代理织入,而AspectJ采用编译期织入和类装载期织入。
- Proxy(代理):
- 一个类被AOP织入增强后,就产生一个结果代理类。
- 具体来讲,就是创建出来的代理对象
(IAccountService) Proxy.newProxyInstance
。
- Aspect(切面):
- 是切入点和通知(引介)的结合。
要明确的事
a、开发阶段(我们做的)
- 编写核心业务代码(开发主线):大部分程序员来做,要求熟悉业务需求。
- 把公用代码抽取出来,制作成通知。(开发阶段最后再做):AOP编程人员来做。
- 在配置文件中,声明切入点与通知间的关系,即切面。:AOP编程人员来做。
b、运行阶段(Spring框架完成的)
- Spring框架监控切入点方法的执行。一旦监控到切入点方法被运行,使用代理机制,动态创建目标对象的代理对象,根据通知类别,在代理对象的对应位置,将通知对应的功能织入,完成完整的代码逻辑运行。
基于XML的AOP配置
案例:增加一个打印日志的通知,让其在切入点方法执行之前执行。
1 | package com.gsynf.service.impl; |
1 | package com.gsynf.utils; |
bean.xml:
1 |
|
添加一个Test:
1 | package com.gsynf.test; |
测试结果:
1 | Logger类中的printLog方法开始记录日志了……………… |
spring中基于XML的AOP配置步骤
详见bean.xml中的注释。
不同类型通知的配置
1 | <aop:config> |
环绕通知
环绕通知这里单独说一下
1 | <aop:around method="aroundPrintLog" pointcut-ref="pt1"></aop:around> |
在bean.xml中的配置和其他通知一样,在Logger类中加入aroundPrintLog方法
1 | public void aroundPrintLog() { |
此时运行,会发现当配置了环绕通知之后,aroundPrintLog方法中的语句打印了,而“执行了保存”没有打印,即切入点方法没有执行,而通知方法执行了。原因是什么呢?通过对比动态代理中的环绕通知代码,发现动态代理的环绕通知有明确的切入点方法,即method.invoke(),而此处的代码中没有。那么如何解决呢?Spring框架提供了一个接口:ProceedingJoinPoint.该接口有一个方法proceed(),该方法就相当于明确调用切入点方法。该方法可以作为环绕通知的方法参数,在程序执行时,Spring框架会为我们提供该接口的实现类供我们调用。
1 | public Object aroundPrintLog(ProceedingJoinPoint pjp) { |
此时运行,会发现结果如下:
1 | Logger类中的aroundPrintLog方法开始记录日志了………………前置 |
也就是说,在环绕通知中可以实现前置、后置、异常、最终这几种通知,其实,Spring中环绕通知另一种理解:它是spring框架为我们提供的一种可以在代码中手动控制增强方法何时执行的方式,换句话说,其他通知除了可以自己配,还可以在环绕通知中配,这就和动态代理中讲到的通知很类似了。
基于注解的AOP配置
普通注解
1 |
|
业务层实现类加注解@Service("accountService")
1 | ………………Logger.java……………… |
此时运行Test,会发现输出
1 | 前置通知:Logger类中的beforePrintLog方法开始记录日志了……………… |
这里最终通知和后置通知是反的,这里目前是无法更改的,所以在选用时比较推荐使用环绕方式配置,因为何时调用全由自己控制,所以不会有问题。
纯注解
1 |
|
注:转载文章请注明出处,谢谢~