AOP的10个实现细节

实验环境:
在这里插入图片描述

1.IOC容器中保存的是组件的代理对象:

1.1、IOC容器中保存的是组件的代理对象:

代理对象和目标对象唯一的关联就是接口类:
Calculator bean = ioc.getBean(Calculator.class);
所以用接口类型去获取代理对象
//所以以上代码只能获取接口对象,而不能去获取目标对象MyMathCalculator.java
//接口一般不加在容器中

1.2、cglib为没有接口的组件也可以创建代理对象:

//没有接口,参数就用本类类型,也可以创建代理对象
MyMathCalculator bean = ioc.getBean(MyMathCalculator.class);

2.切入点表达式写法(通配符):

  • 一个切入点表达式的写法:
@Before("execution(public int com.atguigu.impl.MyMathCalculator.*(int, int))")
public static void logStart(){
    System.out.println("[]" + "方法开始执行了,用的参数列表是:[]");
}

/**
 * 切入点表达式的写法:
 * 固定格式:execution(访问权限符 返回值类型 方法全类名(参数表))
 * 通配符:* 和 ..
 *  1) * :
 *          1.表示可以匹配一个或者多个字符,execution(public int com.atguigu.impl.MyMath*.*(int, int))
 *          2.匹配任意一个参数,execution(public int com.atguigu.impl.MyMathCalculator.*(int, *))
 *          3.在路径里,只能匹配一层路径。execution(public int com.atguigu.*.MyMathCalculator.*(int, int))
 *          4.权限位置不可以用,返回值类型可以用
 *  2) .. :
 *          1.匹配任意多个参数和任意类型参数:execution(public int com.atguigu.impl.MyMathCalculator.*(..))
 *          2.匹配任意多层路径:execution(public int com.atguigu..MyMathCalculator.*(int, int))
 *
 * 最模糊的:execution(* *.*(..))千万别这么写
 * 表达式里面还可以用:&&、||、!
 * 例:execution(public int com.atguigu..MyMathCalculator.*(int, int) && public int com.atguigu..MyMathCalculator.*(int, int))
 */

3.通知方法的执行顺序:

/**
 *   try{
 *      @Before
 *      method.invoke(obj, args);
 *      @AfterReturning
 *      }catch(e){
 *      @AfterThrowing
 *      }finally{
 *      @After
 *      }
 * 通知方法的执行顺序:
 *      正常执行:@Before(前置通知)——》@After(后置通知)——》@AfterReturning(正常返回)
 *      异常执行:@Before(前置通知)——》@After(后置通知)——》@AfterThrowing(方法异常)
 */
@Test
public void test02(){
    Calculator bean = ioc.getBean(Calculator.class);
    bean.div(1, 0);
}

4.JoinPoint获取目标方法的信息:

  • 只需要为通知方法的参数列表上写一个参数,joinPoint。
/**
 * 写入目标方法的详细信息:
 * 只需要为通知方法的参数列表上写一个参数,joinPoint
 *      JoinPoint joinPoint:封装了当前目标方法的详细信息
 */
@Before("execution(public int com.atguigu.impl.MyMathCalculator.*(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) +"]");
}

5.throwing/returning来指定哪个参数用来接收异常:

5.1使用returning来接受返回值:

/**
 * @param joinPoint
 * @param result
 * 使用spring来告诉这个result来接受返回值。
 */
@AfterReturning(value = "execution(public int com.atguigu.impl.MyMathCalculator.*(int, int))", returning = "result")
public static void logReturn(JoinPoint joinPoint, Object result){
    Object[] args = joinPoint.getArgs();//获取目标方法运行时使用的参数
    Signature signature = joinPoint.getSignature();//获取到方法签名
    String name = signature.getName();
    System.out.println("["+ name +"]" + "方法正常执行完成了,计算结果是:"+result);
}

//想在目标方法异常时执行
@AfterThrowing(value = "execution(public int com.atguigu.impl.MyMathCalculator.*(int, int))", throwing = "e")
public static void logException(JoinPoint joinPoint, Exception e){
    Object[] args = joinPoint.getArgs();//获取目标方法运行时使用的参数
    Signature signature = joinPoint.getSignature();//获取到方法签名
    String name = signature.getName();
    System.out.println("["+ name +"]"+ "出现了异常,异常原因是:"+"["+e.getCause()+"]");
}

5.2使用throwing来接受异常:

/**
 * @param joinPoint
 * @param result
 * 使用spring来告诉这个result来接受返回值。
 */
//想在目标方法异常时执行
@AfterThrowing(value = "execution(public int com.atguigu.impl.MyMathCalculator.*(int, int))", throwing = "e")
public static void logException(JoinPoint joinPoint, Exception e){
    Object[] args = joinPoint.getArgs();//获取目标方法运行时使用的参数
    Signature signature = joinPoint.getSignature();//获取到方法签名
    String name = signature.getName();
    System.out.println("["+ name +"]"+ "出现了异常,异常原因是:"+"["+e.getCause()+"]");
}

6.Spring对通知方法的约束(参数表一定正确):

  • spring对于通知方法的什么返回值类型不是太注意,但是参数列表一定正确。
//就像下面的异常写法,很容易出错,空指针异常太小了
@AfterThrowing(value = "execution(public int com.atguigu.impl.MyMathCalculator.*(int, int))", throwing = "e")
public static void logException(JoinPoint joinPoint, NoPointException e){
    Object[] args = joinPoint.getArgs();//获取目标方法运行时使用的参数
    Signature signature = joinPoint.getSignature();//获取到方法签名
    String name = signature.getName();
    System.out.println("["+ name +"]"+ "出现了异常,异常原因是:"+"["+e+"]");
}

