Spring复习(六)之AOP细节

1 切入点表达式

1.1 作用

通过表达式的方式定位一个或多个具体的连接点。

1.2 语法细节

①切入点表达式的语法格式

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

1.3通配符

  • 1)*
    1. 匹配一个或者多个字符:

      execution(public int com.atguigu.impl.MyMath*r.*(int, int))
      //以MyMath开始以r结束的类
      
    2. 匹配任意一个参数:

      //第一个是int类型,第二个参数任意类型;(匹配两个参数)
       execution(public int com.atguigu.impl.MyMath*.*(int, *))
      
    3. 只能匹配一层路径

      //匹配 com.atguigu下任意包
      execution(public int com.atguigu.*.MyMathCalculator.*(..))
      
    4. 权限位置*不能写;权限位置不写就行-----只能切public【可选的】

  • 2)..
    1. 匹配任意多个参数,任意类型参数

      execution(public int com.atguigu.impl.MyMathCalculator.*(..))
      
    2. 匹配任意多层路径:

      //匹配 com.atguigu包下的任意多层路径下的MyMath什么类
      execution(public int com.atguigu..MyMath*.*(..));
      

记住两种;

  • 最精确的:execution(public int com.atguigu.impl.MyMathCalculator.add(int,int))
  • 最模糊的:execution(* *.*(..)):千万别写(权限修饰符不写 任意返回值 任意包类下 任意方法(任意参数));

1.4 && || !

&&:我们要切入的位置满足这两个表达式

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*.*(..))

1.5 切入点表达式重用

抽取可重用的切入点表达式;
1、随便声明一个没有实现的返回void的空方法
2、给方法上标注@Pointcut注解
3、 直接方法名就可以引用

	@Pointcut("execution(public int com.atguigu.impl.MyMathCalculator.*(..))")
	public void hahaMyPoint(){};

	@Before("hahaMyPoint()")
	public static void logStart(JoinPoint joinPoint){...}

2 通知方法的执行顺序

通知方法的执行顺序;

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

正常执行: @Before(前置通知)------->@After(后置通知)------->@AfterReturning(正常返回)
异常执行: @Before(前置通知)------->@After(后置通知)------->@AfterThrowing(方法异常)

3 当前连接点细节

概述

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

JoinPoint

在这里插入图片描述
在通知方法的参数加上JoinPoint joinPoint,通过 joinPoint获取目标方法的信息

 @Before("execution(public int com.atguigu.impl.MyMathCalculator.*(..))")
	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)+"】");
	}

通知方法的参数Spring是有严格要求的:JoinPoint joinPoint Spring是知道的
要在通知方法上添加其他参数,并用注解告诉Spring框架

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

  1. 只需要为通知方法的参数列表上写一个参数:
    JoinPoint joinPoint:封装了当前目标方法的详细信息

  2. 告诉Spring哪个参数是用来接收异常
    throwing="exception":用注解参数告诉Spring哪个参数是用来接收异常
    通知方法参数Exception exception:指定通知方法可以接收哪些异常
    类似于

     ajax接受服务器数据
    	 	$.post(url,function(abc){
    		alert(abc)
    	  	})
    
    @AfterThrowing(value="execution(public int com.atguigu.impl.MyMathCalculator.*(..))",throwing="exception")
    	public static void logException(JoinPoint joinPoint,Exception exception) {
    		System.out.println("【"+joinPoint.getSignature().getName()+"】方法执行出现异常了,异常信息是【"+exception+"】:;这个异常已经通知测试小组进行排查");
    	}
    

3.告诉Spring这个result用来接收目标方法返回值: 注解returning="result"

  //想在目标方法正常执行完成之后执行
	@AfterReturning(value="execution(public int com.atguigu..MyMath*.*(..))",returning="result")
	public static void logReturn(JoinPoint joinPoint,Object result){
		Signature signature = joinPoint.getSignature();
		String name = signature.getName();
		System.out.println("【"+name+"】方法正常执行完成,计算结果是:"+result);
	}

关键

Spring对通知方法的要求不严格;
唯一要求的就是方法的参数列表一定不能乱写?

  • 通知方法是Spring利用反射调用的,每次方法调用得确定这个方法的参数表的值;
  • 参数表上的每一个参数,Spring都得知道是什么?
    • JoinPoint:认识
    • 不知道的参数一定告诉Spring这是什么?用注解(比如返回值,异常)

4 环绕通知

  • 环绕通知是所有通知类型中功能最为强大的,能够全面地控制连接点,甚至可以控制是否执行连接点。
  • 对于环绕通知来说,连接点的参数类型必须是ProceedingJoinPoint。它是 JoinPoint的子接口,允许控制何时执行,是否执行连接点。
  • 在环绕通知中需要明确调用ProceedingJoinPoint的proceed()方法来执行被代理的方法。如果忘记这样做就会导致通知被执行了,但目标方法没有被执行。
  • 注意:环绕通知的方法需要返回目标方法执行之后的结果,即调用 joinPoint.proceed();的返回值,否则会出现空指针异常。
@Around("execution(public int tju.impl.Calculatorimpl.*(int, int))")
    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{
	//环绕前置通知
	method.invoke(obj,args);
	 //环绕返回通知
	 }catch(e){
	//环绕异常通知
	}finally{
	//环绕后置通知
	}
  • 环绕通知和其他通知的顺序
    • 环绕通知:是优先于普通通知执行,执行顺序;
[普通前置]
 {
 	try{
 		环绕前置
 		环绕执行:目标方法执行
 		环绕返回
 	}catch(){
 		环绕出现异常
 	}finally{
 		环绕后置
 	}
 }
  [普通后置]
  [普通方法返回/方法异常]

新的顺序:
(环绕前置—普通前置)----目标方法执行----环绕正常返回/出现异常-----环绕后置----普通后置—普通返回或者异常

5 指定切面的优先级

  • 在同一个连接点上应用不止一个切面时,除非明确指定,否则它们的优先级是不确定的。
  • 切面的优先级可以通过实现Ordered接口或利用@Order注解指定。
  • 实现Ordered接口,getOrder()方法的返回值越小,优先级越高。
  • 若使用@Order注解,序号出现在注解中,序号值越小,优先级越高。
  • 在这里插入图片描述
    当LogUtils切面优先级高于VaAspect切面的时候

在这里插入图片描述

当Logutils切面类加环绕的时候
注意坏绕只影响当前切面
在这里插入图片描述

6 AOP应用

1)AOP加日志保存到数据库;
2)AOP做权限验证;
3)AOP做安全检查;
4)AOP做事务控制;

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值