news 2026/4/18 14:40:42

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/4/17 17:00:02

智能体优化新范式:动态强化学习驱动模块化架构革新

智能体优化新范式&#xff1a;动态强化学习驱动模块化架构革新 【免费下载链接】agentflow-planner-7b 项目地址: https://ai.gitcode.com/hf_mirrors/AgentFlow/agentflow-planner-7b 行业痛点&#xff1a;传统智能体的能力天花板 当开发者试图将大语言模型应用于复杂…

作者头像 李华
网站建设 2026/4/18 6:25:01

AI Agent资源推荐:从入门到实战的完整指南

AI Agent资源推荐&#xff1a;从入门到实战的完整指南 【免费下载链接】500-AI-Agents-Projects The 500 AI Agents Projects is a curated collection of AI agent use cases across various industries. It showcases practical applications and provides links to open-sou…

作者头像 李华
网站建设 2026/4/17 9:44:16

chaiNNer终极AI工具集成指南:高效工作流自动化完整解决方案

chaiNNer终极AI工具集成指南&#xff1a;高效工作流自动化完整解决方案 【免费下载链接】chaiNNer A node-based image processing GUI aimed at making chaining image processing tasks easy and customizable. Born as an AI upscaling application, chaiNNer has grown int…

作者头像 李华
网站建设 2026/4/18 8:33:38

2025年2000元档位最值得买的手机,荣耀500体验分享

荣耀400上市时间 在2025年&#xff0c;2000元档位最值得买的手机重新成为市场主流机型&#xff0c;也是大部分用户的主要选择。如今的中端机型早已不能简单看作旗舰手机的下位阉割版本&#xff0c;在硬件配置和使用体验方面&#xff0c;中端机已达到媲美旗舰手机的水平。在竞争…

作者头像 李华
网站建设 2026/4/17 16:18:21

LIBERO机器人终身学习完整指南:从入门到精通

LIBERO机器人终身学习完整指南&#xff1a;从入门到精通 【免费下载链接】LIBERO 项目地址: https://gitcode.com/gh_mirrors/li/LIBERO 掌握机器人终身学习的核心技术&#xff0c;构建能够持续进化的智能机器人系统 LIBERO是一个专为研究多任务和终身机器人学习问题而设…

作者头像 李华
网站建设 2026/4/18 10:49:51

19、Raspberry Pi户外项目搭建与光线传感器使用指南

Raspberry Pi户外项目搭建与光线传感器使用指南 1. Cacheberry Pi项目搭建 1.1 项目盒准备 可以选择一个标准的塑料项目盒,为LCD屏幕切割一个安装孔,然后将Raspberry Pi也放入盒中。再为电源线和两个USB端口切割孔洞,最后将完成组装的Cacheberry Pi(装在盒子里)安装到汽…

作者头像 李华