🎯 AOP连接点(JoinPoint)详解
连接点是AOP中程序执行过程中的一个特定点,它是切面可以插入增强逻辑的位置。
📍 连接点是什么?
生动的比喻
程序执行就像一部电影 🎬
连接点 → 电影的关键帧(可以插入特效的地方)
切面 → 特效团队
增强 → 实际添加的特效(慢动作、滤镜等)
更技术化的理解
连接点是程序运行中可以被拦截的点,例如:
- 方法调用
- 方法执行
- 异常抛出
- 字段访问
- 对象初始化
在Spring AOP中,连接点特指:方法的执行。
🎪 连接点的具体形式
1. 方法执行(Method Execution) ✅ Spring AOP唯一支持的
java
public class UserService {
public void saveUser(User user) { // ← 这是一个连接点
// 方法体
}
}
2. 方法调用(Method Call) ❌ Spring AOP不支持
java
public class Controller {
public void process() {
userService.saveUser(user); // ← 这是一个调用连接点
}
}
3. 其他连接点(AspectJ支持,但Spring AOP不支持)
java
// 构造器执行
new UserService(); // ← 连接点
// 字段访问
user.name; // ← 连接点
// 异常处理
throw new Exception(); // ← 连接点
🔧 JoinPoint对象详解
在通知方法中,可以通过JoinPoint参数获取连接点信息:
JoinPoint核心方法
java
public interface JoinPoint {
// 获取方法签名
Signature getSignature();
// 获取目标对象
Object getTarget();
// 获取代理对象
Object getThis();
// 获取方法参数
Object[] getArgs();
// 获取连接点类型(Spring AOP中总是:method-execution)
String getKind();
// 获取静态部分信息
JoinPoint.StaticPart getStaticPart();
// 获取源位置信息(文件名、行号等)
SourceLocation getSourceLocation();
}
Signature对象
java
public interface Signature {
String getName(); // 方法名:saveUser
int getModifiers(); // 修饰符:public
Class getDeclaringType(); // 声明类:UserService
String getDeclaringTypeName(); // 类名:com.example.UserService
// 更多信息
String toShortString(); // 简短字符串
String toLongString(); // 完整字符串
}
💻 实际使用示例
示例1:获取方法信息
java
@Before("execution(* com.example.service.*.*(..))")
public void beforeAdvice(JoinPoint joinPoint) {
// 1. 获取方法签名
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
System.out.println("=== 连接点信息 ===");
System.out.println("方法名: " + signature.getName()); // saveUser
System.out.println("方法全名: " + signature.toLongString()); // public void com.example.UserService.saveUser(User)
System.out.println("声明类: " + signature.getDeclaringTypeName()); // com.example.UserService
System.out.println("返回类型: " + signature.getReturnType()); // void
// 2. 获取参数
Object[] args = joinPoint.getArgs();
for (int i = 0; i < args.length; i++) {
System.out.println("参数" + i + ": " + args[i] + " (类型: " +
signature.getParameterTypes()[i] + ")");
}
// 3. 目标对象和代理对象
System.out.println("目标对象: " + joinPoint.getTarget().getClass()); // UserService$$EnhancerBySpringCGLIB
System.out.println("代理对象: " + joinPoint.getThis().getClass()); // UserService$$EnhancerBySpringCGLIB
}
示例2:环绕通知中使用ProceedingJoinPoint
java
@Around("execution(* com.example.service.*.*(..))")
public Object aroundAdvice(ProceedingJoinPoint joinPoint) throws Throwable {
// ProceedingJoinPoint是JoinPoint的子接口,多了proceed()方法
System.out.println("【环绕通知开始】方法: " + joinPoint.getSignature().getName());
// 1. 可以修改参数
Object[] args = joinPoint.getArgs();
if (args.length > 0 && args[0] instanceof String) {
args[0] = ((String) args[0]).toUpperCase(); // 修改参数
}
// 2. 执行目标方法
long start = System.currentTimeMillis();
Object result = joinPoint.proceed(args); // 可以传入修改后的参数
long end = System.currentTimeMillis();
System.out.println("【环绕通知结束】耗时: " + (end - start) + "ms");
// 3. 可以修改返回值
if (result instanceof String) {
result = "处理后的结果: " + result;
}
return result;
}
📊 连接点 vs 切入点 vs 通知
| 概念 | 定义 | 比喻 |
|---|---|---|
| 连接点 | 程序执行中的具体点(如方法执行) | 电影中的具体帧 |
| 切入点 | 匹配连接点的表达式(筛选哪些连接点) | 选中的帧的范围(如所有打斗场景) |
| 通知 | 在连接点执行的增强逻辑 | 在选中的帧上添加的特效 |
三者关系
java
@Aspect
@Component
public class LogAspect {
// 切入点:匹配哪些连接点
@Pointcut("execution(* com.example.service.*.*(..))")
public void serviceMethods() {} // ← 切入点表达式
// 通知:在连接点上执行的逻辑
@Before("serviceMethods()") // ← 应用到切入点匹配的连接点
public void logBefore(JoinPoint joinPoint) { // ← JoinPoint代表具体的连接点
// 这里是通知逻辑
System.out.println("执行方法: " + joinPoint.getSignature().getName());
}
}
🎯 获取连接点信息的实用工具类
java
@Component
public class JoinPointUtils {
/**
* 获取方法参数Map
*/
public static Map<String, Object> getArgsMap(JoinPoint joinPoint) {
Map<String, Object> argsMap = new HashMap<>();
if (joinPoint.getSignature() instanceof MethodSignature) {
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
String[] parameterNames = signature.getParameterNames();
Object[] args = joinPoint.getArgs();
for (int i = 0; i < parameterNames.length; i++) {
argsMap.put(parameterNames[i], args[i]);
}
}
return argsMap;
}
/**
* 获取方法注解
*/
public static <T extends Annotation> T getMethodAnnotation(
JoinPoint joinPoint, Class<T> annotationClass) {
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
Method method = signature.getMethod();
return method.getAnnotation(annotationClass);
}
/**
* 获取类注解
*/
public static <T extends Annotation> T getClassAnnotation(
JoinPoint joinPoint, Class<T> annotationClass) {
Class<?> targetClass = joinPoint.getTarget().getClass();
return targetClass.getAnnotation(annotationClass);
}
/**
* 获取完整方法路径
*/
public static String getFullMethodName(JoinPoint joinPoint) {
String className = joinPoint.getSignature().getDeclaringTypeName();
String methodName = joinPoint.getSignature().getName();
return className + "." + methodName;
}
/**
* 获取IP地址(Web环境下)
*/
public static String getIpAddress(JoinPoint joinPoint) {
try {
ServletRequestAttributes attributes =
(ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
if (attributes != null) {
HttpServletRequest request = attributes.getRequest();
return request.getRemoteAddr();
}
} catch (Exception e) {
// 忽略
}
return "unknown";
}
}
使用示例
java
@Before("execution(* com.example.service.*.*(..))")
public void beforeAdvice(JoinPoint joinPoint) {
// 使用工具类
Map<String, Object> args = JoinPointUtils.getArgsMap(joinPoint);
String fullMethodName = JoinPointUtils.getFullMethodName(joinPoint);
System.out.println("方法: " + fullMethodName);
System.out.println("参数: " + args);
// 获取特定注解
Log logAnnotation = JoinPointUtils.getMethodAnnotation(joinPoint, Log.class);
if (logAnnotation != null) {
System.out.println("日志级别: " + logAnnotation.level());
}
}
🚀 实战场景
场景1:操作日志记录
java
@Aspect
@Component
public class OperationLogAspect {
@AfterReturning(pointcut = "@annotation(log)", returning = "result")
public void logOperation(JoinPoint joinPoint,
@annotation(log) OperationLog log,
Object result) {
// 从连接点获取信息
String methodName = joinPoint.getSignature().getName();
String className = joinPoint.getSignature().getDeclaringTypeName();
Object[] args = joinPoint.getArgs();
// 构建日志
LogRecord record = LogRecord.builder()
.module(log.module())
.operation(log.operation())
.method(className + "." + methodName)
.params(JSON.toJSONString(args))
.result(JSON.toJSONString(result))
.ip(JoinPointUtils.getIpAddress(joinPoint))
.build();
// 保存日志
logService.save(record);
}
}
场景2:参数验证
java
@Aspect
@Component
public class ValidationAspect {
@Before("execution(* com.example.controller.*.*(..))")
public void validateParams(JoinPoint joinPoint) {
// 获取所有参数
Object[] args = joinPoint.getArgs();
for (Object arg : args) {
if (arg instanceof BaseDTO) {
// 执行DTO验证
ValidationResult result = validator.validate(arg);
if (!result.isValid()) {
throw new ValidationException(result.getErrors());
}
}
}
}
}
场景3:缓存切面
java
@Aspect
@Component
public class CacheAspect {
@Around("@annotation(cacheable)")
public Object cacheResult(ProceedingJoinPoint joinPoint,
Cacheable cacheable) throws Throwable {
// 生成缓存key:类名+方法名+参数
String key = generateCacheKey(joinPoint);
// 先从缓存获取
Object cached = cache.get(key);
if (cached != null) {
return cached;
}
// 执行方法
Object result = joinPoint.proceed();
// 存入缓存
cache.put(key, result, cacheable.ttl(), TimeUnit.SECONDS);
return result;
}
private String generateCacheKey(ProceedingJoinPoint joinPoint) {
StringBuilder key = new StringBuilder();
// 类名
key.append(joinPoint.getSignature().getDeclaringTypeName())
.append(".");
// 方法名
key.append(joinPoint.getSignature().getName())
.append(":");
// 参数(简单处理)
for (Object arg : joinPoint.getArgs()) {
key.append(arg != null ? arg.toString() : "null")
.append(",");
}
return key.toString();
}
}
⚠️ 重要限制
Spring AOP只支持方法执行连接点
java
// ✅ 支持的
@Before("execution(* *.*(..))")
// ❌ 不支持的(需要AspectJ)
@Before("call(* *.*(..))") // 方法调用
@Before("initialization(*.new(..))") // 构造器
@Before("get(* *)") // 字段读取
@Before("set(* *)") // 字段设置
代理机制的影响
- JDK动态代理 :只能代理接口方法,
this和target可能不同 - CGLIB代理 :可以代理类,
this和target通常相同 - 内部方法调用不会被拦截(因为不走代理)
🧪 调试技巧
打印连接点信息
java
@Before("execution(* *.*(..))")
public void debugJoinPoint(JoinPoint joinPoint) {
System.out.println("====== JoinPoint信息 ======");
System.out.println("Kind: " + joinPoint.getKind());
System.out.println("Signature: " + joinPoint.getSignature());
System.out.println("SourceLocation: " + joinPoint.getSourceLocation());
System.out.println("StaticPart: " + joinPoint.getStaticPart());
System.out.println("==========================");
}
判断连接点类型
java
@Before("execution(* *.*(..))")
public void checkJoinPoint(JoinPoint joinPoint) {
if (joinPoint instanceof ProceedingJoinPoint) {
System.out.println("这是ProceedingJoinPoint(环绕通知可用)");
} else {
System.out.println("这是普通JoinPoint");
}
}
💎 总结
连接点的核心要点
- 定义:程序执行中可以插入增强的点
- Spring AOP :只支持方法执行这一种连接点
- JoinPoint对象:包含方法签名、参数、目标对象等信息
- ProceedingJoinPoint:用于环绕通知,可以控制方法执行
一句话记忆
连接点就像程序的"穴位",切入点就是"针灸的位置图",通知就是"扎针的治疗手法"。
实用口诀
连接点,执行点,方法执行最常见
JoinPoint,信息全,签名参数都在里边
ProceedingJoinPoint更强,可以控制方法执行权
记住Spring有局限,只支持方法执行这一个连接点