Spring中的AOP

AOP

1、AOP的概念和简介

1.1、AOP简介

  • AOP面向切面编程,一种编程范式,指导开发者如何组织程序结构
    • oop面向对象编程
  • 作用:在不惊动原始设计的基础上为其进行进行能增强
  • Spring理念:无入式侵入/无侵入式

1.2、AOP核心概念

image-20230401103031088

  • 连接点:程序执行过程中的任意位置,粒度为执行方法、抛出异常、设置变量等
    • 在SpringAOP中,理解为方法的执行
  • 切入点:匹配连接点的式子
    • 在SpringAOP中,一个切入点可以只描述一个具体方法,也可以匹配多个方法
      • 一个具体方法:com.anle.dao包下的BookDao接口中的无形参无返回值的sho方法
      • 匹配多个方法:所有的sho方法,所有的get开头的方法,所有Dao接口结尾的接口中的任意方法,所有带一个参数的方法
  • 通知:在切入点执行的操作,也就是共性功能
    • 在SpringAOP中,功能最终以方法的形式呈现
  • 通知类:定义通知的类
  • 切面:描述通知与切入点的对应关系

2、AOP入门案例

  • 第一步:导坐标

    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context</artifactId>
        <version>5.2.10.RELEASE</version>
    </dependency>
    
    <dependency>
        <groupId>org.aspectj</groupId>
        <artifactId>aspectjweaver</artifactId>
        <version>1.9.19</version>
    </dependency>
    
  • 第二步:定义dao接口与实现类

    /**
     * @Author: AnLe
     * @Date: 2023/04/01/10:58
     */
    public interface BookDao {
        public void sho();
        public void update();
    }
    
    /**
     * @Author: AnLe
     * @Date: 2023/04/01/10:59
     */
    
    @Repository("bookDao")
    public class BookDaoImpl implements BookDao {
        @Override
        public void sho() {
            System.out.println(System.currentTimeMillis());
            System.out.println("book,,,dao,,,,sho");
        }
    
        @Override
        public void update() {
            System.out.println("book...dao...update");
        }
    }
    
  • 第三步:定义通知类,制作通知

    public class MyAdvice {
    
      
         public void method(){
             System.out.println(System.currentTimeMillis());
          }
    }
    
  • 定义切入点

    public class MyAdvice {
    
        //定义切入点
        @Pointcut("execution(void com.anle.aop.dao.BookDao.update())")
         public void qrd(){
          }
          }
    
    • 说明:切入点定义依托一个不具有实际意义的方法进行,既无参数,无返回值,方法体无实际逻辑
  • 第五步:绑定切入点与通知关系,并指定通知到原始连接点的具体执行位置

/**
 * @Author: AnLe
 * @Date: 2023/04/01/16:21
 */
public class MyAdvice {

    //定义切入点
    @Pointcut("execution(void com.anle.aop.dao.BookDao.update())")
     public void qrd(){
      }

      @Before("qrd()")
     public void method(){
         System.out.println(System.currentTimeMillis());
      }
}
  • 第六步:定义通知类受spring容器管理,并定义当前类为切面类
/**
 * @Author: AnLe
 * @Date: 2023/04/01/16:21
 */
@Component
@Aspect
public class MyAdvice {

    //定义切入点
    @Pointcut("execution(void com.anle.aop.dao.BookDao.update())")
     public void qrd(){
      }

      @Before("qrd()")
     public void method(){
         System.out.println(System.currentTimeMillis());
      }
}
  • 第七步:开启spring对AOP注解驱动支持
/**
 * @Author: AnLe
 * @Date: 2023/04/01/11:02
 */

@Configuration
@ComponentScan("com.anle")
@EnableAspectJAutoProxy
public class SpringConfig {
}

3、AOP的工作流程

  1. Spring容器启动
  2. 读取所有切面配置中的切入点
  3. 初始化bean,判定bean对应的类中的方法是否匹配到任意切入点
    1. 初始化失败,创建对象
    2. 匹配成功,创建原始对象(目标对象)的代理对象
  4. 获取bean的执行方法
    1. 获取bean,调用方法并执行,完成操作
    2. 获取的bean是代理对象时,更具代理对象的运行模式运行原始方法与增强的能容,完成操作。

3.1、AOP核心概念

  • 目标对象:原始功能去掉共性功能对应的类产生的对象,这种对象是无法完成最终工作的
  • 代理:目标对象无法直接完成对应的工作,需要对其进行功能进行回填,通过原始对象的代理对象实现

4、AOP切入点表达式

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

4.1、语法格式

  • 切入点表达式标准格式:动作关键字(访问修饰符 返回值 包名.类/接口名.方法名(参数)异常名)

     @Pointcut("execution(void com.anle.aop.dao.BookDao.update())")
    
    • 动作关键字:描述切入点的动作,列如execution表示执行到指定切入点
    • 访问修饰符:public , private等,可以省略
    • 返回值
    • 类/接口名
    • 方法名
    • 参数
    • 异常名:方法定义中抛出异常,可以省略

