SSM Day4

Spring——AOP细节

5.1 切入点表达式
5.1.1 作用 通过表达式的方式定位一个或多个具体的连接点。

5.1.2 语法细节

切入点表达式的语法格式

execution([权限修饰符] [返回值类型] [简单类名/全类名] 方法名)

例子
表达式 execution(***** com.atguigu.spring.ArithmeticCalculator.*(…))
含义 ArithmeticCalculator 接口中声明的所有方法。
第一个“”代表任意修饰符及任意返回值。
第二个“”代表任意方法。
“…”匹配任意数量、任意类型的参数。
若目标类、接口与该切面类在同一个包中可以省略包名。
3)在 AspectJ 中,切入点表达式可以通过 “&&”、“||”、“!”等操作符结合起来。

5.1.3 切入点表达式应用到实际的切面类中

5.2 当前连接点细节
5.2.1 概述

切入点表达式通常都会是从宏观上定位一组方法,和具体某个通知的注解结合起来就能 够确定对应的连接点。那么就一个具体的连接点而言,我们可能会关心这个连接点的一些具 体信息,例如:当前连接点所在方法的方法名、当前传入的参数值等等。这些信息都封装在 JoinPoint 接口的实例对象中。

5.2.2 JoinPoint

5.3 通知
5.3.1 概述
在具体的连接点上要执行的操作。

一个切面可以包括一个或者多个通知。

通知所使用的注解的值往往是切入点表达式。

5.3.2 前置通知
前置通知:在方法执行之前执行的通知

使用@Before 注解

5.3.3 后置通知
后置通知:后置通知是在连接点完成之后执行的,即连接点返回结果或者抛出异常的时 候

使用@After 注解

5.3.4 返回通知
返回通知:无论连接点是正常返回还是抛出异常,后置通知都会执行。如果只想在连接 点返回的时候记录日志,应使用返回通知代替后置通知。

使用@AfterReturning 注解,在返回通知中访问连接点的返回值

①在返回通知中,只要将 returning 属性添加到@AfterReturning 注解中,就可以访问连接点的返回值。该属性的值即为用来传入返回值的参数名称

②必须在通知方法的签名中添加一个同名参数。在运行时 Spring AOP 会通过这个参数 传递返回值

③原始的切点表达式需要出现在 pointcut 属性中

5.3.5 异常通知
异常通知:只在连接点抛出异常时才执行异常通知

将 throwing 属性添加到@AfterThrowing 注解中,也可以访问连接点抛出的异常。

Throwable 是所有错误和异常类的顶级父类,所以在异常通知方法可以捕获到任何错误 和异常。

如果只对某种特殊的异常类型感兴趣,可以将参数声明为其他异常的参数类型。然后通 知就只在抛出这个类型及其子类的异常时才被执行
5.3.6 环绕通知
环绕通知是所有通知类型中功能最为强大的,能够全面地控制连接点,甚至可以控制是 否执行连接点。

对于环绕通知来说,连接点的参数类型必须是 ProceedingJoinPoint。它是 JoinPoint 的 子接口,允许控制何时执行,是否执行连接点。

在环绕通知中需要明确调用 ProceedingJoinPoint 的 proceed()方法来执行被代理的方法。 如果忘记这样做就会导致通知被执行了,但目标方法没有被执行。

注意:环绕通知的方法需要返回目标方法执行之后的结果,即调用 joinPoint.proceed(); 的返回值,否则会出现空指针异常

5.4 重用切入点定义
在编写 AspectJ 切面时,可以直接在通知注解中书写切入点表达式。但同一个切点表达 式可能会在多个通知中重复出现。

在 AspectJ 切面中,可以通过@Pointcut 注解将一个切入点声明成简单的方法。切入点 的方法体通常是空的,因为将切入点定义与应用程序逻辑混在一起是不合理的。

切入点方法的访问控制符同时也控制着这个切入点的可见性。如果切入点要在多个切面 中共用,最好将它们集中在一个公共的类中。在这种情况下,它们必须被声明为 public。在引入这个切入点时,必须将类名也包括在内。如果类没有与这个切面放在同一个包中, 还必须包含包名。

其他通知可以通过方法名称引入该切入点

5.4 指定切面的优先级

在同一个连接点上应用不止一个切面时,除非明确指定,否则它们的优先级是不确定的。

切面的优先级可以通过实现 Ordered 接口或利用@Order 注解指定。

实现 Ordered 接口,getOrder()方法的返回值越小,优先级越高。

若使用@Order 注解,序号出现在注解中

实例:

@Aspect
@Component
@Order(1)//使用Order改变切面顺序;数值越小优先级越高
public class LogUtils {
	
	/**
	 * 告诉Spring每个方法都什么时候运行;
	 * try{
	 * 		@Before
	 * 		method.invoke(obj,args);
	 * 		@AfterReturning
	 * }catch(e){
	 * 		@AfterThrowing
	 * }finally{
	 * 		@After
	 * }
	 * 	
	 * 5个通知注解
	 * @Before:在目标方法之前运行;  					 前置通知
	 * @After:在目标方法结束之后						后置通知
	 * @AfterReturning:在目标方法正常返回之后			返回通知
	 * @AfterThrowing:在目标方法抛出异常之后运行			异常通知
	 * @Around:环绕								环绕通知
	 * 
	 * 
	 * 抽取可重用的切入点表达式;
	 * 1、随便声明一个没有实现的返回void的空方法
	 * 2、给方法上标注@Pointcut注解
	 */
	@Pointcut("execution(public int com.atguigu.impl.MyMathCalculator.*(..))")
	public void hahaMyPoint(){};
	


	
	//想在执行目标方法之前运行;写切入点表达式
	//execution(访问权限符  返回值类型  方法签名)
	@Before("hahaMyPoint()")
	public static void logStart(JoinPoint joinPoint){
		//获取到目标方法运行是使用的参数
		Object[] args = joinPoint.getArgs();
		//获取到方法签名
		Signature signature = joinPoint.getSignature();
		String name = signature.getName();
		System.out.println("[LogUtils-前置]【"+name+"】方法开始执行,用的参数列表【"+Arrays.asList(args)+"】");
	}
	
