Spring学习——AOP的理解

1. 概述

AOP(Aspect Oriented Programming)面向切面编程,指在程序运行期间,将某段代码动态的切入到指定方法的指定位置(方法的开始、结束、异常等)进行运行的这种编程方式。其中,动态代理可以实现AOP,但动态代理的缺点:

  • 较为复杂,书写困难
  • 若目标对象没有实现任何接口, jdk默认的动态代理无法为其创建代理对象

通过Spring实现的AOP功能,其底层就是动态代理,其优点:

  • 简便的创建动态代理
  • 实现简单,而且没有强制要求目标对象必须实现接口

2. AOP的术语

在这里插入图片描述
注:切面类的通知方法须为public

3. AOP使用步骤

导包

commons-logging-1.1.3.jar
spring-aop-4.0.0.RELEASE.jar
spring-beans-4.0.0.RELEASE.jar
spring-context-4.0.0.RELEASE.jar
spring-core-4.0.0.RELEASE.jar
spring-expression-4.0.0.RELEASE.jar

Spring支持面向切面编程(AOP)的包:

基础版:spring-aspects-4.0.0.RELEASE.jar
加强版:(即使目标对象没有实现任何接口也能创建动态代理)
com.springsource.net.sf.cglib-2.2.0.jar
com.springsource.org.aopalliance-1.0.0.jar
com.springsource.org.aspectj.weaver-1.6.8.RELEASE.jar

写配置

  1. 将目标类和切面类(封装了通知方法(在目标方法执行前后执行的方法))加入到ioc容器中
  2. 告诉Spring哪个是切面类——使用@Aspect注解
  3. 告诉Spring切面类里的方法何时何地运行
  4. 开启基于注解的AOP模式
<!--  开启基于注解的AOP功能;aop名称空间-->
	<aop:aspectj-autoproxy></aop:aspectj-autoproxy>

Spring有5个通知注解
@Before——前置通知:在目标方法之前运行;
@After——后置通知:在目标方法结束之后
@AfterReturning——返回通知:在目标方法正常返回之后
@AfterThrowing——异常通知:在目标方法抛出异常之后运行
@Around——环绕通知

try{
     @Before
     method.invoke(obj,args);
     @AfterReturning
 }catch(e){
     @AfterThrowing
 }finally{
     @After
 }

于是有:

测试

@Aspect
@Component
public class LogUtils {
	
	@Before(value="execution(public int com.guigu.implement.MyCalculator.*(int, int))")
	public static void logStart(JoinPoint joinPoint){
		Object[] args = joinPoint.getArgs();
		Signature signature = joinPoint.getSignature();
		String name = signature.getName();
		
		System.out.println("【"+name+"】方法开始执行,用的参数列表【"+Arrays.asList(args)+"】");
	}
	
	@AfterReturning(value="execution(public int com.guigu.implement.MyCalculator.*(int, int))",returning="result")
	public static void logReturn(JoinPoint joinPoint, Object result){
		System.out.println("【"+joinPoint.getSignature().getName()+"】方法正常执行完成,计算结果是:"+result);
	}
	@AfterThrowing(value="execution(public int com.guigu.implement.MyCalculator.*(int, int))",throwing="exception")
	public static void logException(JoinPoint joinPoint, Exception exception){
		System.out.println("【"+joinPoint.getSignature().getName()+"】方法执行出现异常了,异常信息是:" + exception + ";这个异常已经通知测试小组进行排查");
	}
	@After(value="execution(public int com.guigu.implement.MyCalculator.*(int, int))")
	public static void logEnd(JoinPoint joinPoint) {
		System.out.println("【"+joinPoint.getSignature().getName()+"】方法最终结束了");
	}

}

细节一:

  • 从ioc容器中拿到目标对象,如果继承了接口,一定用其接口类型,不要用其本类;因为AOP的底层是动态代理,容器中保存的组件是他的代理对象——另一个实现了接口的对象
  • 从ioc容器中拿到目标对象,如果没有继承接口,那么容器中保存的组件本类类型的对象
  • 也可以通过id从ioc容器中拿到目标对象,其id默认目标类首字母小写

细节二:
切入点表达式
固定格式:
execution(访问权限符 返回值类型 方法全类名(参数表))

(1)*通配符

  • 匹配一个或者多个字符:execution(public int com.atguigu.impl.MyMath*r.*(int, int))
  • 匹配任意一个参数:第一个是int类型,第二个参数任意类型;例如:execution(public int com.atguigu.impl.MyMath*.*(int, *))(且匹配的参数数量为2)
  • 只能匹配一层路径
  • 权限位置不能写*;权限位置是可选的,因为也只能切public

