自调用失效与代理对象获取
引言
最常见的“@Transactional / @Async / AOP 没生效”现场:方法明明标注了注解,也确实是 public,却毫无效果。排查后发现调用发生在同一个类内部(this 自调用)。本章讲清失效原理与四种修复手段。
核心概念
- 失效原理:注解式 AOP/事务基于代理。外部调用
bean.method()穿过代理 → 通知生效;类内部this.method()直接落在原始对象上,绕过代理,通知自然不触发。 - 官方定义:代理模式(默认)下,只有“经由代理的外部调用”才会被拦截,自调用不构成代理调用(以官方文档为准)。
- 修复思路分四类:
- 拆 Bean:把被调方法移到另一个 Spring Bean,跨 Bean 调用天然走代理——最推荐;
- 自注入:Bean 注入自己的代理(
ObjectProvider<Self>/@Lazy自注入 / 从ApplicationContext.getBean取),用代理调用自己; - AopContext.currentProxy():开启
exposeProxy = true后,在方法内取当前代理再调用; - 重构:把“自调用”改成外部编排,消除对自调用的依赖。
代码示例
@Service
public class OrderService {
private final ObjectProvider<OrderService> self; // 方式二:自注入代理
public OrderService(ObjectProvider<OrderService> self) { this.self = self; }
public void placeOrder(Order order) {
// ❌ this.ship(order) —— 自调用,ship 上的事务/AOP 注解不生效
// ✅ 改用注入的自身代理:
self.getObject().ship(order);
}
@Transactional // 由代理调用时才会真正开启事务
public void ship(Order order) { /* 扣库存、改状态 */ }
}
// 方式三(片段):开启 exposeProxy 后
// @Configuration 上:@EnableAspectJAutoProxy(exposeProxy = true)
// 方法内:((OrderService) AopContext.currentProxy()).ship(order);
注意点
- 自调用失效同样影响
@Transactional、@Async、@Cacheable、@EventListener前的切面等一切代理型能力。 - 拆 Bean 是首选:自注入虽能用,却让类依赖自己的代理,测试与阅读都别扭;
@Lazy自注入可避免构造期循环。 AopContext.currentProxy()依赖线程本地且要 exposeProxy,拿到前先判空,别滥用。- 修复后验证:给方法打日志或用代理类名断言,确认调用确实穿过代理。
- 即使方法在别的 Bean 里,若调用方直接 new 目标类(不经容器),同样不生效——代理只存在于容器管理的关系中。
小结
代理模式默认只拦“穿过代理的外部调用”,this.method() 自调用必然失效。修复按顺序选:拆 Bean(推荐)→ 自注入代理 → AopContext(兜底)。凡是注解型横切能力,都遵循“容器 + 代理调用”这个前提。