当前位置: 首页 > news >正文

AspectJ编译期织入实战

JDK动态代理对final类/方法增强无效,CGLIB因继承机制无法代理final类/方法。当业务场景中必须使用final类(如工具类、第三方依赖类)或final方法时,Spring AOP(动态代理)已无法满足需求,此时需使用AspectJ编译期织入实现AOP增强。

本文核心:用规范的开发示例,讲解AspectJ编译期织入的配置、编码、编译运行全流程,解决final类/方法的AOP增强痛点,贴合实际开发规范。

一、核心前提:AspectJ编译期织入原理

与Spring AOP(运行时动态代理)不同,AspectJ编译期织入是在代码编译阶段,将切面逻辑(通知)直接织入目标类的字节码中,生成包含增强逻辑的class文件。

核心优势:不依赖动态代理,不受final类/方法的限制——无论目标类/方法是否为final,只要匹配切点规则,就能实现增强,且性能优于运行时代理(无反射开销)。

关键区别:Spring AOP是“运行时增强”,AspectJ编译期织入是“编译时增强”,直接修改目标类字节码,无需代理对象。

二、规范开发示例(SpringBoot + AspectJ 编译期织入)

以“final工具类的方法增强(日志记录)”为实战场景,完整演示从依赖配置、切面编写、编译配置到测试运行的全流程,符合企业开发规范。

2.1 场景定义

假设存在一个final工具类(业务要求必须为final,防止被继承篡改),需对其内部的final方法添加日志增强,记录方法调用参数、返回值和执行耗时。

