【Spring】核心概念:AOP 说明

新博客:https://blog.bigdataboy.cn/article/449.html

概念

AOP(面向切面编程)是一种编程思想,Python 的装饰器有这种思想,逆向中的 Hook 技术也有这种思想

不改变原来方法的情况下,增加功能,只是在不同框架,不同语言下,实现方式和写法不一样

实现思路: 获取原来函数 - 执行顺序 - 参数 & 返回值

  • 增强方法,那肯定会让原来的方法肯执行,所以得需要获取原来方法
  • 既然还要增强方法,那得考虑增强的逻辑什么时候执行吧,是在原来的函数执行前,还是执行后
  • 最后是参数返回值的问题,既然是增强,那我的参数返回值符合原来的函数的规则

Spring 的实现方式

pom.xml 添加依赖

<dependencies>
    <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.4</version>
    </dependency>
</dependencies>

mark

编写 增强方法类,这个类还是需要按照 Spring 的规则变成交给容器管理的 Bean

@Component  // 记得 Bean
public class Advice {

}

获取 原来的方法,Spring 使用了一种用表达式匹配的方式,后面详细介绍

@Component  // 记得 Bean
public class Advice {

    @Pointcut("execution(void cn.bigdataboy.dao.NameDao.getById())") // 切入点表达式 
    private void pt(){} // 可以理解为代表 原方法

}

编写 增强逻辑的方法,并设定执行的时机,有五种,后面详细介绍

@Component  // 记得 Bean
public class Advice {

    @Pointcut("execution(void cn.bigdataboy.dao.NameDao.getById())")
    private void pt(){} // 可以理解为代表 原方法

    @Before("pt()")  // 增强方法执行时机,有五种
    public void method(){
        // 增强的方法在这里
        System.out.println( "当前执行时间:" + System.currentTimeMillis());
    }
}

添加注解,告诉 Spring 这是 AOP 的类

@Component  // 记得 Bean
@Aspect  // 添加注解
public class Advice {

    @Pointcut("execution(void cn.bigdataboy.dao.NameDao.getById())")
    private void pt(){} // 可以理解为代表 原方法

    @Before("pt()")  // 增强方法执行时机,有五种,参数是原方法
    public void method(){
        // 增强的方法在这里
        System.out.println( "当前执行时间:" + System.currentTimeMillis());
    }
}

最后在配置类添加注解,开启 AOP 功能

@Configuration
@ComponentScan({"cn.bigdataboy.dao","cn.bigdataboy.aop"}) // 注意 Bean 不要漏了
@EnableAspectJAutoProxy  // 开启 aop 代理
public class SpringConfig {
}

mark

五种执行时机

@Before("")

在原函数执行前执行

@Before("pt()")  // 之前执行
public void methodBefore(){
    // 增强的方法在这里
    System.out.println( "@Before running ...");
}  

mark

@After("")

在原函数执行后执行,如果原函数报错也会执行

@After("pt()")  // 之后执行,如果原方法报错,也会执行
public void methodAfter(){
    // 增强的方法在这里
    System.out.println( "@After running ...");
}

mark

@Around("") 重点常用

环绕执行,所以它有点特殊,有一个参数,包含原方法和它的各种信息,相对于执行锚点,能控制在增强函数的什么位置执行

@Around("pt()")  // 环绕执行
public void methodAround(ProceedingJoinPoint pjp) throws Throwable {
    // 增强的方法在这里
    System.out.println( "当前执行时间 @Around:" + System.currentTimeMillis());
    pjp.proceed(); // 相对于传入锚点,执行位置
    System.out.println( "当前执行时间 @Around:" + System.currentTimeMillis());

    }

mark

@AfterReturning("")

原方法成功执行时触发,原方法报错,则不会执行

@AfterReturning("pt()")  // 在原方法正常执行后才会触发,也就说 入原方法报错,就不会触发了
public void methodAfterReturning(){
    // 增强的方法在这里
    System.out.println( "@AfterReturning running ...");
}

mark

@AfterThrowing("")

在原方法报错触发

@AfterThrowing("pt()")  // 在原方法报错触发
public void methodAfterThrowing(){
    // 增强的方法在这里
    System.out.println( "@AfterThrowing running ...");
}

mark

切入点表达式

切入点表达式,是 Spring 用来表达增强方法对哪些方法生效的式子

@Pointcut("execution(void cn.bigdataboy.dao.NameDao.getById())")
private void pt(){} // 可以理解为代表 原方法

格式:动作关键字(访问修饰符 返回值类型 包名.类/接口名.方法名(参数) 异常名) (有些是可以省略的)

  • 动作关键字:几乎都是 execution()
  • 访问修饰符: publicprivate ... 可以省略
  • 返回值:是什么写什么
  • 异常名:方法中定义的抛出异常,可以省略

切入点表达式 通配 快速描述

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

execution(* cn.bigdataboy.dao.*Dao.getBy*())

..: 多个连续任意符号,可以独立出现,常用于简化 包名 和 参数出现

execution(* cn..NameDao.getById(..))

+: 专用于匹配子类型

execution(* cn.bigdataboy.dao.NameDao+.*())

参数 & 返回值处理

既然是增强方法,那参数返回值就要处理,那同时,如果方法异常,那异常信息也要处理。都是使用形参获取的。

参数处理

@Before@After@AfterReturning@AfterThrowing 都是传入 JoinPoint 对象执行 getArgs() 获取原始方法参数数组

JoinPoint 对象包含大量信息

mark

mark

对于 @Around 是传入 ProceedingJoinPoint 对象执行 getArgs() 获取原始方法参数数组

mark

返回值处理

对于 返回值,只有@Around@AfterReturning 有,其他的不涉及返回值

@Around 方式设计参数和返回值,所以为了规范,返回值可以是 Object

@Around("pt()")  // 环绕执行
public Object methodAround(ProceedingJoinPoint pjp) throws Throwable {
    // 增强的方法在这里
    System.out.println("@Around running args: " + Arrays.toString(pjp.getArgs()));
    Object proceed = pjp.proceed(pjp.getArgs());// 相对于锚点,执行位置
    System.out.println("@Around running res: " + proceed);
    return proceed;
}

@AfterReturning 获取返回值有点特殊,需要指定注解的returning参数

@AfterReturning(value = "pt()", returning = "ret")  // 在原方法正常执行后才会触发,也就说 入原方法报错,就不会触发了
public void methodAfterReturning(Object ret) {
    // 增强的方法在这里
    System.out.println("@AfterReturning running res: " + ret);
}

mark

案例代码:https://pan.bigdataboy.cn/s/Lglh5

发表评论 / Comment

用心评论~