爪爪哇碎碎念

Spring Bean 的 7 个可挂载点

「我的注解为什么不生效」这个问题,90% 的答案都能在这张顺序表里找到。

Spring 5.3 / 6.x 含源码方法名 含 6 个失效场景
核心结论先给: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 里调事务方法一样无效, 而且那时数据源可能已经开始关闭了。

五、配套读的