/** * 业务要求:必须为final类(工具类,禁止继承) * 内部方法为final,禁止重写 */publicfinalclassFinalToolUtil{/** * final方法:业务核心工具方法,需添加日志增强 * @param param 入参 * @return 处理结果 */publicfinalStringprocess(Stringparam){// 模拟业务逻辑:参数处理try{Thread.sleep(100);// 模拟处理耗时}catch(InterruptedExceptione){thrownewRuntimeException("处理失败",e);}return"处理结果:"+param.toUpperCase();}}

2.2 第一步:添加依赖(SpringBoot项目)

需添加AspectJ核心依赖和编译期织入插件,确保编译时能将切面逻辑织入目标类。

pom.xml 配置(规范依赖版本,贴合SpringBoot版本):

<!-- SpringBoot 基础依赖(省略,根据自身版本引入) --><!-- 1. AspectJ 核心依赖 --><dependency><groupId>org.aspectj</groupId><artifactId>aspectjrt</artifactId><version>1.9.19</version></dependency><!-- 2. AspectJ 编译期织入插件(关键) --><build><plugins><plugin><groupId>org.codehaus.mojo</groupId><artifactId>aspectj-maven-plugin</artifactId><version>1.14.0</version><configuration><!-- 指定Java版本,与项目一致 --><source>1.8</source><target>1.8</target><complianceLevel>1.8</complianceLevel><!-- 指定切面类所在包(扫描切面) --><aspectLibraries><aspectLibrary><groupId>org.aspectj</groupId><artifactId>aspectjrt</artifactId></aspectLibrary></aspectLibraries></configuration><executions><execution><goals><!-- 编译时织入 --><goal>compile</goal><goal>test-compile</goal></goals></execution></executions></plugin></plugins></build>

说明:aspectj-maven-plugin 是编译期织入的核心,负责在maven编译阶段,将切面逻辑织入目标类字节码。

2.3 第二步:编写AspectJ切面(规范编写)

使用AspectJ注解编写切面,定义切点(匹配final类的final方法)和通知(日志增强逻辑),遵循AOP开发规范。

importorg.aspectj.lang.ProceedingJoinPoint;importorg.aspectj.lang.annotation.Around;importorg.aspectj.lang.annotation.Aspect;importorg.aspectj.lang.annotation.Pointcut;importorg.slf4j.Logger;importorg.slf4j.LoggerFactory;importorg.springframework.stereotype.Component;/** * AspectJ 切面类(编译期织入) * 用于增强 FinalToolUtil 的 final 方法 */@Aspect// 标识为AspectJ切面@Component// 交给Spring管理(可选,若不依赖Spring,可省略)publicclassFinalMethodAspect{privatestaticfinalLoggerlog=LoggerFactory.getLogger(FinalMethodAspect.class);/** * 切点:匹配 FinalToolUtil 类的所有final方法 * 切点表达式规范:execution(修饰符 返回值 全类名.方法名(参数)) */@Pointcut("execution(public final String com.example.demo.util.FinalToolUtil.process(..))")publicvoidfinalMethodPointcut(){}/** * 环绕通知:记录方法调用日志、执行耗时 * 环绕通知可控制方法执行,适合记录耗时、异常处理 */@Around("finalMethodPointcut()")publicObjectaroundFinalMethod(ProceedingJoinPointjoinPoint)throwsThrowable{// 1. 前置增强:记录方法调用信息StringmethodName=joinPoint.getSignature().getName();Object[]args=joinPoint.getArgs();log.info("【Final方法增强】开始调用方法:{},入参:{}",methodName,args[0]);// 2. 记录开始时间,执行目标方法(final方法)longstartTime=System.currentTimeMillis();Objectresult=joinPoint.proceed();// 执行目标final方法// 3. 后置增强:记录执行耗时和返回值longcostTime=System.currentTimeMillis()-startTime;log.info("【Final方法增强】方法{}执行完毕,耗时:{}ms,返回值:{}",methodName,costTime,result);// 4. 返回目标方法结果returnresult;}}

关键说明:

  • 切点表达式必须精准匹配final方法:`execution(public final String com.example.demo.util.FinalToolUtil.process(..))`,明确方法的修饰符(final)、返回值、全类名、方法名和参数。

  • @Around通知:AspectJ的环绕通知与Spring AOP用法一致,可完整控制目标方法的执行流程,适合日志、耗时统计等场景。

  • 切面类可交给Spring管理(@Component),也可独立使用(不依赖Spring),本文演示SpringBoot集成场景。

2.4 第三步:编译验证(核心步骤)

AspectJ编译期织入的核心是“编译阶段织入”,需通过maven编译,确保切面逻辑被织入FinalToolUtil的字节码中。

执行maven编译命令:

mvn clean compile

编译成功后,可通过反编译工具(如JD-GUI)查看FinalToolUtil.class文件,会发现:切面的日志逻辑已被直接织入process()方法中,而非通过代理实现。

反编译核心片段(示意):

publicfinalclassFinalToolUtil{publicfinalStringprocess(Stringparam){// 织入的切面逻辑(前置日志)Loggerlog=LoggerFactory.getLogger(FinalMethodAspect.class);StringmethodName="process";Object[]args=newObject[]{param};log.info("【Final方法增强】开始调用方法:{},入参:{}",methodName,args[0]);longstartTime=System.currentTimeMillis();// 原业务逻辑try{Thread.sleep(100);}catch(InterruptedExceptione){thrownewRuntimeException("处理失败",e);}Stringresult="处理结果:"+param.toUpperCase();// 织入的切面逻辑(后置日志)longcostTime=System.currentTimeMillis()-startTime;log.info("【Final方法增强】方法{}执行完毕,耗时:{}ms,返回值:{}",methodName,costTime,result);returnresult;}}

可见:编译后,切面逻辑已与目标final方法的业务逻辑融合,无需代理,直接执行。

2.5 第四步:测试运行(实战验证)

编写测试类,调用FinalToolUtil的process()方法,验证AOP增强是否生效(日志是否打印)。

importcom.example.demo.util.FinalToolUtil;importorg.junit.jupiter.api.Test;importorg.springframework.beans.factory.annotation.Autowired;importorg.springframework.boot.test.context.SpringBootTest;@SpringBootTestpublicclassFinalToolUtilTest{// 直接注入final类实例(无需代理,编译后已包含增强逻辑)@AutowiredprivateFinalToolUtilfinalToolUtil;@TestpublicvoidtestProcess(){// 调用final方法Stringresult=finalToolUtil.process("aspectj-test");System.out.println("测试结果:"+result);}}

2.6 运行结果(符合预期)

【Final方法增强】开始调用方法:process,入参:aspectj-test 【Final方法增强】方法process执行完毕,耗时:102ms,返回值:处理结果:ASPECTJ-TEST 测试结果:处理结果:ASPECTJ-TEST

结论:final类的final方法成功被增强,日志正常打印,证明AspectJ编译期织入生效。

三、关键注意事项(规范开发必看)

  1. 依赖版本一致:aspectjrt、aspectj-maven-plugin的版本需匹配,避免编译报错(本文使用稳定版本组合,可直接复用)。

  2. 切点表达式精准:必须明确匹配final方法的修饰符(final),否则无法织入(AspectJ支持精准匹配修饰符)。

  3. 编译方式:必须使用maven编译(mvn compile),IDE直接编译可能无法触发AspectJ织入(需配置IDE的AspectJ插件,如IntelliJ IDEA的AspectJ Support)。

  4. 与Spring AOP区分:AspectJ编译期织入无需依赖Spring AOP,可独立使用;若集成SpringBoot,只需添加@Component将切面交给Spring管理即可。

  5. 第三方final类增强:若目标final类是第三方依赖(无法修改源码),只需在切面中精准配置切点表达式,编译时同样能织入增强逻辑(核心优势)。

四、总结(开发实战结论)

当遇到final类/方法需要AOP增强时,Spring AOP(JDK/CGLIB)无法解决,此时AspectJ编译期织入是最优方案:

  • 优势:不受final限制,性能优于动态代理(无反射开销),支持第三方final类增强。

  • 实战流程:添加依赖 → 编写切面 → maven编译 → 测试运行,符合企业开发规范。

  • 适用场景:工具类、第三方依赖类、业务要求必须为final的类/方法的AOP增强(日志、权限、耗时统计等)。

本文示例可直接复制到项目中复用,只需修改包名、类名和切点表达式,即可快速实现final类/方法的AOP增强。

http://www.jsqmd.com/news/684061/

相关文章:

  • YOLO自动标注工具软件
  • 2026 年绍兴养发加盟机构权威排行榜 TOP5(千唯养发居首) - 小艾信息发布
  • MLOps资源管理优化:从GPU虚拟化到智能调度
  • 消息队列消费积压到打爆磁盘:我用Consumer Lag监控+阈值告警在5分钟内止血
  • 别再死记硬背了!用PyTorch手把手带你理解ReLU和Sigmoid激活函数到底在干啥
  • 网络不稳,很多时候不在交换机:通信系统安装的结构逻辑与落地
  • PyTorch计算机视觉深度学习七日速成指南
  • 从‘Invalid HTTP status’到稳定连接:UniApp微信小程序WebSocket实战配置详解
  • Docker构建缓存失效之谜,深度解析.dockerignore误配、时间戳漂移与远程缓存断连的3大隐形杀手
  • 不止STM32F0!国产MM32L073等Cortex-M0芯片IAP中断问题通用解法
  • Reference Extractor终极指南:3分钟从Word文档恢复Zotero和Mendeley引用
  • html怎么部署到服务器_HTML文件如何上传到Nginx或Apache
  • 86253
  • C#构建低延迟AI微服务的最后机会:.NET 11推理加速黄金组合(Span<T>零拷贝+MemoryPool<T>预分配+Custom TensorKernel),仅剩217行核心代码未开源
  • JavaWeb 核心:JavaBean+JSP 动作标签 + EL 表达式全解析
  • FPGA实战:在Vivado里快速搭建一个可配置的偶数分频IP核(附源码)
  • 网络安全已进入“高频攻击、高复杂度、高不确定性”的新阶段
  • 数百种蛋白同步解析:抗体芯片如何重塑WB技术边界
  • ESP-C3-12F内置USB烧录实测:比传统串口快多少?省时技巧与常见错误排查
  • MySQL触发器在主从架构下的表现_MySQL触发器主从同步策略
  • 高效解决开发环境依赖问题:Visual C++运行库完整配置指南
  • 告别Office依赖!用Aspose.Slides for .NET在服务器端批量生成PPT(附C#代码示例)
  • 手把手教你理解芯片‘身份证’PUF:从制造误差到密钥生成,一次搞懂SRAM PUF的完整生命周期
  • 别再死记硬背了!用C语言手搓DES-CBC加密,从S盒到IV的实战避坑指南
  • 玩客云魔改指南:除了NAS还能跑Docker?Armbian系统下的5种隐藏玩法实测
  • 词袋模型(Bag Of Words)在文本分类中的原理与实践
  • 计算机毕业设计:Python大盘行情与个股诊断预测系统 Flask框架 TensorFlow LSTM 数据分析 可视化 大数据 大模型(建议收藏)✅
  • Dify .NET客户端源码AOT适配全链路分析(从IL修剪到NativeAOT陷阱避坑指南)
  • Phi-3-mini-4k-instruct-gguf效果对比:vs Qwen2-0.5B/Qwen1.5-1.8B在指令任务上的差异
  • 5块钱的2N3819 JFET到手实测:从真假辨别到搭建简易非接触验电笔