一、什么是AOP?
AOP:Aspect Oriented Programming的缩写,意为面向切面编程,通过预编译方式或运行期动态代理实现程序功能的统一维护的一种技术。主要功能是:日志记录,性能统计,安全控制,事务处理,异常处理等等。
实现方式:
- 预编译——AspectJ
- 运行期动态代理(JDK动态代理,CGlib动态代理)——SpringAOP、JbossAOP
SpringAOP默认使用标准的JavaSE动态代理作为AOP代理,这使得任何接口(或者接口集)都可以被代理;SpringAOP中也可以使用CGLib代理(如果一个业务对象并没有实现一个接口)
Sprin的AOP是纯Java实现,无需特殊的编译过程,不需要控制类加载器层次。目前只支持方法执行连接点(通知SpringBean的方法执行),Spring的AOP不是为了提供最完整的AOP实现(尽管他非常强大),而是侧重于提供一种AOP实现和SpringIOC容器之间的整合,用于帮助解决企业级应用中的常见问题。SpringAOP不会与AspectJ去竞争,也不会提供综合全面的AOP解决方案。
二、AOP相关概念
Aspect
(切面): Aspect 声明类似于 Java 中的类声明,在 Aspect 中会包含着一些 Pointcut 以及相应的 Advice。Joint point
(连接点):表示在程序中明确定义的点,典型的包括方法调用,对类成员的访问以及异常处理程序块的执行等等,它自身还可以嵌套其它 joint point。Pointcut
(切点):表示一组 joint point,这些 joint point 或是通过逻辑关系组合起来,或是通过通配、正则表达式等方式集中起来,它定义了相应的 Advice 将要发生的地方。
可以通过以下两种方式相匹配的方法:
- 名称匹配
- 正则表达式匹配
Advice
(增强):Advice 定义了在Pointcut
里面定义的程序点具体要做的操作,它通过 before、after 和 around 来区别是在每个 joint point 之前、之后还是代替执行的代码。Target
(目标对象):织入Advice
的目标对象.。Weaving
(织入):将Aspect
和其他对象连接起来, 并创建Advice
d object 的过程。织入一般发生在如下几个时机:
(1)编译时:当一个类文件被编译时进行织入,这需要特殊的编译器才可以做的到,例如AspectJ的织入编译器
(2)类加载时:使用特殊的ClassLoader在目标类被加载到程序之前增强类的字节代码
(3)运行时:切面在运行的某个时刻被织入,SpringAOP就是以这种方式织入切面的,原理应该是使用了JDK的动态代理技术
三、Advice 的类型
before advice
, 在 join point 前被执行的 advice. 虽然 before advice 是在 join point 前被执行, 但是它并不能够阻止 join point 的执行, 除非发生了异常(即我们在 before advice 代码中, 不能人为地决定是否继续执行 join point 中的代码)after return advice
, 在一个 join point 正常返回后执行的 adviceafter throwing advice
, 当一个 join point 抛出异常后执行的 adviceafter(final) advice
, 无论一个 join point 是正常退出还是发生了异常, 都会被执行的 advice.around advice
, 在 join point 前和 joint point 退出后都执行的 advice. 这个是最常用的 advice.introduction
,introduction可以为原有的对象增加新的属性和方法。
四、配置切面aspect
切面Aspect: 满足同时对多个模块增加功能(例如:日志记录,性能统计,安全控制,事务处理,异常处理等等),多个模块可以共用此功能,方便快捷的实现业务需求。
Spring所有的切面和通知器都必须放在一个<aop:config>中,可以包含多个<aop:config>元素,每一个<aop:config>可以包含pointcut,advisor和aspect元素,他们必须按照这个顺序进行声明。<aop:config>风格的配置大量使用了Spring的自动代理机制。
<bean id="moocAspect" class="com.imooc.aop.schema.advice.MoocAspect"></bean>
<bean id="aspectBiz" class=" com.imooc.aop.schema.advice.biz.AspectBiz"></bean>
<aop:config>
<!-- 配置切面aspect -->
<aop:aspect id="moocAspectAop" ref="moocAspect">
<!-- 配置切入点 pointcut **切入点执行以Biz结尾的所有类的方法(注意第一个*后边的空格不能少 ) -->
<aop:pointcut expression="execution(* com.imooc.aop.schema.advice.biz.*Biz.*(..))" id="moocPointCut" />
<!-- 配置通知 advice 在连接点执行之前的通知,不会阻止该连接点的执行 -->
<aop:before method="before" pointcut-ref="moocPointCut" />
<!-- 配置通知 advice 在连接点征程执行之后的通知(即使抛异常时也会执行此通知) -->
<aop:after method="after" pointcut-ref="moocPointCut" />
<!-- 配置通知 advice 在连接点征程执行之后的通知(抛异常时不会执行此通知) -->
<aop:after-returning method="afterReturning" pointcut-ref="moocPointCut" />
<!-- 配置通知 advice 在连接点征程执行时,抛异常时的通知 -->
<aop:after-throwing method="afterThrowing" pointcut-ref="moocPointCut" />
<!-- 配置通知 advice 可以实现在连接点运行前,运行后进行通知 通知方法的第一个参数必须是ProceedingJoinPoint类型 -->
<aop:around method="around" pointcut-ref="moocPointCut" />
<!-- 配置通知 advice 实现带参数的通知 可以实现在连接点运行前,运行后进行通知 通知方法的第一个参数必须是ProceedingJoinPoint类型 -->
<aop:around method="aroundInit" pointcut="execution(* com.imooc.aop.schema.advice.biz.AspectBiz.init(String,int) ) and args(bizName,times)" />
</aop:aspect>
</aop:config>
Around advice通知方法的第一个参数必须是ProceedingJoinPoint类型。
Introductions:允许一个切面声明一个实现指定接口的通知对象,并且提供了一个接口实现类来代表这些对象。
前面描述的几种增强(Advice)都是在目标方法范围内织入,而引介(Introduction)不同,直接在类级别上添加目标未实现的接口方法。
在Spring中可以通过扩展DelegatingIntroductionInterceptor类来实现引介增强类。
Advisors:advisor就像一个小的自包含的方面,只有一个advice,切面自身通过一个bean表示,并且必须实现某个advice接口,同时,advisor也可以很好的利用AspectJ的切入点表达式。
Spring通过配置文件中<aop:advisor>元素支持advisor,实际使用中,大多数情况下它会和transactional advice配合使用。
为了定义一个advisor的优先级以便让advice可以有序,可以使用order属性来定义advisor的顺序。