[豪の学习笔记] Spring框架学习碎碎念#4

Spring AOP

学习文献:

AOP概念

​ AOP,Aspect Oriented Programming,即面向切面编程,面向特定方法编程;AOP是OOP(面向对象编程)的一种延续,二者互补,并不对立

​ AOP的核心思想就是将横切关注点从核心业务逻辑中分离出来,形成一个个的切面

​ 动态代理是面向切面编程最主流的实现,而Spring AOP是Spring框架的高级技术,旨在管理bean对象的过程中,主要通过底层的动态代理机制对特定的方法进行编程

AOP关键术语

  • 横切关注点(cross-cutting-concerns):

    ​ 多个类或对象中的公共行为(如日志记录、事务管理、权限控制、接口限流、接口幂......)

  • 连接点(JoinPoint):[可以被AOP控制的方法(暗含方法执行时的相关信息)]

    ​ 连接点是方法调用或者方法执行时的某个特定时刻(如方法调用、异常抛出......)

  • 通知(Advice):[指哪些重复的逻辑,即共性功能,最终体现为一个方法]

    ​ 通知就是切面在某个连接点要执行的操作。通知有五种类型,分别是前置通知(Before)、后置通知(After)、返回通知(AfterReturning)、异常通知(AfterThrowing)和环绕通知(Around),前四种通知都是在目标方法的前后执行,环绕通知可以控制目标方法的执行过程

  • 切入点(Pointcut):[匹配连接点的条件,通知仅会在切入点方法执行时被应用]

    ​ 一个切入点是一个表达式,它用来匹配哪些连接点需要被切面所增强。切点可以通过注解、正则表达式、逻辑运算等方式来定义,如:

    execution(* com.magicshushu.service..*(..))
    

    匹配com.magicshushu.service包及其子包下的类或接口

  • 切面(Aspect):[描述通知与切入点的对应关系(通知+切入点)]

    ​ 对横切关注点进行封装的类,一个切面是一个类,切面可以定义多个通知,用来实现具体的功能

  • 织入(Weaving):

    ​ 织入是将切面和目标对象连接起来的过程,也就是将通知应用到切点匹配的连接点上。常见的织入时机有两种,分别是编译期织入(Compile-Time Weaving)和运行期织入(Runtime Weaving)