	/**
	 * 切入点表达式的写法;
	 * 固定格式: execution(访问权限符  返回值类型  方法全类名(参数表))
	 *   
	 * 通配符:
	 * 		*:
	 * 			1)匹配一个或者多个字符:execution(public int com.atguigu.impl.MyMath*r.*(int, int))
	 * 			2)匹配任意一个参数:第一个是int类型,第二个参数任意类型;(匹配两个参数)
	 * 				execution(public int com.atguigu.impl.MyMath*.*(int, *))
	 * 			3)只能匹配一层路径
	 * 			4)权限位置*不能;权限位置不写就行;public【可选的】
	 * 		..:
	 * 			1)匹配任意多个参数,任意类型参数
	 * 			2)匹配任意多层路径:
	 * 				execution(public int com.atguigu..MyMath*.*(..));
	 * 
	 * 记住两种;
	 * 最精确的:execution(public int com.atguigu.impl.MyMathCalculator.add(int,int))
	 * 最模糊的:execution(* *.*(..)):千万别写;
	 * 
	 * &&”、“||”、“!
	 * 
	 * &&:我们要切入的位置满足这两个表达式
	 * 	MyMathCalculator.add(int,double)
	 * execution(public int com.atguigu..MyMath*.*(..))&&execution(* *.*(int,int))
	 * 
	 * 
	 * ||:满足任意一个表达式即可
	 * execution(public int com.atguigu..MyMath*.*(..))&&execution(* *.*(int,int))
	 * 
	 * !:只要不是这个位置都切入
	 * !execution(public int com.atguigu..MyMath*.*(..))
	 * 
	 * 告诉Spring这个result用来接收返回值:
	 * 	returning="result";
	 */
	//想在目标方法正常执行完成之后执行
	@AfterReturning(value="hahaMyPoint()",returning="result")
	public static void logReturn(JoinPoint joinPoint,Object result){
		Signature signature = joinPoint.getSignature();
		String name = signature.getName();
		System.out.println("[LogUtils-返回]【"+name+"】方法正常执行完成,计算结果是:"+result);
	}

	
	/**
	 * 细节四:我们可以在通知方法运行的时候,拿到目标方法的详细信息;
	 * 1)只需要为通知方法的参数列表上写一个参数:
	 * 		JoinPoint joinPoint:封装了当前目标方法的详细信息
	 * 2)、告诉Spring哪个参数是用来接收异常
	 * 		throwing="exception":告诉Spring哪个参数是用来接收异常
	 * 3)、Exception exception:指定通知方法可以接收哪些异常
	 * 
	 * ajax接受服务器数据
	 * 	$.post(url,function(abc){
	 * 		alert(abc)
	 * 	})
	 */
	//想在目标方法出现异常的时候执行
	@AfterThrowing(value="hahaMyPoint()",throwing="exception")
	public static void logException(JoinPoint joinPoint,Exception exception) {
		System.out.println("[LogUtils-异常]【"+joinPoint.getSignature().getName()+"】方法执行出现异常了,异常信息是【"+exception+"】:;这个异常已经通知测试小组进行排查");
	}

	//想在目标方法结束的时候执行
	/**
	 * Spring对通知方法的要求不严格;
	 * 唯一要求的就是方法的参数列表一定不能乱写?
	 * 	通知方法是Spring利用反射调用的,每次方法调用得确定这个方法的参数表的值;
	 * 	参数表上的每一个参数,Spring都得知道是什么?
	 * 	JoinPoint:认识
	 * 	不知道的参数一定告诉Spring这是什么?
	 * 
	 * @param joinPoint
	 */
	@After("hahaMyPoint()")
	private int logEnd(JoinPoint joinPoint) {
		System.out.println("[LogUtils-后置]【"+joinPoint.getSignature().getName()+"】方法最终结束了");
		return 0;
	}
	
	/**
	 * @throws Throwable 
	 * @Around:环绕	:是Spring中强大的通知;
	 * @Around:环绕:动态代理;
	 * 	try{
	 * 			//前置通知
	 * 			method.invoke(obj,args);
	 * 			//返回通知
	 * 	}catch(e){
	 * 			//异常通知
	 *  }finally{
	 * 			//后置通知
	 * 	}
	 * 		
	 * 	四合一通知就是环绕通知;
	 * 	环绕通知中有一个参数:	ProceedingJoinPoint pjp
	 * 
	 *环绕通知:是优先于普通通知执行,执行顺序;
	 *
	 *[普通前置]
	 *{
	 *	try{
	 *		环绕前置
	 *		环绕执行:目标方法执行
	 *		环绕返回
	 *	}catch(){
	 *		环绕出现异常
	 *	}finally{
	 *		环绕后置
	 *	}
	 *}
	 *
	 *
	 *[普通后置]
	 *[普通方法返回/方法异常]
	 *新的顺序:
	 *		(环绕前置---普通前置)----目标方法执行----环绕正常返回/出现异常-----环绕后置----普通后置---普通返回或者异常
	 *注意:
	 */
	
	@Around("hahaMyPoint()")
	public Object myAround(ProceedingJoinPoint pjp) throws Throwable{
		
		Object[] args = pjp.getArgs();
		String name = pjp.getSignature().getName();
		//args[0] = 100;
		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;
	}
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值