news 2026/2/7 3:19:47

Spring AOP连接点实战解析

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Spring AOP连接点实战解析

🎯AOP连接点(JoinPoint)详解

连接点是AOP中程序执行过程中的一个特定点,它是切面可以插入增强逻辑的位置。


📍连接点是什么?

生动的比喻

程序执行就像一部电影 🎬 连接点 → 电影的关键帧(可以插入特效的地方) 切面 → 特效团队 增强 → 实际添加的特效(慢动作、滤镜等)

更技术化的理解

连接点是程序运行中可以被拦截的点,例如:

  • 方法调用
  • 方法执行
  • 异常抛出
  • 字段访问
  • 对象初始化

在Spring AOP中,连接点特指:方法的执行


🎪连接点的具体形式

1. 方法执行(Method Execution)Spring AOP唯一支持的

publicclassUserService{publicvoidsaveUser(Useruser){// ← 这是一个连接点// 方法体}}

2. 方法调用(Method Call)Spring AOP不支持

publicclassController{publicvoidprocess(){userService.saveUser(user);// ← 这是一个调用连接点}}

3. 其他连接点(AspectJ支持,但Spring AOP不支持)

// 构造器执行newUserService();// ← 连接点// 字段访问user.name;// ← 连接点// 异常处理thrownewException();// ← 连接点

🔧JoinPoint对象详解

在通知方法中,可以通过JoinPoint参数获取连接点信息:

JoinPoint核心方法

publicinterfaceJoinPoint{// 获取方法签名SignaturegetSignature();// 获取目标对象ObjectgetTarget();// 获取代理对象ObjectgetThis();// 获取方法参数Object[]getArgs();// 获取连接点类型(Spring AOP中总是:method-execution)StringgetKind();// 获取静态部分信息JoinPoint.StaticPartgetStaticPart();// 获取源位置信息(文件名、行号等)SourceLocationgetSourceLocation();}

Signature对象

publicinterfaceSignature{StringgetName();// 方法名:saveUserintgetModifiers();// 修饰符:publicClassgetDeclaringType();// 声明类:UserServiceStringgetDeclaringTypeName();// 类名:com.example.UserService// 更多信息StringtoShortString();// 简短字符串StringtoLongString();// 完整字符串}

💻实际使用示例

示例1:获取方法信息

@Before("execution(* com.example.service.*.*(..))")publicvoidbeforeAdvice(JoinPointjoinPoint){// 1. 获取方法签名MethodSignaturesignature=(MethodSignature)joinPoint.getSignature();System.out.println("=== 连接点信息 ===");System.out.println("方法名: "+signature.getName());// saveUserSystem.out.println("方法全名: "+signature.toLongString());// public void com.example.UserService.saveUser(User)System.out.println("声明类: "+signature.getDeclaringTypeName());// com.example.UserServiceSystem.out.println("返回类型: "+signature.getReturnType());// void// 2. 获取参数Object[]args=joinPoint.getArgs();for(inti=0;i<args.length;i++){System.out.println("参数"+i+": "+args[i]+" (类型: "+signature.getParameterTypes()[i]+")");}// 3. 目标对象和代理对象System.out.println("目标对象: "+joinPoint.getTarget().getClass());// UserService$$EnhancerBySpringCGLIBSystem.out.println("代理对象: "+joinPoint.getThis().getClass());// UserService$$EnhancerBySpringCGLIB}

示例2:环绕通知中使用ProceedingJoinPoint

@Around("execution(* com.example.service.*.*(..))")publicObjectaroundAdvice(ProceedingJoinPointjoinPoint)throwsThrowable{// ProceedingJoinPoint是JoinPoint的子接口,多了proceed()方法System.out.println("【环绕通知开始】方法: "+joinPoint.getSignature().getName());// 1. 可以修改参数Object[]args=joinPoint.getArgs();if(args.length>0&&args[0]instanceofString){args[0]=((String)args[0]).toUpperCase();// 修改参数}// 2. 执行目标方法longstart=System.currentTimeMillis();Objectresult=joinPoint.proceed(args);// 可以传入修改后的参数longend=System.currentTimeMillis();System.out.println("【环绕通知结束】耗时: "+(end-start)+"ms");// 3. 可以修改返回值if(resultinstanceofString){result="处理后的结果: "+result;}returnresult;}

📊连接点 vs 切入点 vs 通知

概念定义比喻
连接点程序执行中的具体点(如方法执行)电影中的具体帧
切入点匹配连接点的表达式(筛选哪些连接点)选中的帧的范围(如所有打斗场景)
通知在连接点执行的增强逻辑在选中的帧上添加的特效

三者关系

@Aspect@ComponentpublicclassLogAspect{// 切入点:匹配哪些连接点@Pointcut("execution(* com.example.service.*.*(..))")publicvoidserviceMethods(){}// ← 切入点表达式// 通知:在连接点上执行的逻辑@Before("serviceMethods()")// ← 应用到切入点匹配的连接点publicvoidlogBefore(JoinPointjoinPoint){// ← JoinPoint代表具体的连接点// 这里是通知逻辑System.out.println("执行方法: "+joinPoint.getSignature().getName());}}

🎯获取连接点信息的实用工具类

@ComponentpublicclassJoinPointUtils{/** * 获取方法参数Map */publicstaticMap<String,Object>getArgsMap(JoinPointjoinPoint){Map<String,Object>argsMap=newHashMap<>();if(joinPoint.getSignature()instanceofMethodSignature){MethodSignaturesignature=(MethodSignature)joinPoint.getSignature();String[]parameterNames=signature.getParameterNames();Object[]args=joinPoint.getArgs();for(inti=0;i<parameterNames.length;i++){argsMap.put(parameterNames[i],args[i]);}}returnargsMap;}/** * 获取方法注解 */publicstatic<TextendsAnnotation>TgetMethodAnnotation(JoinPointjoinPoint,Class<T>annotationClass){MethodSignaturesignature=(MethodSignature)joinPoint.getSignature();Methodmethod=signature.getMethod();returnmethod.getAnnotation(annotationClass);}/** * 获取类注解 */publicstatic<TextendsAnnotation>TgetClassAnnotation(JoinPointjoinPoint,Class<T>annotationClass){Class<?>targetClass=joinPoint.getTarget().getClass();returntargetClass.getAnnotation(annotationClass);}/** * 获取完整方法路径 */publicstaticStringgetFullMethodName(JoinPointjoinPoint){StringclassName=joinPoint.getSignature().getDeclaringTypeName();StringmethodName=joinPoint.getSignature().getName();returnclassName+"."+methodName;}/** * 获取IP地址(Web环境下) */publicstaticStringgetIpAddress(JoinPointjoinPoint){try{ServletRequestAttributesattributes=(ServletRequestAttributes)RequestContextHolder.getRequestAttributes();if(attributes!=null){HttpServletRequestrequest=attributes.getRequest();returnrequest.getRemoteAddr();}}catch(Exceptione){// 忽略}return"unknown";}}

使用示例

@Before("execution(* com.example.service.*.*(..))")publicvoidbeforeAdvice(JoinPointjoinPoint){// 使用工具类Map<String,Object>args=JoinPointUtils.getArgsMap(joinPoint);StringfullMethodName=JoinPointUtils.getFullMethodName(joinPoint);System.out.println("方法: "+fullMethodName);System.out.println("参数: "+args);// 获取特定注解LoglogAnnotation=JoinPointUtils.getMethodAnnotation(joinPoint,Log.class);if(logAnnotation!=null){System.out.println("日志级别: "+logAnnotation.level());}}

🚀实战场景

场景1:操作日志记录

@Aspect@ComponentpublicclassOperationLogAspect{@AfterReturning(pointcut="@annotation(log)",returning="result")publicvoidlogOperation(JoinPointjoinPoint,@annotation(log)OperationLoglog,Objectresult){// 从连接点获取信息StringmethodName=joinPoint.getSignature().getName();StringclassName=joinPoint.getSignature().getDeclaringTypeName();Object[]args=joinPoint.getArgs();// 构建日志LogRecordrecord=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:参数验证

@Aspect@ComponentpublicclassValidationAspect{@Before("execution(* com.example.controller.*.*(..))")publicvoidvalidateParams(JoinPointjoinPoint){// 获取所有参数Object[]args=joinPoint.getArgs();for(Objectarg:args){if(arginstanceofBaseDTO){// 执行DTO验证ValidationResultresult=validator.validate(arg);if(!result.isValid()){thrownewValidationException(result.getErrors());}}}}}

场景3:缓存切面

@Aspect@ComponentpublicclassCacheAspect{@Around("@annotation(cacheable)")publicObjectcacheResult(ProceedingJoinPointjoinPoint,Cacheablecacheable)throwsThrowable{// 生成缓存key:类名+方法名+参数Stringkey=generateCacheKey(joinPoint);// 先从缓存获取Objectcached=cache.get(key);if(cached!=null){returncached;}// 执行方法Objectresult=joinPoint.proceed();// 存入缓存cache.put(key,result,cacheable.ttl(),TimeUnit.SECONDS);returnresult;}privateStringgenerateCacheKey(ProceedingJoinPointjoinPoint){StringBuilderkey=newStringBuilder();// 类名key.append(joinPoint.getSignature().getDeclaringTypeName()).append(".");// 方法名key.append(joinPoint.getSignature().getName()).append(":");// 参数(简单处理)for(Objectarg:joinPoint.getArgs()){key.append(arg!=null?arg.toString():"null").append(",");}returnkey.toString();}}

⚠️重要限制

Spring AOP只支持方法执行连接点

// ✅ 支持的@Before("execution(* *.*(..))")// ❌ 不支持的(需要AspectJ)@Before("call(* *.*(..))")// 方法调用@Before("initialization(*.new(..))")// 构造器@Before("get(* *)")// 字段读取@Before("set(* *)")// 字段设置

代理机制的影响

  • JDK动态代理:只能代理接口方法,thistarget可能不同
  • CGLIB代理:可以代理类,thistarget通常相同
  • 内部方法调用不会被拦截(因为不走代理)

🧪调试技巧

打印连接点信息

@Before("execution(* *.*(..))")publicvoiddebugJoinPoint(JoinPointjoinPoint){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("==========================");}

判断连接点类型

@Before("execution(* *.*(..))")publicvoidcheckJoinPoint(JoinPointjoinPoint){if(joinPointinstanceofProceedingJoinPoint){System.out.println("这是ProceedingJoinPoint(环绕通知可用)");}else{System.out.println("这是普通JoinPoint");}}

💎总结

连接点的核心要点

  1. 定义:程序执行中可以插入增强的点
  2. Spring AOP:只支持方法执行这一种连接点
  3. JoinPoint对象:包含方法签名、参数、目标对象等信息
  4. ProceedingJoinPoint:用于环绕通知,可以控制方法执行

一句话记忆

连接点就像程序的"穴位",切入点就是"针灸的位置图",通知就是"扎针的治疗手法"。

实用口诀

连接点,执行点,方法执行最常见 JoinPoint,信息全,签名参数都在里边 ProceedingJoinPoint更强,可以控制方法执行权 记住Spring有局限,只支持方法执行这一个连接点
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/2/2 6:19:50

OpenVoice语音克隆终极指南:5分钟快速上手免费神器

OpenVoice语音克隆终极指南&#xff1a;5分钟快速上手免费神器 【免费下载链接】OpenVoice 项目是MyShell AI开源的即时语音克隆技术OpenVoice&#xff0c;旨在提供一种能够快速从少量语音样本中准确复制人类声音特征&#xff0c;并实现多种语言及语音风格转换的解决方案。 项…

作者头像 李华
网站建设 2026/2/6 6:20:02

OrcaSlicer智能分层技术:让3D打印告别“选择困难症“

OrcaSlicer智能分层技术&#xff1a;让3D打印告别"选择困难症" 【免费下载链接】OrcaSlicer G-code generator for 3D printers (Bambu, Prusa, Voron, VzBot, RatRig, Creality, etc.) 项目地址: https://gitcode.com/GitHub_Trending/orc/OrcaSlicer 还在为…

作者头像 李华
网站建设 2026/2/6 18:43:42

Universal G-Code Sender 终极故障排查指南

Universal G-Code Sender 终极故障排查指南 【免费下载链接】Universal-G-Code-Sender A cross-platform G-Code sender for GRBL, Smoothieware, TinyG and G2core. 项目地址: https://gitcode.com/gh_mirrors/un/Universal-G-Code-Sender Universal G-Code Sender&…

作者头像 李华
网站建设 2026/2/3 7:08:35

终极AI设计指南:5分钟生成专业UI原型的开源神器

终极AI设计指南&#xff1a;5分钟生成专业UI原型的开源神器 【免费下载链接】superdesign 项目地址: https://gitcode.com/gh_mirrors/su/superdesign 在当今快速迭代的开发环境中&#xff0c;您是否经常面临这样的困境&#xff1a;设计想法难以快速落地&#xff0c;原…

作者头像 李华
网站建设 2026/2/5 16:38:52

SoFixer:Android SO文件修复与加固的终极指南

SoFixer&#xff1a;Android SO文件修复与加固的终极指南 【免费下载链接】SoFixer 项目地址: https://gitcode.com/gh_mirrors/so/SoFixer SoFixer是一款专为Android开发者设计的强大工具&#xff0c;主要用于修复和加固SO文件&#xff0c;解决各种ELF格式相关的兼容性…

作者头像 李华
网站建设 2026/2/4 21:39:49

PhyloSuite:生物信息学研究的终极进化分析工具指南

PhyloSuite&#xff1a;生物信息学研究的终极进化分析工具指南 【免费下载链接】PhyloSuite PhyloSuite is an integrated and scalable desktop platform for streamlined molecular sequence data management and evolutionary phylogenetics studies 项目地址: https://gi…

作者头像 李华