Spring AOP面向切面编程

Spring AOP

AOP实现可分为两类 按照AOP框架修改源代码的时机

  1. 静态AOP实现: AOP框架在编译阶段对程序进行修改,实现对目标类的增强,生成静态的AOP代理类,如AspectJ,这时class文件已经被改掉了,需要使用特定的编译器
  2. 动态AOP实现:AOP框架在运行阶段动态生成AOP代理(在内存中以JDK动态代理或者cglib动态地生成AOP代理类),以实现对目标的增强。以Spring AOP为代表

AOP的基本概念

AOP框架并不与特定的代码耦合,能出力程序执行中特定的切入点(Pointcut),而不与某个具体类讴歌,具有如下两个特征:

  1. 各步骤之间的良好隔离性
  2. 源代码无关性
    一些术语
  3. 切面(Aspect):切面用于组织多个Advice, Advice放在切面中定义;
  4. 连接点(Joinpoint):程序执行过程中明确的点,如方法的调用,或者异常的抛出。在Spring AOP中,连接点总是方法的调用;
  5. 增强处理(Advice):AOP框架在特定的切入点执行的增强处理。处理有"around"、"before"和"after"等类型;
  6. 切入点(Pointcut):可以插入增强处理的连接点。当某个连接点满足指定要求,该连接点将被添加增强处理,该连接点也就变成了切入点。

如何使用表达式来定义切入点是AOP的核心,如:
pointcut xxxPointCut():execution(void H*.say*())

  1. 引入:将方法或字段添加到被处理的类中。
  2. 目标对象:被AOP框架进行增强处理的对象,也被称为被增强的对象。如果AOP框架采用的是动态AOP实现,那么该对象就是一个被代理的对象。
  3. AOP代理:AOP框架创建的对象,代理就是对目标对象的增强
  4. 织入(Weaving):将增强处理添加到目标对象中,并创建一个被增强的对象的过程就是织入,有两种实现方式,编译时增强和运行时增强。
Created with Raphaël 2.2.0 开始 AOP框架织入的增强处理 回调目标对象的方法 AOP框架织入的增强处理 结束

以上是AOP代理的方法与目标对象的方法示意图

Spring中的AOP代理是由Spring的IoC容器负责生成、管理,其依赖关系也由IoC容器负责管理。故,AOP代理可以直接使用容器中的其他Bean实例作为目标,这种关系可由IoC容器的依赖注入提供,Spring默认使用Java动态代理来创建AOP代理,同时,Spring也可以使用cglib代理,在需要代理类而不是接口的时候,Spring会自动切换为使用cglib代理。但Spring推荐使用面向接口编程。
Spring目前仅支持将方法调用作为连接点(Joinpoint),如果需要对成员变量的访问和更新也作为增强处理的连接点,则可以考虑使用AspectJ。

AOP编程,程序员需要做以下三个部分:

  1. 定义普通业务组件;
  2. 定义切入点,一个切入点可能横切多个业务组件;
  3. 定义增强处理,增强处理就是在AOP框架为普通业务组件织入的处理动作

基于XML配置文件使用AOP编程

在Spring配置文件中,所有的切面、切入点和增强处理都必须定义在<aop:config…/>元素内部。<beans…/>元素下可以包含多个<aop:config…/>元素,一个<aop:config>可以包含pointcut、advisor和aspect元素,且这三个元素都必须按照此顺序来定义。
注意:使用<aop:config…/>方式进行配置时,可能与Spring的自动代理方式相冲突,如<aop:aspectj-autoproxy/>,建议要吗全部使用配置方式,要吗全部使用自动代理方式。

  1. 配置切面
    定义切面使用<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"/>
  1. 配置增强处理
  • <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>的子元素定义时,表明该切入点只在该切面内有效;

基于注解的零配置方式

待续。。。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值