AOP常见的通知类型

  • @Around:[环绕通知,此注解标注的通知方法在目标方法前后都被执行]

    ​ 编程式控制目标对象的方法调用。环绕通知是所有通知类型中可操作范围最大的一种,它可以直接拿到目标对象及要执行的方法,所以环绕通知可以任意地在目标对象的方法调用前后操作,甚至不调用目标对象的方法

    ​ @Around环绕通知需要自己调用ProceedingJoinPoint.proceed()来让原始方法执行,其他通知不需要考虑目标方法执行

    ​ @Around环绕通知方法的返回值,必须指定为Object,来接受原始方法的返回值

    @Around("execution(* com.magicshushu.service.impl.DeptServiceImpl.*(..))")
    public Object around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable{
        log.info("around before ... ");
        Object result = proceedingJoinPoint.proceed();
        log.info("around after ... ");
        return result;
    }
    
  • @Before:[前置通知]

    ​ 此注解标注的通知方法在目标方法执行前被执行

    @Before("execution(* com.magicshushu.service.impl.DeptServiceImpl.*(..))")
    public void before(){
        log.info("before ... ");
    }
    
  • @After:[后置通知]

    ​ 此注解标注的通知方法在目标方法后被执行,无论是否有异常都会执行

    @After("execution(* com.magicshushu.service.impl.DeptServiceImpl.*(..))")
    public void after(){
        log.info("after ... ");
    }
    
  • @AfterReturning:[返回后通知]

    ​ 此注解标注的通知方法在目标方法后被执行,有异常不会执行

    @AfterReturning("execution(* com.magicshushu.service.impl.DeptServiceImpl.*(..))")
    public void afterReturning(){
        log.info("afterReturning ... ");
    }
    
  • @AfterThrowing:[异常后通知,此注解标注的通知方法发生异常后执行]

    ​ 目标对象的方法运行中抛出/触发异常后触发(PS:AfterReturning与AfterThrowing二者互斥,如果方法调用成功则无异常,有返回值;如果方法抛出异常,则不会有返回值

    @AfterThrowing("execution(* com.magicshushu.service.impl.DeptServiceImpl.*(..))")
    public void afterThrowing(){
        log.info("afterThrowing ... ");
    }
    

通知执行顺序

​ 当有多个切面的切入点都匹配到了目标方法,目标方法运行时,多个通知方法都会被执行

不同切面类中,默认按照切面类的类名字母排序:

  • 目标方法前的通知方法:字母排名靠前的先执行
  • 目标方法后的通知方法:字母排名靠前的后执行

用@Order(数字)加在切面类上来控制顺序

  • 目标方法前的通知方法:数字小的先执行
  • 目标方法后的通知方法:数字小的后执行

切入点表达式

​ 切入点表达式:描述切入点方法的一种表达式

​ 作用:主要用来决定项目中的哪些方法需要加入通知

execution

​ execution主要根据方法的返回值、包名、类名、方法名、方法参数等信息来匹配,语法为:

execution(访问修饰符 返回值 包名.类名.方法名(方法参数) throws 异常)

​ 访问修饰符:可省略

​ 包名.类名:可省略

​ throws 异常:可省略(注意是方法上声明抛出的异常,不是实际抛出的异常)

可以使用通配符描述切入点

  • * :单个独立的任意符号,可以通配任意返回值、包名、类名、方法名、任意类型的一个参数,也可以通配包、类、方法名的一部分

    execution(* com.*.service.*.update*(*))
    
  • .. :多个连续的任意符号,可以通配任意层级的包,或任意类型、任意个数的参数

    execution(* com.magicshushu..DeptService.*(..))
    

开发中根据业务需要,可以使用 && 、|| 、! 来组合比较复杂的切入点表达式

@annotation

​ @annotation(...):根据指定的注解匹配特定的方法

@Before("@annotation(com.magicshushu.anno.Log)")
public void before(){
    log.info("before...");
}
抽取切入点表达式
@Pointcut("execution(* com.magicshushu.service.impl.DeptServiceImpl.*(..))")
public void magic(){}

@Before("magic()")
public void before(){
    log.info("before ... ");
}

连接点

​ 在Spring中用JoinPoint抽象了连接点,用它可以获得方法执行时的相关信息,如目标类名、方法名、方法参数等

​ 对于@Around通知,获取连接点信息只能使用 ProceedingJoinPoint

​ 对于其他四种通知,获取连接点信息只能使用 JoinPoint,它是ProceedingJoinPoint的父类型

@Around("execution(* com.magicshushu.service.DeptService.*(..))")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable{
    // 获取目标类名
    String className = joinPoint.getTarget().getClass().getName();
    // 获取目标方法签名
    Signature signature = joinPoint.getSignature();
    // 获取目标方法名
    String methodName = joinPoint.getSignature().getName();
    // 获取目标方法运行参数
    Object[] args = joinPoint.getArgs();
    // 执行原始方法,获取返回值(环绕通知)
    Object res = joinPoint.proceed();
    
    return res;
}
@Before("execution(* com.magicshushu.service.DeptService.*(..))")
public void before(JoinPoint joinPoint){
    // 获取目标类名
    String className = joinPoint.getTarget().getClass().getName();
    // 获取目标方法签名
    Signature signature = joinPoint.getSignature();
    // 获取目标方法名
    String methodName = joinPoint.getSignature().getName();
    // 获取目标方法运行参数
    Object[] args = joinPoint.getArgs();
}
@Around("pt()")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable{
    log.info("around before ...");
    // 1.获取目标对象的类名
    String className = joinPoint.getTarget().getClass().getName();
    log.info("目标对象的类名:{}", className);
    // 2.获取目标方法的方法名
    String methodName = joinPoint.getSignature().getName();
    log.info("目标方法的方法名:{}", methodName);
    // 3.获取目标方法运行时传入的参数
    Object[] args = joinPoint.getArgs();
    log.info("目标方法运行时传入的参数:{}", Arrays.toString(args));
    // 4.放行目标方法执行
    Object result = joinPoint.proceed();
    // 5.获取目标方法运行的返回值
    log.info("目标方法的返回值:{}", result);
    
    log.info("around after ...");
    return result;
}

AOP解决了什么问题

​ OOP不能很好地处理一些分散在多个类或对象中的公共行为(如日志记录、事务管理、权限控制、接口限流、接口幂......),这些行为通常被称为横切关注点。如果我们在每个类或对象中都重复实现这些行为,则会导致代码冗余

​ AOP可以将横切关注点从核心业务逻辑中分离出来,实现关注点的分离

AOP的应用场景

  • 日志记录:自定义日志记录注解
  • 性能统计:利用AOP在目标方法的执行前后,统计方法的执行时间,便于优化和分析
  • 事务管理:@Transactional注解可以让Spring为我们进行事务管理,免去重复的事务管理逻辑;@Transactional注解就是基于AOP实现的
  • 权限控制:利用AOP在目标方法执行前判断用户是否具备所需要的权限,如果具备就执行目标方法,否则不执行
  • 接口限流:利用AOP在目标方法执行前通过具体的限流算法和实现对请求进行限流处理
  • 缓存管理:利用AOP在目标方法执行前后进行缓存的读取和更新
posted @ 2025-09-01 20:08  SchwarzShu  阅读(9)  评论(0)    收藏  举报