AOP学习(二)

工作流程

  1. Spring容器启动

  2. 读取所有切面配置中的切入点

  3. 初始化bean

    1.  匹配成功,创建原始对象(目标对象)的代理对象。
      1. 匹配成功说明需要对其进行增强
      2. 对哪个类做增强,这个类对应的对象就叫做目标对象
      3. 因为要对目标对象进行功能增强,而采用的技术是动态代理,所以会为其创建一个代理对象
      4. 最终运行的是代理对象的方法,在该方法中会对原始方法进行功能增强
    2. 匹配失败,创建对象
      1. 匹配失败说明不需要增强,直接调用原始对象的方法即可。
  4. 获取bean执行方法

    1. 获取的bean是原始对象时,调用方法并执行,完成操作
    2. 获取的bean是代理对象时,根据代理对象的运行模式运行原始方法与增强的内容,完成操作

核心概念

目标对象(Target)

原始功能去掉共性功能对应的类产生的对象,这种对象是无法直接完成最终工作的

代理(Proxy)

目标对象无法直接完成工作,需要对其进行功能回填,通过原始对象的代理对象实现

SpringAOP本质

代理模式

AOP切入点表达式

语法格式

概念

  • 切入点:要进行增强的方法
  • 切入点表达式:要进行增强的方法的描述方式

切入点的描述

其实有两种方式的,先来看下例子

  • 描述方式一:执行com.itheima.dao包下的BookDao接口中的无参数update方法
    • execution(void com.itheima.dao.BookDao.update())
  • 描述方式二:执行com.itheima.dao.impl包下的BookDaoImpl类中的无参数update方法
    • execution(void com.itheima.dao.impl.BookDaoImpl.update())

切入点表达式标准格式:

动作关键字(访问修饰符  返回值  包名.类/接口名.方法名(参数) 异常名) 

 execution(public User com.itheima.service.UserService.findById(int))

  • execution:动作关键字,描述切入点的行为动作,例如execution表示执行到指定切入点
  • public:访问修饰符,还可以是public,private等,可以省略
  • User:返回值,写返回值类型
  • com.itheima.service:包名,多级包使用点连接
  • UserService:类/接口名称
  • findById:方法名
  • int:参数,直接写参数的类型,多个类型用逗号隔开
  • 异常名:方法定义中抛出指定异常,可以省略

 通配符

  •  `*`:单个独立的任意符号,可以独立出现,也可以作为前缀或者后缀的匹配符出现
    • execution(public * com.itheima.*.UserService.find*(*))
    • 匹配com.itheima包下的任意包中的UserService类或接口中所有find开头的带有一个参数的方法
  • `..`:多个连续的任意符号,可以独立出现,常用于简化包名与参数的书写
    • execution(public User com..UserService.findById(..))
    • 匹配com包下的任意包中的UserService类或接口中所有名称为findById的方法
  • `+`:专用于匹配子类类型
    • execution(* *..*Service+.*(..))