..通配符

  • 匹配任意多个参数,任意类型参数
  • 匹配任意多层路径:execution(public int com.atguigu..MyMath*.*(..))

最精确的:
execution(public int com.atguigu.impl.MyMathCalculator.add(int,int))
最模糊的(不写):
execution(* *.*(..))

(2)逻辑表达式
&&——切入的位置同时满足这两个表达式:
execution(public int com.atguigu..MyMath*.*(..))&&execution(* *.*(int,int))

||——满足任意一个表达式即可、!——只要不是这个位置都切入

细节三:
通知方法的执行顺序;

try{
     @Before
     method.invoke(obj,args);
     @AfterReturning
 }catch(e){
     @AfterThrowing
 }finally{
     @After
 }

正常执行: @Before(前置通知)→@After(后置通知)→@AfterReturning(正常返回)

异常执行: @Before(前置通知)→@After(后置通知)→@AfterThrowing(方法异常)

细节四:
我们可以在通知方法运行的时候,拿到目标方法的详细信息;

  1. 只需要为通知方法的参数列表上写一个参数JoinPoint joinPoint——封装了当前目标方法的详细信息
  2. 告诉Spring哪个参数是用来接收异常的——使用@AfterReturningthrowing="exception"属性
  3. 告诉Spring个参数是用来接收返回值的——使用@AfterReturningreturning属性
  4. Exception exception——指定通知方法可以接收哪些类型的异常

细节五:
Spring对通知方法的要求不严格,唯一要求的就是方法的参数列表一定正确,因为通知方法是Spring利用反射调用的,每次方法调用须确定方法的参数列表;
参数表上的每一个参数,Spring都得知道是什么?
JoinPoint:认识
不知道的参数一定告诉Spring这是什么?
细节六:

  • 使用@Order(int)注解改变多个切面执行顺序,数值越小,优先级越高
  • 环绕通知只影响当前切面
    在这里插入图片描述

4. 环绕通知

  • 环绕通知是所有通知类型中功能最为强大的,能够全面地控制连接点,甚至可以控制是否执行连接点
  • 对于环绕通知来说,连接点的参数类型必须是ProceedingJoinPoint。它是 JoinPoint的子接口,允许控制何时执行,是否执行连接点
  • 在环绕通知中需要明确调用ProceedingJoinPointproceed()方法来执行被代理的方法。如果忘记这样做就会导致通知被执行了,但目标方法没有被执行
  • 环绕通知优先于普通通知执行
  • 注意:环绕通知的方法需要返回目标方法执行之后的结果,即调用 joinPoint.proceed();的返回值,否则会出现空指针异常
@Around("hahaMyPoint()")
	public Object myAround(ProceedingJoinPoint pjp) throws Throwable{
		
		Object[] args = pjp.getArgs();
		String name = pjp.getSignature().getName();
		Object proceed = null;
		
		try {
			//@Before
			System.out.println("【环绕前置通知】【"+name+"方法开始】");
			//就是利用反射调用目标方法,即method.invoke(obj,args)
			proceed = pjp.proceed(args);
			//@AfterReturing
			System.out.println("【环绕返回通知】【"+name+"方法返回,返回值"+proceed+"】");
		} catch (Exception e) {
			//@AfterThrowing
			System.out.println("【环绕异常通知】【"+name+"】方法出现异常,异常信息:"+e);
			//为了让外界能知道这个异常,这个一定要抛出
			throw new RuntimeException(e);
		} finally{
			//@After
			System.out.println("【环绕后置通知】【"+name+"】方法结束");
		}
		
		//反射调用后的返回值也须返回
		return proceed;
	}
[普通前置]
{
  try{
     环绕前置
     环绕执行:目标方法执行
     环绕返回
  }catch(){
     环绕出现异常
  }finally{
     环绕后置
  }
}
[普通后置]
[普通方法返回/方法异常]

5. 以XML方式配置Aspect

