在 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 接口
ProceedingJoinPoint 是 JoinPoint 的一个子接口,它只在 @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()方法,用于控制目标方法的执行,并可以修改参数。
6163

被折叠的 条评论
为什么被折叠?



