bigywater.tech
Spring · 22

自调用失效:同一个类里调用为什么没有代理

为什么同一个类里的方法互相调用,AOP 切面和事务就不生效了?怎么修?代理对象上的字段为什么是 null?这篇在 Spring Framework 7.0.9 上逐项验证。

0 对 1
自调用与外部调用时,切面对同一个方法的拦截次数
3 种
让内部调用被拦截的办法:注入自己、AopContext.currentProxy()、拆成两个 Bean
null
通过 CGLIB 代理读目标对象 public 字段得到的值

为什么失效

实测 · 本机执行输出
  (a)普通的自调用 this.b()                       → 外部直接调 b():拦截 1 次;经过 a() 间接调 b():拦截 0 次
    外部调 b() 时目标方法里的 this:this 是代理?false,this.getClass()=Svc(proxy 把调用转给了目标对象,this 是目标,不是代理)

内部调用的 this 是目标对象,不经过代理。

怎么修

实测 · 本机执行输出
  (b)注入自己的代理(@Autowired @Lazy self)        → 外部直接调 b():拦截 1 次;经过 a() 间接调 b():拦截 1 次
  (c)AopContext.currentProxy()(exposeProxy=true) → 外部直接调 b():拦截 1 次;经过 a() 间接调 b():拦截 1 次
    没有 exposeProxy=true(或不是代理对象)时 AopContext.currentProxy() → IllegalStateException: Cannot find current proxy: Set 'exposeProxy' property on Advised to 'true' to make it available, and ensure that AopContext.currentProxy() is invoked in the same thread as the AOP invocation context.
  (d)拆成两个 Bean(Other 调 svc.b())            → 外部直接调 b():拦截 1 次;经过 a() 间接调 b():拦截 1 次

注入自己的代理、AopContext.currentProxy() 或者拆成两个 Bean。

字段的陷阱

实测 · 本机执行输出
  通过代理读 public 字段 s.field = null(目标对象里是 "字段值");这个 Bean 的类:Main$Svc·CGLIB(AopUtils.isAopProxy=true)
  目标对象上读 field = 字段值

代理对象的字段不会被初始化,读字段要通过 getter。

分步演示

自调用失效
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64 / OpenJDK 21.0.12(Homebrew) / Spring Boot 4.0.8(Spring Framework 7.0.9、Tomcat 11.0.24) / Spring Cloud 2025.1.3;用 javac 编译、java 直接运行,依赖由 Maven 从 Maven Central 解析;数字每次运行会有小幅抖动。用不带 Boot 的 Spring Framework 7.0.9 + AspectJ 注解,proxyTargetClass = true;切面用 @annotation(Counted) 拦截;注入自己的写法需要 @Lazy 才能避免构造时循环
    • 没有验证:AspectJ 编译期或加载期织入(能拦截自调用,没有测);JDK 代理下自调用的行为(原理相同,没有单独测);@Transactional 与 @Async 的自调用(见后面的事务与 @Async 篇);Spring 6.2 之后对某些自调用场景的变化(没有查)
    京ICP备2026067502号-1京公网安备11010502063487号