Spring AOP
AOP实现可分为两类 按照AOP框架修改源代码的时机
- 静态AOP实现: AOP框架在编译阶段对程序进行修改,实现对目标类的增强,生成静态的AOP代理类,如AspectJ,这时class文件已经被改掉了,需要使用特定的编译器
- 动态AOP实现:AOP框架在运行阶段动态生成AOP代理(在内存中以JDK动态代理或者cglib动态地生成AOP代理类),以实现对目标的增强。以Spring AOP为代表
AOP的基本概念
AOP框架并不与特定的代码耦合,能出力程序执行中特定的切入点(Pointcut),而不与某个具体类讴歌,具有如下两个特征:
- 各步骤之间的良好隔离性
- 源代码无关性
一些术语 - 切面(Aspect):切面用于组织多个Advice, Advice放在切面中定义;
- 连接点(Joinpoint):程序执行过程中明确的点,如方法的调用,或者异常的抛出。在Spring AOP中,连接点总是方法的调用;
- 增强处理(Advice):AOP框架在特定的切入点执行的增强处理。处理有"around"、"before"和"after"等类型;
- 切入点(Pointcut):可以插入增强处理的连接点。当某个连接点满足指定要求,该连接点将被添加增强处理,该连接点也就变成了切入点。
如何使用表达式来定义切入点是AOP的核心,如:
pointcut xxxPointCut():execution(void H*.say*())
- 引入:将方法或字段添加到被处理的类中。
- 目标对象:被AOP框架进行增强处理的对象,也被称为被增强的对象。如果AOP框架采用的是动态AOP实现,那么该对象就是一个被代理的对象。
- AOP代理:AOP框架创建的对象,代理就是对目标对象的增强
- 织入(Weaving):将增强处理添加到目标对象中,并创建一个被增强的对象的过程就是织入,有两种实现方式,编译时增强和运行时增强。
以上是AOP代理的方法与目标对象的方法示意图
Spring中的AOP代理是由Spring的IoC容器负责生成、管理,其依赖关系也由IoC容器负责管理。故,AOP代理可以直接使用容器中的其他Bean实例作为目标,这种关系可由IoC容器的依赖注入提供,Spring默认使用Java动态代理来创建AOP代理,同时,Spring也可以使用cglib代理,在需要代理类而不是接口的时候,Spring会自动切换为使用cglib代理。但Spring推荐使用面向接口编程。
Spring目前仅支持将方法调用作为连接点(Joinpoint),如果需要对成员变量的访问和更新也作为增强处理的连接点,则可以考虑使用AspectJ。
AOP编程,程序员需要做以下三个部分:
- 定义普通业务组件;
- 定义切入点,一个切入点可能横切多个业务组件;
- 定义增强处理,增强处理就是在AOP框架为普通业务组件织入的处理动作
基于XML配置文件使用AOP编程
在Spring配置文件中,所有的切面、切入点和增强处理都必须定义在<aop:config…/>元素内部。<beans…/>元素下可以包含多个<aop:config…/>元素,一个<aop:config>可以包含pointcut、advisor和aspect元素,且这三个元素都必须按照此顺序来定义。
注意:使用<aop:config…/>方式进行配置时,可能与Spring的自动代理方式相冲突,如<aop:aspectj-autoproxy/>,建议要吗全部使用配置方式,要吗全部使用自动代理方式。
- 配置切面
定义切面使用<aop:aspect…/>元素,实质是讲一个已有的Spring Bean转换成前面bean,所以需要先定义一个普通的Spring Bean,通过在<aop:aspect…/>元素中使用ref属性来引用该bean即可转换成切面bean。
配置<aop:aspect…/>元素时可以指定如下三个属性:
- id:定义该切面的标识名;
- ref:用于将ref属性所引用的普通Bean转换为切面Bean;
- order:指定该切面Bean的优先级,order属性值越小,该切面对应的优先级越高。
<aop:config>
<!-- 将容器中的普通bean转换成切面bean -->
<aop:aspect id="afterAdviceAspect" ref="afterAdviceBean">
...
</aop:aspect>
</aop:config>
<!-- 定义一个普通Bean实例,该Bean实例将作为Aspect Bean -->
<bean id="afterAdviceBean" class="lee.AfterAdviceTest"/>
- 配置增强处理
- <aop:before…/>: 配置Before增强处理;
- <aop:after…/>: 配置After增强处理,不管目标方法成功完成还是异常终止,都会被织入;
- <aop:after-returning…/>: 配置AfterReturning增强处理,只有在目标方法成功完成之后才会被织入;
- <aop:after-throwing…/>: 配置AfterThrowing增强处理;
- <aop:around…/>: 配置Around增强处理,即可在执行目标方法之前织入,也可在执行目标方法之后织入增强动作,功能强大,可以决定目标方法在什么时候执行,如何执行,甚至可以完全阻止目标方法的执行、该增强处理方法第一个参数必须为ProceedingJoinPoint类型,通过调用ProceedingJoinPoint的proceed()方法才会执行目标方法–这是Around增强可以完全控制目标方法的执行时机、如何执行的关键;如果程序没有调用proceed方法,则目标方法不会被执行。;
上面这些元素都不支持子元素,但通常可指定如下属性:
- pointcut:该属性指定一个切入表达式,Spring将在匹配该表达式的连接点时织入该增强处理;
- pointcut-ref: 该属性指定一个已经存在的切入点名称,通常pointcut和pointcut-ref两个属性只需使用其中之一;
- method:指定一个方法名,指定将切面Bean的该方法转换为增强处理。
- throwing:该属性只对<after-throwing/>元素有效,用于指定一个形参名,AfterThrowing增强处理方法可通过该形参访问目标方法所抛出的异常;
- returning:该属性只对有效,<after-returning/>元素有效,用于指定一个形参名,AfterReturning增强处理方法可通过该形参访问目标方法的返回值;
public class FourAdviceTest {
public Object processTx(ProceedingJoinPoint jp) {
System.out.println("Around 增强:执行目标方法之前,模拟开始事务...");
Object[] args = jp.getArgs();
if (args != null && args.length > 0 && args[0].getClass() == String.class) {
args[0] = "【增加的前缀】" + args[0];
}
Object rvt = jp.proceed(args);
System.out.println("Around 增强:执行目标方法之后,模拟结束事务...");
if (rvt != null && rvt instanceof Integer) {
rvt = (Integer) rvt * (Integer) rvt;
}
return rvt;
}
public void authority(JoinPoint jp) {
System.out.println("*2*Before 增强:模拟执行权限检查");
// 返回被织入增强处理的目标方法
System.out.println("*2*Before 增强:被织入增强处理的目标方法为:"+jp.getSignature().getName());
// 访问执行目标方法的参数
System.out.println("*2*Before 增强:目标方法的参数为:"+Arrays.toString(jp.getArgs()));
// 访问被增强处理的目标对象
System.out.println("*2*Before 增强:被织入增强处理的目标对象为:"+jp.getTarget());
}
public void log(JoinPoint jp, Object rvt) {
System.out.println("AfterReturning增强:获取目标方法返回值");
System.out.println("AfterReturning增强:模拟记录日志功能...");
// 返回被织入增强处理的目标方法
System.out.println("AfterReturning增强:被织入增强处理的目标方法为:"+jp.getSignature().getName());
// 访问执行目标方法的参数
System.out.println("AfterReturning增强:目标方法的参数为:"+Arrays.toString(jp.getArgs()));
// 访问被增强处理的目标对象
System.out.println("AfterReturning增强:被织入增强处理的目标对象为:"+jp.getTarget());
}
public void release(JoinPoint jp) {
System.out.println("After增强:模拟方法结束后的释放资源...");
// 返回被织入增强处理的目标方法
System.out.println("After增强:被织入增强处理的目标方法为:"+jp.getSignature().getName());
// 访问执行目标方法的参数
System.out.println("After增强:目标方法的参数为:"+Arrays.toString(jp.getArgs()));
// 访问被增强处理的目标对象
System.out.println("After增强:被织入增强处理的目标对象为:"+jp.getTarget());
}
}
该FourAdviceTest.java几乎是一个POJO类,此外,下面定义一个简单的切面类
public class SecondAdviceTest{
// 定义Before增强处理
public void authority(String aa){
System.out.println("目标方法的参数为:"+aa);
System.out.println("*1*Before增强:模拟执行权限检查");
}
}
上面切面类的authority()方法里多了一个String的形参,应用试图通过该形参来访问目标方法的参数值,这需要在配置该切面Bean时使用args切入点指示符。
本示例的Spring配置文件如下:
<aop:config>
<!-- 将fourAdvicBean转换成切面Bean 指定优先级为2 -->
<aop:aspect id="fourAdviceAspect" ref="fourAdviceBean" order="2">
<!-- 定义一个After增强处理 以切面Bean中的release方法作为增强处理方法 -->
<aop:after pointcut="execution(* org.app.my.service.impl.*.*(..))" method="release"/>
<!-- 定义一个Before增强处理 以切面Bean中的authority方法作为增强处理方法 -->
<aop:before pointcut="execution(* org.app.my.service.impl.*.*(..))" method="authority"/>
<!-- 定义一个AfterReturning增强处理 以切面Bean中的log方法作为增强处理方法 -->
<aop:after-returning pointcut="execution(* org.app.my.service.impl.*.*(..))" method="log"
<!-- 定义一个Around增强处理 以切面Bean中的release方法作为增强处理方法 -->
<aop:around pointcut="execution(* org.app.my.service.impl.*.*(..))" method="processTx" returning="rvt"/>
</aop:aspect>
<!-- 将secondAdvicBean转换成切面Bean 指定优先级为1 该切面里的增强处理将被优先织入-->
<aop:aspect id="secondAdviceAspect" ref="secondAdviceBean" order="1">
<!-- 直接定义一个Before增强处理 以切面Bean中的authority方法作为增强处理方法 且该参数必须为String类型(由authority方法中声明的msg参数的类型决定)-->
<aop:before pointcut="execution(* org.app.my.service.impl.*.*(..)) and args(aa)" method="authority"/>
</aop:aspect>
</aop:config>
<bean id="fourAdviceBean" class="or.app.aspect.FourAdviceTest"/>
<bean id="secondAdviceBean" class="or.app.aspect.SecondAdviceTest"/>
需要注意的是Spring提供了<aop:pointcut>元素来定义切入点,若该元素作为<aop:config>的子元素定义时,表明该切入点是全局的,可被多个切面共享;反之,该元素作为<aop:aspect>的子元素定义时,表明该切入点只在该切面内有效;
基于注解的零配置方式
待续。。。