Spring AOP 如何获取连接点的详细信息?

在 Spring AOP 中,获取连接点(Join Point)的详细信息是一个非常常见的需求,这能让我们的通知(Advice)逻辑变得更加智能和具体。Spring AOP 为此提供了两个核心接口:JoinPoint 和它的子接口 ProceedingJoinPoint

1. JoinPoint 接口

JoinPoint 接口几乎可以在所有类型的通知(@Before, @After, @AfterReturning, @AfterThrowing)中使用。它就像一个“信息包”,包含了关于被拦截方法的所有元数据。

如何使用?

只需在你的通知方法的参数中声明一个 JoinPoint 类型的参数即可,Spring AOP 会自动将它注入进来。

@Before("execution(* com.example.service..*.*(..))")
public void logBefore(JoinPoint joinPoint) {
    // 现在我们可以通过 joinPoint 对象获取各种信息
    // ...
}
JoinPoint 的常用方法

下面是 JoinPoint 提供的一些最实用的方法:

方法返回类型描述示例输出
getSignature()Signature获取方法签名。 这是最常用的方法,返回的 Signature 对象包含了方法的详细信息。String com.example.service.UserService.findUser(Long,String)
getSignature().**getName()**String获取方法名。findUser
getSignature().**getDeclaringTypeName()**String获取方法所在的类的完全限定名。com.example.service.UserService
getSignature().**getDeclaringType()**Class获取方法所在的类的 Class 对象。class com.example.service.UserService
getArgs()Object[]获取传入目标方法的参数数组。[101, "Alice"]
getTarget()Object获取目标对象。 即被代理的那个原始对象实例。UserService@4a5f4a74
getThis()Object获取代理对象本身。 即 Spring 创建的那个包装了目标对象的代理实例。UserService$$EnhancerBySpringCGLIB$$...
getKind()String获取连接点的类型,在 Spring AOP 中通常是 "method-execution""method-execution"
getSourceLocation()SourceLocation获取源文件位置信息(如文件名和行号),需要特定配置和编译选项。UserService.java:25
getStaticPart()JoinPoint.StaticPart获取连接点的静态部分信息,提供对签名的快速访问。-

2. ProceedingJoinPoint 接口

ProceedingJoinPointJoinPoint 的一个子接口,它只在 @Around (环绕通知) 中使用

它继承了 JoinPoint 的所有方法,因此你同样可以获取上述所有信息。但它增加了一个最重要的方法:proceed()

proceed() 方法
  • Object proceed(): 执行目标方法。这个方法是 @Around 通知继续执行调用链的关键。
  • Object proceed(Object[] args): 执行目标方法,并可以传入一个新的参数数组来修改原始参数

综合示例

让我们创建一个切面,来演示如何使用这些方法获取详细信息。

目标服务类:

package com.example.service;

import org.springframework.stereotype.Service;

@Service
public class ReportService {
    public String generateReport(Long reportId, String type, boolean isUrgent) {
        System.out.println("--- 核心业务: 正在生成报告... ---");
        return "Report-" + reportId;
    }
}

切面类:

package com.example.aop;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;
import java.util.Arrays;

@Aspect
@Component
public class DetailLoggingAspect {

    // 使用 @Before 通知演示 JoinPoint
    @Before("execution(* com.example.service.ReportService.generateReport(..))")
    public void logDetailsBefore(JoinPoint joinPoint) {
        System.out.println("========= @Before: 获取连接点详细信息 =========");

        // 1. 获取方法签名
        Signature signature = joinPoint.getSignature();
        System.out.println("方法签名 (Signature): " + signature);
        System.out.println("  - 方法名 (Name): " + signature.getName());
        System.out.println("  - 声明类型 (Declaring Type): " + signature.getDeclaringTypeName());
        
        // 2. 获取方法参数
        Object[] args = joinPoint.getArgs();
        System.out.println("方法参数 (Args): " + Arrays.toString(args));

        // 3. 获取目标对象和代理对象
        Object target = joinPoint.getTarget();
        Object proxy = joinPoint.getThis();
        System.out.println("目标对象 (Target): " + target.getClass().getName());
        System.out.println("代理对象 (This): " + proxy.getClass().getName());
        
        System.out.println("============================================\n");
    }
    
    // 使用 @Around 通知演示 ProceedingJoinPoint
    @Around("execution(* com.example.service.ReportService.generateReport(..))")
    public Object profileAndModifyArgs(ProceedingJoinPoint pjp) throws Throwable {
        System.out.println("========= @Around: 环绕通知开始 =========");
        
        // 同样可以获取所有 JoinPoint 的信息
        System.out.println("方法: " + pjp.getSignature().getName());
        System.out.println("原始参数: " + Arrays.toString(pjp.getArgs()));

        // **修改参数**
        Object[] originalArgs = pjp.getArgs();
        // 假设我们想强制将所有报告类型改为 "INTERNAL"
        Object[] newArgs = new Object[]{originalArgs[0], "INTERNAL", originalArgs[2]};
        System.out.println("修改后参数: " + Arrays.toString(newArgs));
        
        // 使用修改后的参数执行目标方法
        Object result = pjp.proceed(newArgs);

        System.out.println("目标方法返回值: " + result);
        System.out.println("========= @Around: 环绕通知结束 =========");
        
        return result;
    }
}

运行调用代码:

// 在某个测试类或 CommandLineRunner 中
reportService.generateReport(101L, "PUBLIC", true);

预期输出:

========= @Around: 环绕通知开始 =========
方法: generateReport
原始参数: [101, PUBLIC, true]
修改后参数: [101, INTERNAL, true]
========= @Before: 获取连接点详细信息 =========
方法签名 (Signature): String com.example.service.ReportService.generateReport(Long,String,boolean)
  - 方法名 (Name): generateReport
  - 声明类型 (Declaring Type): com.example.service.ReportService
方法参数 (Args): [101, INTERNAL, true]
目标对象 (Target): com.example.service.ReportService
代理对象 (This): com.example.service.ReportService$$EnhancerBySpringCGLIB$$...
============================================

--- 核心业务: 正在生成报告... ---
目标方法返回值: Report-101
========= @Around: 环绕通知结束 =========

总结

  • 要获取连接点信息,在通知方法的参数中声明 JoinPoint (或 @Around中的ProceedingJoinPoint)。
  • joinPoint.getSignature() 用于获取方法签名相关信息(方法名、类名)。
  • joinPoint.getArgs() 用于获取方法参数。
  • joinPoint.getTarget() 获取原始目标对象,joinPoint.getThis() 获取代理对象。
  • 只有 @Around 中的 ProceedingJoinPoint 才有 proceed() 方法,用于控制目标方法的执行,并可以修改参数。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

冰糖心书房

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值