热部署后AOP切面重复执行的本质是旧代理未卸载、新旧代理共存,导致同一方法被多层代理拦截;需通过线程ID+this哈希验证、检查代理实例数、确认AdvisorAutoProxyCreator是否重复注册及类加载器隔离问题来定位。

热部署后 AOP 切面重复执行,本质是旧代理对象未被卸载、新旧代理共存,导致同一个方法被多个代理链拦截。这不是代码逻辑错误,而是类加载与代理生命周期管理失控的结果。
确认是否真有多个代理实例在运行
先验证现象是否为“重复执行”,而非日志误判或异步调用干扰:
- 在切面通知中加入唯一标识,例如 Thread.currentThread().getId() + System.identityHashCode(this),打印到日志,观察每次调用时的 this 是否不同
- 检查目标方法上是否有多个同类注解(如两个
@Transactional或两个自定义日志注解),排除配置叠加 - 用
jmap -histo查看堆中org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor或com.sun.proxy.$Proxy实例数量是否异常增长
检查代理创建源头是否重复注册
Spring 容器中若存在多个 AdvisorAutoProxyCreator 类型的后置处理器,会为同一 Bean 创建多层代理:
- 搜索项目中是否手动声明了
DefaultAdvisorAutoProxyCreator或AspectJAwareAdvisorAutoProxyCreator的 Bean,尤其注意 XML 配置、@Bean方法、第三方 Starter 自动配置冲突 - Spring Boot 项目默认启用
AspectJAwareAdvisorAutoProxyCreator;若又显式配置了DefaultAdvisorAutoProxyCreator,就会触发双重代理 - 查看
spring.factories中是否引入了多个 AOP 相关自动配置类(如某些监控 SDK 或老版本 starter 可能自带)
验证类加载器隔离与旧类残留
热部署工具(JRebel / DevTools)未彻底卸载旧类时,旧代理类仍驻留于某个 ClassLoader,而新代理由新 ClassLoader 创建:
- 启动时添加 JVM 参数
-verbose:class,观察类加载日志中*Proxy或*Advice类是否反复加载(尤其是不同 ClassLoader hash) - 用
jcmd或VM.native_memory summary jvisualvm的“ClassLoader”页签,确认是否存在多个业务类加载器且都持有代理类 - 若使用 JRebel,检查
rebel.xml是否将切面类或配置类排除在外,导致其未被重载
强制清理并收敛代理生命周期
避免依赖热部署工具自动回收,主动控制代理生成与销毁边界:
- 禁用 Spring Boot DevTools(它与 JRebel 冲突),在
application.yml中设spring.devtools.restart.enabled: false - 关闭自动代理,改用显式
@EnableAspectJAutoProxy(exposeProxy = true)并统一管理代理暴露策略 - 在应用关闭钩子(
@PreDestroy或SmartLifecycle.stop())中,对关键切面 Bean 调用AopContext.setCurrentProxy(null),减少上下文污染 - 对高频热替换模块,考虑改用接口+策略模式替代强依赖代理的 AOP,降低类加载耦合度










