核心结论先给:AOP 代理是在postProcessAfterInitialization才生成的。 在这之前调用的方法,事务、异步、缓存注解全部不生效——包括你自己的@PostConstruct方法体。
一、顺序表:一个 Bean 从生到死
| # | 阶段 | 源码位置 | 此刻能否安全用依赖 |
|---|---|---|---|
| 1 | 实例化(构造器) | AbstractAutowireCapableBeanFactory#createBeanInstance |
否,字段全是 null |
| 2 | 属性填充 @Autowired / @Value |
#populateBean → AutowiredAnnotationBeanPostProcessor |
是,第一个能读到依赖的时刻 |
| 3 | Aware 回调(BeanName / BeanFactory / ApplicationContext) | #invokeAwareMethods;ApplicationContextAware 在 ApplicationContextAwareProcessor |
是 |
| 4 | BeanPostProcessor#postProcessBeforeInitialization |
遍历所有 BPP | 是 |
| 5 | @PostConstruct |
InitDestroyAnnotationBeanPostProcessor(在阶段 4 内执行) |
是,但仍是原始对象 |
| 6 | InitializingBean#afterPropertiesSet → init-method |
#invokeInitMethods |
是,仍是原始对象 |
| 7 | BeanPostProcessor#postProcessAfterInitialization ← AOP 代理在这里生成 |
AbstractAutoProxyCreator |
是,返回代理对象 |
为什么 @PostConstruct 写在阶段 4 里面?
因为它本身就是一个 BeanPostProcessor 干的活(CommonAnnotationBeanPostProcessor
继承自 InitDestroyAnnotationBeanPostProcessor)。它不是「框架特判」,是插件。
二、AOP 代理到底在哪一步套上去
阶段 7 里的 AbstractAutoProxyCreator 判断这个 Bean 是否命中切点,
命中就返回一个代理对象替换掉原始对象。这解释了三件事:
- 阶段 1~6 里
this是原始对象,代理还不存在 → 事务 / 异步 / 缓存注解全部无效。 - 别人注入到的、容器里存的是代理,所以外部调用有效。
- Bean 内部
this.method()是原始对象自调用,绕过代理 → 事务失效。
三、六个最常见的失效场景
1. @PostConstruct 里调 @Transactional 方法
@PostConstruct
void init() {
this.loadFromDb(); // 无效:此时 this 还不是代理
}
解法:改到 ApplicationRunner / @EventListener(ApplicationReadyEvent.class) 里做,那时容器已就绪,注入进来的是代理。
2. 同类内部调用
public void a() { this.b(); } // 无效:绕过了代理
@Transactional public void b() { }
解法:拆到另一个 Bean;或用 AopContext.currentProxy()(需开 exposeProxy)。
3. 方法不是 public / 是 final
CGLIB 靠继承重写,final 方法重写不了;private 方法代理拦不到。protected 在部分版本可用但不建议依赖。
4. 自己 new 出来的对象
UserService s = new UserService(); // 不在容器里,没有代理,没有注入
注解是容器的能力,不是语言的能力。这条是初学者最多踩的。
5. 异常被自己吞掉
@Transactional
public void a() {
try { dao.insert(); } catch (Exception e) { log.error("", e); }
}
默认只对 RuntimeException 与 Error 回滚;吞掉异常等于告诉事务「一切正常」。受检异常要显式写 rollbackFor = Exception.class。
6. 循环依赖 + 构造器注入
Spring 解决循环依赖靠三级缓存(singletonObjects / earlySingletonObjects /
singletonFactories),前提是能先实例化再注入。构造器注入无法先给半成品,
所以直接抛 BeanCurrentlyInCreationException。
这也是为什么 Spring Boot 2.6 起默认禁止循环依赖(spring.main.allow-circular-references=false)——它本来就是设计问题。
四、一张判断图(记住这个顺序就不会错)
构造器 → 注入(@Autowired) → Aware → @PostConstruct
→ afterPropertiesSet → init-method
→ 【AOP 代理生成】 ← 从这一刻起,事务/异步/缓存才有效
→ 使用中
→ @PreDestroy → DisposableBean → destroy-method
补一个容易忽略的点:销毁阶段同样拿不到代理。@PreDestroy 里调事务方法一样无效,
而且那时数据源可能已经开始关闭了。