Spring AOP 专题

AOP 使用

xml 复制代码
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-aop</artifactId>
</dependency>

AOP 实现

java 复制代码
@Aspect
@Component
public class RecordTimeAspect {

    // 我要代理的路径类中的方法
    @Around("execution(* com.example.controller.*.*(..))")
    public Object recordTime(ProceedingJoinPoint pjp) throws Throwable {
        System.out.println("记录时间");
        long start = System.currentTimeMillis();
        // 目标方法执行
        Object proceed = pjp.proceed();

        // 获取方法签名
        pjp.getSignature();

        long end = System.currentTimeMillis();
        System.out.println("方法执行耗时:" + (end - start));
        return proceed;
    }

}

AOP 的原理

  • 连接点:表示JoinPoint 被AOP控制的方法,包含方法信息
  • 通知:Advice 表示要升级的功能,也就是AOP中升级的方法
  • 切入点:PointCut 表示要匹配哪些条件
  • 切面:Aspect,通知+切入点
  • 目标对象:Target 通知所应用的对象,也就是原始类

用Service 打印命名,可以看出是代理对象还是原类。

java 复制代码
helloService.getClass().getName() // 下面输出表示是代理的。
// com.example.service.HelloService$$EnhancerBySpringCGLIB$$2a189639

通知

根据通知方法执行时机的不同,将通知类型分为以下常见的五类:

  • @Around:环绕通知,此注解标注的通知方法在目标方法前、后都被执行
  • @Before:前置通知,此注解标注的通知方法在目标方法前被执行
  • @After :后置通知,此注解标注的通知方法在目标方法后被执行,无论是否有异常都会执行
  • @AfterReturning: 返回后通知,此注解标注的通知方法在目标方法后被执行,有异常不会执行
  • @AfterThrowing : 异常后通知,此注解标注的通知方法发生异常后执行

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

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

Java 复制代码
@Aspect
@Component
public class RecordTimeAspect {

    // 定义切点
    @Pointcut("execution(* com.example.service.*.*(..))")
    public void pt() {
    }

    @Before("pt()")
    public void before(JoinPoint joinPoint) {
        System.out.println("开始执行方法:" + joinPoint.getSignature().getName());
    }

    @Around("pt()")
    public Object recordTime(ProceedingJoinPoint pjp) throws Throwable {
        System.out.println("记录时间");
        long start = System.currentTimeMillis();
        // 目标方法执行
        Object proceed = pjp.proceed();

        // 获取方法签名
        pjp.getSignature();

        long end = System.currentTimeMillis();
        System.out.println("方法执行耗时:" + (end - start));
        return proceed;
    }

    @After("pt()")
    public void after(JoinPoint joinPoint) {
        System.out.println("结束执行方法:" + joinPoint.getSignature().getName());
    }

    @AfterReturning("pt()")
    public void afterReturning(JoinPoint joinPoint) {
        System.out.println("方法正常返回,返回结果为:" + joinPoint.getSignature().getName());
    }

    @AfterThrowing("pt()")
    public void afterThrowing(JoinPoint joinPoint) {
        System.out.println("方法异常返回,异常结果为:" + joinPoint.getSignature().getName());
    }

}

通知顺序

如果一个方法被多个切面,切到了是都会执行的。

默认顺序按照类名的字母排序。

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

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

大概流程是,环绕->方法运行之前,如果有多个切面,第二个切面执行,环绕->方法运行之前->方法运行结束或者方法报错 ->回到环绕...如此执行。

java 复制代码
@Aspect
@Component
@Order(3)
public class RecordTimeAspect {

切入点表达式

  • 介绍:描述切入点方法的一种表达式。
  • 作用:用来决定项目中的哪些方法需要加入通知

常见形式:

  • execution(......):根据方法的签名来匹配
  • @annotation(......) :根据注解匹配

注解方式

java 复制代码
@Before("@annotation(com.ruoyi.common.annotation.DataScope)")
public void doBefore(JoinPoint point) {

}

注解的第二种方式,可以直接获取注解

java 复制代码
@DataScope(deptAlias = "dddd")
public String hello() {

// 切面类中
@DataScope(deptAlias = "dddd")
public String hello() {

JoinPoint 类

java 复制代码
@Before("@annotation(dataScope)")
public void before(JoinPoint joinPoint, DataScope dataScope) {
    // 获取方法名
    String name = joinPoint.getSignature().getName();
    // 获取目标对象
    Object target = joinPoint.getTarget();
    // 获取目标类
    String name1 = target.getClass().getName();
    // 获取目标方法参数
    Object[] args = joinPoint.getArgs();
}

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

  • 对于 @Around 通知,获取连接点信息只能使用 ProceedingJoinPoint
  • 对于其它四种通知,获取连接点信息只能使用 JoinPoint ,它是 ProceedingJoinPoint 的父类型
相关推荐
张柏慈1 天前
Java性能优化:实战技巧与案例解析
java
天“码”行空1 天前
简化Lambda——方法引用
java·开发语言
带刺的坐椅1 天前
MCP 进化:让静态 Tool 进化为具备“上下文感知”的远程 Skills
java·ai·llm·agent·solon·mcp·tool-call·skills
java1234_小锋1 天前
Java线程之间是如何通信的?
java·开发语言
张张努力变强1 天前
C++ Date日期类的设计与实现全解析
java·开发语言·c++·算法
while(1){yan}1 天前
Spring事务
java·数据库·spring boot·后端·java-ee·mybatis
毕设源码-赖学姐1 天前
【开题答辩全过程】以 高校社团管理平台为例,包含答辩的问题和答案
java
余瑜鱼鱼鱼1 天前
线程和进程的区别和联系
java·开发语言·jvm
小唐同学爱学习1 天前
如何解决海量数据存储
java·数据库·spring boot·mysql
962464i1 天前
SBE(simple-binary-encoding)-Demo
java