4.2、通配符

  • 可以使用通配符描述切入点,可以快速描述

    • *:单个独立的任意字符,可以独立出现,也可以作为前缀或者后缀的匹配符出现

      @Pointcut("execution(public * com.anle.*.UserService.u*(*))")
      

      匹配com.anle包下的任意包中的UserService类接口中所有名称以u开头的方法

    • … :表示多个连接的任意符号,可以独立出现,常用于简化包名与参数的书写

    @Pointcut("execution(public User com..UserService.update(..))")
    

    ​ 匹配com包下的任意包中的UserService类接口中所有名称以update的方法

    • +:专用于匹配子类类型

      @Pointcut("execution(* *..Service+.*(..))")
      

4.3、书写技巧

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

5、AOP通知类型

5.1、AOP通知类型

  • AOP通知描述了抽取的共性功能,根据共性功能抽取的位置不同,最终运行代码时要加入到合理的位置
  • AOP通知共分为5种类型
    • 前置通知
    • 后置通知
    • 环绕通知(重点)
    • 返回后通知(了解)
    • 抛出异常后通知(了解)

5.2、类型介绍

  • 名称:@Before

  • 类型:方法注解

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

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

  • 范例:

     //前置通知
      @Before("pt()")
      public void before(){
          System.out.println("before....advice");
      }
    
  • 名称:@Around(重点,常用)

  • 类型:方法注解

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

  • 范例:

      //环绕通知
      @Around("pt()")
      public void around(ProceedingJoinPoint point) throws Throwable {
          System.out.println("before....advice");
          //表示对原始数据的操作
          Object proceed = point.proceed();
          System.out.println("after....advice");
      }
    
  • @Around注意事项

    • 环绕通知必须依靠形参ProceedingJoinPoint才能实习对原始方法的调用,进而实现原始方法调用前后同时增加通知
    • 通知中如果未使用ProceedingJoinPoint对原始方法进行调用将跳过原始方法的执行
    • 原始方法的调用可以不接受返回值,通知方法设置成void即可,如果接收返回值,必须设定为object类型
    • 原始方法的返回值如果是void类型,通知方法的返回值类型可以设置成void,也可以设置成object
    • 由于无法预知原始方法运行后台是否会抛出异常,因此环绕通知方法必须抛出 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(默认):切入点方法名,格式为类名.方法名()

6、AOP通知获取数据

6.1、获取参数

  • 获取切入点方法的参数

    • JoinPoint:适用于前置、后置、返回后、抛出异常后通知
    • ProceedJoinPoint:适用于环绕通知
  • JoinPoint对象描述了连接点方法的运行状态,可以获取到原始方法的调用参数

     @Before("pt()")
     public void before(JoinPoint jp){
         Object[] args = jp.getArgs();
         System.out.println(Arrays.toString(args));
         System.out.println("before.......");
     }
    
  • ProceedJoinPoint是JoinPoint的子类

    @Around("pt()")
    public Object around(ProceedingJoinPoint pjp) throws Throwable {
        Object[] args = pjp.getArgs();
        System.out.println(Arrays.toString(args));
        
        Object ref = pjp.proceed(args);
        return ref;
    }
    

6.2、获取返回值

  • 获取切入点的返回值

    • 返回后通知
    • 环绕通知
  • 抛出异常后通知可获取切入点方法中出现的异常信息,使用形参可以接收对应的异常对象

  @AfterReturning(value = "pt()",returning = "ret")
public void afterReturning(Object ret){
    System.out.println("afterReturning....advice");
}
  • 环绕通知可手工书写对原始方法的调用,得到的结果即为原始方法的返回值

    @Around("pt()")
    public Object around(ProceedingJoinPoint pjp) throws Throwable {
        Object ref = pjp.proceed(args);
        return ref;
    }
    

6.3、获取异常

  • 获取切入点方法运行异常信息

    • 抛出异常后通知
    • 环绕通知
  • 抛出异常后通知可以获取切入点方法中的异常信息,使用形参可以接收对应的异常对象

     @AfterThrowing(value = "pt()",throwing = "t")
    public void afterThrowing(Throwable t){
        System.out.println("afterThrowing....advice");
    }
    
  • 抛出异常后通知可以获取切入点方法中的异常信息,使用形参可以接收运行时抛出的异常对象

@Around("pt()")
public Object around(ProceedingJoinPoint pjp) {
    Object[] args = pjp.getArgs();
    System.out.println(Arrays.toString(args));

    Object ref = null;
    try {
        ref = pjp.proceed(args);
    } catch (Throwable e) {
        throw new RuntimeException(e);
    }
    return ref;
}

owable t){
System.out.println(“afterThrowing…advice”);
}




- 抛出异常后通知可以获取切入点方法中的异常信息,使用形参可以接收运行时抛出的异常对象

```Java
@Around("pt()")
public Object around(ProceedingJoinPoint pjp) {
  Object[] args = pjp.getArgs();
  System.out.println(Arrays.toString(args));

  Object ref = null;
  try {
      ref = pjp.proceed(args);
  } catch (Throwable e) {
      throw new RuntimeException(e);
  }
  return ref;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值