书写技巧

  1. 所有代码按照标准规范开发,否则以下技巧全部失效
  2. 描述切入点通常描述接口,而不描述实现类
  3. 访问控制修饰符针对接口开发均采用public描述(可省略访问控制修饰符描述
  4. 返回值类型对于增删改类使用精准类型加速匹配,对于查询类使用\*通配快速描述
  5. 包名书写尽量不使用..匹配,效率过低,常用\*做单个包描述匹配,或精准匹配
  6. 接口名/类名书写名称与模块相关的采用\*匹配,例如UserService书写成\*Service,绑定业务层接口名
  7. 方法名书写以动词进行精准匹配,名词采用*匹配,例如getById书写成getBy*,selectAll书写成selectAll
  8. 参数规则较为复杂,根据业务方法灵活调整
  9. 通常不使用异常作为匹配规则

 AOP通知类型

类型介绍

AOP通知描述了抽取的共性功能,根据共性功能抽取的位置不同,最终运行代码时要将其加入到合理的位置。

共提供了5种通知类型:

  • 前置通知

    • 名称:@Before
    • 类型 :方法注解
    • 位置 :通知方法定义上方
    • 作用:设置当前通知方法与切入点之间的绑定关系,当前通知方法在原始切入点方法后运行
    • 范例:
    •  @Before("pt()")
       public void Before() {
           System.out.println("Before advice ...");
       }
      
    • 相关属性:value(默认):切入点方法名,格式为类名。方法名()
  • 后置通知

    • 名称:@After
    • 类型 :方法注解
    • 位置 :通知方法定义上方
    • 作用:设置当前通知方法与切入点之间的绑定关系,当前通知方法在原始切入点方法后运行
    • 范例:
    •  @After("pt()")
       public void after() {
           System.out.println("after advice ...");
       }
      
    • 相关属性:value(默认):切入点方法名,格式为类名。方法名()
  • 环绕通知(重点)

    •  名称:@Around

    • 类型:方法注解

    • 位置:通知方法定义上方

    • 作用:设置当前通知方法与切入点之间的绑定关系,当前通知方法在原始切入点方法前后运行 

    • 范例:

    • @Around("pt()")
      public Object around(ProceedingJoinPoint pjp) throws Throwable{
          System.out.println("around before advice ...");
          //表示对原始操作的调用
          Object ret =pjp.proceed();
          System.out.println("around after advice ...");
          return ret;
      }
    • 注意事项:

      1. 环绕通知必须依赖形参ProceedingJoinPoint才能实现对原始方法的调用,进而实现原始方法调用前后同时添加通知

      2. 通知中如果未使用ProceedingJoinPoint对原始方法进行调用将跳过原始方法的执行

      3. 对原始方法的调用可以不接收返回值,通知方法设置成void即可,如果接收返回值,最好设定为Object类型

      4. 原始方法的返回值如果是void类型,通知方法的返回值类型可以设置成void,也可以设置成Object

      5. 由于无法预知原始方法运行后是否会抛出异常,因此环绕通知方法必须要处理Throwable异常

  • 返回后通知(了解)

    • 名称:@AfterReturning
    • 类型 :方法注解
    • 位置 :通知方法定义上方
    • 作用:设置当前通知方法与切入点之间的绑定关系,当前通知方法在原始切入点方法后运行
    • 范例:
    • @AfterReturning("pt2()")
        public void AfterReturning() {
           System.out.println("AfterReturning advice ...");
       }
      
    • 相关属性:value(默认):切入点方法名,格式为类名。方法名()
  • 抛出异常后通知(了解)

    • 名称:@AfterThrowing
    • 类型 :方法注解
    • 位置 :通知方法定义上方
    • 作用:设置当前通知方法与切入点之间的绑定关系,当前通知方法在原始切入点方法后运行
    • 范例:
    • @AfterThrowing("pt2()")
        public void AfterThrowing() {
           System.out.println("AfterThrowing advice ...");
       }
      
    • 相关属性:value(默认):切入点方法名,格式为类名。方法名()

 为了更好的理解这几种通知类型,我们来看一张图

  • 前置通知,追加功能到方法执行前,类似于在代码1或者代码2添加内容
  • 后置通知,追加功能到方法执行后,不管方法执行的过程中有没有抛出异常都会执行,类似于在代码5添加内容
  • 返回后通知,追加功能到方法执行后,只有方法正常执行结束后才进行,类似于在代码3添加内容,如果方法执行抛出异常,返回后通知将不会被添加
  • 抛出异常后通知,追加功能到方法抛出异常后,只有方法执行出异常才进行,类似于在代码4添加内容,只有方法抛出异常后才会被添加
  • 环绕通知,环绕通知功能比较强大,它可以追加功能到方法执行的前后,这也是比较常用的方式,它可以实现其他四种通知类型的功能,具体是如何实现的,需要我们往下学习。

 AOP通知获取数据

获取数据

  • 获取切入点方法的参数
    • JoinPoint:适用于前置、后置、返回后、抛出异常后通知
    • JoinPoint对象描述了连接点方法的运行状态,可以获取原始方法的调用参数
    •    @Before("pt()")
          public void before(JoinPoint jp) 
              Object[] args = jp.getArgs();
              System.out.println(Arrays.toString(args));
              System.out.println("before advice ..." );
          }
    • ProceedingJoinPoint:适用于环绕通知
    • ProceedingJoinPoint是JoinPoint的子类
    • @Around("pt()")
      public Object around(ProceedingJoinPoint pjp)throws Throwable {
          Object[] args = pjp.getArgs();
          System.out.println(Arrays.toString(args));
          Object ret = pjp.proceed();
          return ret;
      }
      

获取返回值

  • 获取切入点方法返回值,前置和抛出异常后通知是没有返回值,后置通知可有可无,所以不做研究
    • 返回后通知
    • 抛出异常后通知获取切入点方法方法中出现的异常信息,使用形参可以接收对应的异常对象
    • @AfterReturning(value = "pt()",returning = "ret")
      public void afterReturning(Object ret) {
          System.out.println("afterReturning advice ..."+ret);
      }
    • 环绕通知
    • 环绕通知中可以手工书写对原始方法的调用,得到的结果即为原始方法的返回值
    •  @Around("pt()")
          public Object around(ProceedingJoinPoint pjp) throws Throwable{
              Object ret= pjp.proceed();
              return ret;
          }

获取异常

  • 获取切入点方法运行异常信息,前置和返回后通知是不会有,后置通知可有可无,所以不做研究
    • 抛出异常后通知
    • 抛出异常后通知获取切入点方法方法中出现的异常信息,使用形参可以接收对应的异常对象
    • @AfterThrowing(value = "pt()",throwing = "t")
      public void afterThrowing(Throwable t) {
          System.out.println("afterThrowing advice ..."+t);
      }
    • 环绕通知
    • 抛出异常后通知获取切入点方法运行的异常信息,使用形参可以接收运行时抛出的异常对象
    • @Around("pt()")
      public Object around(ProceedingJoinPoint pjp){
          Object ret = null;
          try{
              ret = pjp.proceed();
          }catch(Throwable throwable){
              t.printStackTrace();
          }
          return ret;
      }
  • 27
    点赞
  • 34
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值