7.抽取可重用的切入点表达式:

/**
 * 抽取可重用的切入点表达式
 *      1.随便声明一个没有实现返回值void的空方法
 *      2.给方法上标注@Pointcut注解
 */
@Pointcut("execution(public int com.atguigu.impl.MyMathCalculator.*(int, int))")
public void hahaMyPoint(){}

@Before("hahaMyPoint()")
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 = "hahaMyPoint()", returning = "result")
public static void logReturn(JoinPoint joinPoint, Object result){
    Object[] args = joinPoint.getArgs();//获取目标方法运行时使用的参数
    Signature signature = joinPoint.getSignature();//获取到方法签名
    String name = signature.getName();
    System.out.println("["+ name +"]" + "方法正常执行完成了,计算结果是:"+result);
}

@AfterThrowing(value = "hahaMyPoint()", throwing = "e")
public static void logException(JoinPoint joinPoint, Exception e){
    Object[] args = joinPoint.getArgs();//获取目标方法运行时使用的参数
    Signature signature = joinPoint.getSignature();//获取到方法签名
    String name = signature.getName();
    System.out.println("["+ name +"]"+ "出现了异常,异常原因是:"+"["+e+"]");
}

@After("hahaMyPoint()")
public static void logEnd(JoinPoint joinPoint){
    Object[] args = joinPoint.getArgs();//获取目标方法运行时使用的参数
    Signature signature = joinPoint.getSignature();//获取到方法签名
    String name = signature.getName();
    System.out.println("["+ name +"]" + "方法执行结束了。");
}

8.环绕通知@Around:

/**
 * 抽取可重用的切入点表达式
 *      1.随便声明一个没有实现返回值void的空方法
 *      2.给方法上标注@Pointcut注解
 */
@Pointcut("execution(public int com.atguigu.impl.MyMathCalculator.*(int, int))")
public void hahaMyPoint(){}

/**
 * @Around:是spring中最强大的通知方法,动态代理。
 *  try{
 *      @Before,前置通知
 *      method.invoke(obj, args);
 *      @AfterReturning,返回通知
 *      }catch(e){
 *      @AfterThrowing,异常通知
 *      }finally{
 *      @After,后置通知
 *      }
 *  四合一就是环绕通知;环绕通知里面有一个参数,ProceedingJoinPoint(进程通知)
 */
@Around("hahaMyPoint()")
public Object myArround(ProceedingJoinPoint pjp) throws Throwable {
    //获取目标方法运行时使用的参数
    Object[] args = pjp.getArgs();
    String name = pjp.getSignature().getName();

    Object proceed = null;
    try {
        System.out.println("[环绕前置通知],"+name+"方法开始");

        //利用反射调用目标方法,相当于method.invoke(obj, args);
        proceed = pjp.proceed(args);

        System.out.println("[环绕返回通知],"+name+"方法返回,返回值:"+ proceed);
    } catch (Exception e) {
        System.out.println("[环绕异常通知],异常信息:"+e);
    } finally {
        System.out.println("[环绕后置通知],"+name+"方法最终结束。");
    }

    //反射调用后的返回值一定返回出去
    return proceed;
}

9.环绕通知的执行顺序,抛出异常让其他通知感受到:

  • 如果环绕通知和普通通知一起运行:
  1. 正常情况下:
    在这里插入图片描述
  2. 异常情况下:
    在这里插入图片描述

注意:环绕通知是优于普通通知执行

  • 若两者混合使用,注意执行顺序为:
{
    try{
        【环绕前置】
        //【普通前置】
        环绕执行:目标方法执行
        【环绕返回】
    } catch(){
        【环绕异常】
    } fianlly{
        【环绕后置】  
    }
}
【普通后置】
【普通方法返回/方法异常返回】

环绕前置---普通前置---目标方法执行---环绕正常返回/出现异常---环绕后置---普通后置---普通返回或异常

注意:环绕通知会吃掉异常,为了让外界知道这个异常,一定要把这个异常跑出去。
@Around("hahaMyPoint()")
public Object myArround(ProceedingJoinPoint pjp) throws Throwable {
    //获取目标方法运行时使用的参数
    Object[] args = pjp.getArgs();
    String name = pjp.getSignature().getName();

    Object proceed = null;
    try {
        System.out.println("[环绕前置通知],"+name+"方法开始");

        //利用反射调用目标方法,相当于method.invoke(obj, args);
        proceed = pjp.proceed(args);

        System.out.println("[环绕返回通知],"+name+"方法返回,返回值:"+ proceed);
    } catch (Exception e) {
        System.out.println("[环绕异常通知],异常信息:"+e);
        //抛出异常
        throw new RuntimeException(e);
    } finally {
        System.out.println("[环绕后置通知],"+name+"方法最终结束。");
    }

    //反射调用后的返回值一定返回出去
    return proceed;
}

10.多切面运行顺序:

  • 两个切面类:
    在这里插入图片描述

  • 写了两个切面方法,这两个切面方法在测试类中运行时的结果是:
    在这里插入图片描述

  • 我们发现先开始的切面方法后结束,后开始的切面方法先结束。

  • 包裹的先后顺序导致的:
    在这里插入图片描述

  • 而这里面的包裹顺序看的是,切面类第一个首字母的大小,L大于V所以LogUtils.java切面类包裹在最外面。
    @Order(1)//使用order可以改变切面顺序,数值越小优先级越高。

  • 在LogUtils.java切面类中加入环绕通知:
    在这里插入图片描述

  • 所以环绕通知只是影响当前切面。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值