Spring框架 AOP

AOP(Aspect Oriented Programming)

原理

正常程序执行顺序都为纵向执行流程 ,在某一个步骤的前后,做一个前置通知
和后置通知,这个过程称为切面编程。
即:在原有的纵向执行流程中添加一个横切面

特点

①高扩展性:对原先步骤流程无影响
②原有功能释放了部分逻辑,让职责更加明确

常用概念

①原有功能:切点
②前置/后置通知:在切点之前/之后执行的功能,before/after advice
③若切点执行过程出现异常,会触发异常通知:throws advice
④所有功能总称叫切面
⑤把切面嵌入到原有功能的过程叫织入

依赖的jar包

①四个核心包
②aop和aspects
③tx
④commons-logging
⑤aspectjweaver(https://mvnrepository.com/artifact/org.aspectj/aspectjweaver)
和aopalliance(https://mvnrepository.com/artifact/aopalliance/aopalliance/1.0)
Ps:添加命名空间:xmlns:aop和xsi:schemalocation

实现过程(Spring提供2种方式)

第一种:基于Schema-based

配置文件主要构件
将前置/后置通知使用添加到sping容器中
使用<aop:config>进行具体设置:
子元素<aop:pointcut id="" experssion="">为切点的定位
子元素<aop:advisor advice-ref=“前置/后置通知bean id” pointcut-ref=“切点bean id”>
Ps:由于配置文件中添加前/后置等通知时并无明显区别,即无法分辨是什么通知,因此通知类需继承接口,并实现其中方法,使得Spring可以判断出不同的通知

关于expression表达式为:
execution(* 包名.类名.方法名())
若有参数:execution(* 包名.类名.方法名(参数类型,参数类型)) and args(参数别名,参数别名),类型和别名相对应
`*`为通配符,会匹配任意方法名,任意类名,任意一级包名,如:com.*.project.*()
第一个`*`表示声明通配符
若匹配任意类型参数:execution(* 包名.类名.方法名(…))

①前/后置通知
前置/后置通知实现
类需实现MethodBeforeAdvice(前置)或AfterReturningAdvice(后置)接口
再实现接口中的方法,即为前置/后置方法
前置方法的参数
第一个为切点方法,即反射所得Method
第二个为切点方法参数列表
第三个为切点方法所在类的对象
后置通知增加了一个arg0:切点方法返回值

<!-- 前置通知 -->   
<bean id="beforeAdvice" class="com.mfqh.BeforeAdvice"></bean>
<!-- 后置通知 -->
<bean id="afterAdvice" class="com.mfqh.AfterAdvicce"></bean>
<!--切点-->
<bean id="tAService" class="com.mfqh.service.TestAdviceService"></bean>
<!-- 切面配置-->
<aop:config>
  	<!-- 配置切点 -->
  	<aop:pointcut expression="* com.mfqh.service.TestAdviceService.demo()" id="demoCut"/>
  	<!-- 声明前置通知 -->
  	<aop:advisor advice-ref="beforeAdvice" pointcut-ref="demoCut"/>
  	<!-- 声明后置通知 -->
  	<aop:advisor advice-ref="afterAdvice" pointcut-ref="demoCut"/>
</aop:config>

测试结果:
在这里插入图片描述
②环绕通知
把前置通知和后置通知放在一个方法中
配置文件,在<aop:config>中:
<aop:advisor advice-ref=“环绕类bean id” pointcut-ref=""/>
方法,实现MethodInterceptor(拦截器)接口:
在方法中依次进行前置处理、调用切点方法(arg0.proceed()),后置处理

<!-- 环绕通知 -->
 <bean id="interAdvice" class="com.mfqh.advice.InterceptorAdvice"></bean>
 <!-- 切面配置-->
<aop:config>
	<aop:pointcut expression="execution(* com.mfqh.service.*.demo1(..))" id="demo1Cut"/>
  	<aop:advisor advice-ref="interAdvice" pointcut-ref="demo1Cut"/>
</aop:config>

测试结果:
在这里插入图片描述
③异常通知
配置文件,在<aop:config>中:
<aop:advisor advice-ref=“处理异常类bean id” pointcut-ref=""/>
处理异常方法,继承ThrowsAdvice:
第一个方法,afterThrowing(Exception e)
第二个方法,afterThrowing(Method m,Object[] args,Object target, Exception e)
Ps:若两个方法都定义,执行后面的;
若在方法中将异常进行捕获,则不会进行异常通知
Tips:aop一般用于service中,添加声明式事务后,若出现异常就会自动回滚,
则service不能进行捕获异常,即aop方法中并不会捕获异常

<!-- 异常通知 -->
<bean id="throwAdvice" class="com.mfqh.advice.ThrowAdvice"></bean>
 <!-- 切面配置-->
<aop:config>
  	<aop:pointcut expression="execution(* com.*.service.*.demo2(..))" id="demo2Cut"/>
  	<aop:advisor advice-ref="throwAdvice" pointcut-ref="demo2Cut"/>
 </aop:config>

测试结果(使用一个最简单的异常,在方法中定义5/0):
在这里插入图片描述
Ps:此处需要在调用demo3()时try catch捕获异常,不然会报错

第二种:基于AspectJ

配置文件主要构件
使用<aop:config>的子元素<aop:aspect ref=“类 bean id”>内部进行配置
子元素<aop:pointcut id="" experssion="">为切点的定位
子元素<aop:before method="" pointcut-ref=""/>为切点前置方法
子元素<aop:after>及<aop:after-returning>同before
区别:出异常时after依旧会执行,after-returning不执行,两者及<aop:afterthrowing>执行顺序与配置文件声明顺序有关
Ps:由于在配置文件中不同的通知所使用的属性也不同,Spring可以直接分辨出不同的通知,因此通知类不需要继承特定的接口,编写较为灵活

①前/后置通知
前置/后置通知并不需要实现接口
参数获取:
Ⅰ、不能使用execution(* 包名.类名.方法名()),必须表明参数类型及名称
Ⅱ、在<aop:before>、<aop:after>等元素中添加属性:
arg-names=“参数别名,参数别名”,此处必须包含所有参数
Ⅲ、并且所定义的通知方法形参名必须和参数别名相同

<!-- 通知所在方法类 -->
<bean id="allAdvice" class="com.mfqh.advice.AllAdvice"></bean>
<!-- 切面配置 -->
<aop:config>
  	<aop:aspect ref="allAdvice">
   		<aop:pointcut expression="execution(* com.mfqh.service.TestAdviceService.demo3(String,int)) and args(name1,num1)" id="demo3Cut"/>
   		<aop:before method="beforeAdvice" pointcut-ref="demo3Cut" arg-names="name1,num1"/>
   		<aop:after method="afterAdvice" pointcut-ref="demo3Cut" arg-names="name1,num1"/>
  		</aop:aspect>
</aop:config>

测试结果:
在这里插入图片描述

②环绕通知
方法返回值为Object,参数为ProceedingJoinPoint p,通过p.proceed()调用切点方法

<!-- 通知所在方法类 -->
<bean id="allAdvice" class="com.mfqh.advice.AllAdvice"></bean>
<!-- 切面配置 --> 
<aop:config>
     	<aop:aspect ref="allAdvice">
      		<aop:pointcut expression="execution(* *.*.*.TestAdviceService.demo4())" id="demo4Cut"/>
      		<aop:around method="arroundProfiling" pointcut-ref="demo4Cut"/>
     	</aop:aspect>
</aop:config>

测试结果:
在这里插入图片描述

③异常通知
在<aop:config><aop:aspect ref=“处理异常类bean id”>中:
子元素<aop:after-throwing method=“异常处理方法名” pointcut-ref=“切点bean id”/>
参数获取:
若异常处理方法有参数Exception e,则需添加属性throwing=“e”(必须和参数名一致)
但反过来添加该属性方法时可以不设置参数(未声明)

<!-- 切面配置 --> 
<aop:config>
     	<aop:aspect ref="allAdvice">
      		<aop:pointcut expression="execution(* *.*.*.TestAdviceService.demo5())" id="demo5Cut"/>
     	 	<aop:after-throwing method="dealException" pointcut-ref="demo5Cut" throwing="e"/>
     	</aop:aspect>
</aop:config>

测试结果:
在这里插入图片描述

发布了82 篇原创文章 · 获赞 1 · 访问量 1451

猜你喜欢

转载自blog.csdn.net/qq_41891805/article/details/105173486