<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xmlns:context="http://www.springframework.org/schema/context"
	xmlns:aop="http://www.springframework.org/schema/aop"
	xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
		http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd
		http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.0.xsd">

	<!-- 基于注解的AOP步骤;
	1、将目标类和切面类都加入到ioc容器中。@Component
	2、告诉Spring哪个是切面类。@Aspect
	3、在切面类中使用五个通知注解来配置切面中的这些通知方法都何时何地运行
	4、开启基于注解的AOP功能
	 -->
	<!--  开启基于注解的AOP功能;aop名称空间-->
	
	<!--  基于配置的AOP-->
	<bean id="myMathCalculator" class="com.atguigu.impl.MyMathCalculator"></bean>
	<bean id="BValidateApsect" class="com.atguigu.utils.BValidateApsect"></bean>
	<bean id="logUtils" class="com.atguigu.utils.LogUtils"></bean>
	
	<!-- 需要AOP名称空间 -->
	<aop:config>
		<aop:pointcut expression="execution(* com.atguigu.impl.*.*(..))" id="globalPoint"/>
	
	
		<!-- 普通前置  ===== 目标方法  =====(环绕执行后置/返回)====s普通后置====普通返回    -->
		<!-- 指定切面:@Aspect -->
		<aop:aspect ref="logUtils" order="1">
			<!-- 配置哪个方法是前置通知;method指定方法名 
			logStart@Before("切入点表达式")
			-->
			<!-- 当前切面能用的 -->
			<aop:around method="myAround" pointcut-ref="mypoint"/>
			<aop:pointcut expression="execution(* com.atguigu.impl.*.*(..))" id="mypoint"/>
			<aop:before method="logStart" pointcut="execution(* com.atguigu.impl.*.*(..))"/>
			<aop:after-returning method="logReturn" pointcut-ref="mypoint" returning="result"/>
			<aop:after-throwing method="logException" pointcut-ref="mypoint" throwing="exception"/>
			<aop:after method="logEnd" pointcut-ref="mypoint"/>
		</aop:aspect>
	
		<aop:aspect ref="BValidateApsect" order="3">
			<aop:before method="logStart" pointcut-ref="globalPoint"/>
			<aop:after-returning method="logReturn" pointcut-ref="globalPoint" returning="result"/>
			<aop:after-throwing method="logException" pointcut-ref="globalPoint" throwing="exception"/>
			<aop:after method="logEnd" pointcut-ref="globalPoint"/>
		</aop:aspect>
		<!-- 在切面类中使用五个通知注解来配置切面中的这些通知方法都何时何地运行 -->
	</aop:config>

	<!--注解:快速方便
		配置:功能完善;重要的用配置,不重要的用注解;
	  -->
</beans>

对比:

  • 注解方式——快速方便
  • 配置方式——功能完善;所以重要的用配置,不重要的用注解;

. AOP的应用场景

  • AOP加日志保存到数据库
  • AOP做权限验证、安全检查
  • AOP做事务控制
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Spring AOPSpring框架中的一个重要模块,它提供了面向切面编程(AOP)的支持。AOP是一种编程思想,它可以在不改变原有代码的情况下,通过在程序运行时动态地将代码“织入”到现有代码中,从而实现对原有代码的增强。 Spring AOP提供了基于注解的AOP实现,使得开发者可以通过注解的方式来定义切面、切点和通知等相关内容,从而简化了AOP的使用。 下面是一个基于注解的AOP实现的例子: 1. 定义切面类 ```java @Aspect @Component public class LogAspect { @Pointcut("@annotation(Log)") public void logPointcut() {} @Before("logPointcut()") public void beforeLog(JoinPoint joinPoint) { // 前置通知 System.out.println("执行方法:" + joinPoint.getSignature().getName()); } @AfterReturning("logPointcut()") public void afterLog(JoinPoint joinPoint) { // 后置通知 System.out.println("方法执行完成:" + joinPoint.getSignature().getName()); } @AfterThrowing(pointcut = "logPointcut()", throwing = "ex") public void afterThrowingLog(JoinPoint joinPoint, Exception ex) { // 异常通知 System.out.println("方法执行异常:" + joinPoint.getSignature().getName() + ",异常信息:" + ex.getMessage()); } } ``` 2. 定义业务逻辑类 ```java @Service public class UserService { @Log public void addUser(User user) { // 添加用户 System.out.println("添加用户:" + user.getName()); } @Log public void deleteUser(String userId) { // 删除用户 System.out.println("删除用户:" + userId); throw new RuntimeException("删除用户异常"); } } ``` 3. 在配置文件中开启AOP ```xml <aop:aspectj-autoproxy/> <context:component-scan base-package="com.example"/> ``` 在这个例子中,我们定义了一个切面类LogAspect,其中通过@Aspect注解定义了一个切面,通过@Pointcut注解定义了一个切点,通过@Before、@AfterReturning和@AfterThrowing注解分别定义了前置通知、后置通知和异常通知。 在业务逻辑类中,我们通过@Log注解标注了需要增强的方法。 最后,在配置文件中,我们通过<aop:aspectj-autoproxy/>开启了AOP功能,并通过<context:component-scan>扫描了指定包下的所有组件。 这样,当我们调用UserService中的方法时,就会触发LogAspect中定义的通知,从而实现对原有代码的增强。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值