Spring IoC 容器底层实现原理与 Bean 生命周期实战

Spring IoC 容器底层实现原理与 Bean 生命周期实战

版本基线 :Spring Framework 7.0.x(2025-11 GA,当前 7.0.10)/ Spring Boot 4.1.x JDK 基线 :Java 17 起,推荐 JDK 21/25 命名空间 :Spring 7 已彻底移除 javax.annotation,@PostConstruct / @PreDestroy / @Resource 一律使用 jakarta.annotation.*

文中源码片段为便于阅读做了删减(去掉异常分支、日志、防御性判断),但保留了真实的方法名、字段名与调用顺序,可对照 spring-beans / spring-context 源码阅读。


目录

  1. [本质:IoC 容器到底是什么](#本质:IoC 容器到底是什么 "#1-%E6%9C%AC%E8%B4%A8ioc-%E5%AE%B9%E5%99%A8%E5%88%B0%E5%BA%95%E6%98%AF%E4%BB%80%E4%B9%88")
  2. 容器的类型体系与职责分工
  3. 元数据层:BeanDefinition
  4. [启动主流程:refresh() 十二步](#启动主流程:refresh() 十二步 "#4-%E5%90%AF%E5%8A%A8%E4%B8%BB%E6%B5%81%E7%A8%8Brefresh-%E5%8D%81%E4%BA%8C%E6%AD%A5")
  5. [单例创建核心链路:doGetBean 到 doCreateBean](#单例创建核心链路:doGetBean 到 doCreateBean "#5-%E5%8D%95%E4%BE%8B%E5%88%9B%E5%BB%BA%E6%A0%B8%E5%BF%83%E9%93%BE%E8%B7%AFdogetbean-%E5%88%B0-docreatebean")
  6. 三级缓存与循环依赖
  7. [Bean 生命周期全景图与回调顺序](#Bean 生命周期全景图与回调顺序 "#7-bean-%E7%94%9F%E5%91%BD%E5%91%A8%E6%9C%9F%E5%85%A8%E6%99%AF%E5%9B%BE%E4%B8%8E%E5%9B%9E%E8%B0%83%E9%A1%BA%E5%BA%8F")
  8. [生命周期各环节实战(12 个真实场景)](#生命周期各环节实战(12 个真实场景) "#8-%E7%94%9F%E5%91%BD%E5%91%A8%E6%9C%9F%E5%90%84%E7%8E%AF%E8%8A%82%E5%AE%9E%E6%88%9812-%E4%B8%AA%E7%9C%9F%E5%AE%9E%E5%9C%BA%E6%99%AF")
  9. 扩展点选型决策表
  10. 常见陷阱与排查清单
  11. 一句话总结

1. 本质:IoC 容器到底是什么

剥掉所有设计模式的外衣,Spring IoC 容器在 Java 底层只做了五件事:

底层能力 解决的问题 对应 Java 技术
元数据建模 运行期才知道要创建哪些类 BeanDefinition + 注解/字节码解析
反射实例化 打破编译期的 new 硬绑定 Class.forName / Constructor.newInstance
反射注入 把依赖塞进私有字段 Field.setAccessible(true) + Field.set
缓存池 单例复用、解决循环依赖 三个 ConcurrentHashMap(三级缓存)
回调扩展 让第三方在生命周期里插手 BeanPostProcessor / BeanFactoryPostProcessor

所谓"控制反转",反转的是对象创建与装配的归属权 :从 new 关键字,交给这套基于反射 + 元数据 + Map 的运行时工厂。

一句话概括:

用 Map 存配方,用反射造对象,用反射填依赖,用缓存保单例,用回调做增强。


2. 容器的类型体系与职责分工

2.1 BeanFactory 与 ApplicationContext

这是最容易混淆的一对概念。它们不是两种实现,而是能力分层:

arduino 复制代码
BeanFactory (接口)                  ← 最底层:只定义"怎么拿 Bean"
   ↑
ListableBeanFactory                ← 增加"批量枚举"(按类型查所有 Bean)
   ↑
ConfigurableBeanFactory            ← 增加"可配置"(作用域、后置处理器、值解析器)
   ↑
AutowireCapableBeanFactory         ← 增加"自动装配能力"
   ↑
AbstractBeanFactory
   ↑
AbstractAutowireCapableBeanFactory ← 真正实现 createBean / doCreateBean
   ↑
DefaultListableBeanFactory         ← 注册中心:持有 beanDefinitionMap,Spring 的默认引擎

ApplicationContext 并不自己造 Bean,它持有一个 DefaultListableBeanFactory 并委托给它:

typescript 复制代码
// AbstractApplicationContext
public abstract class AbstractApplicationContext extends DefaultResourceLoader
        implements ConfigurableApplicationContext {
​
    // GenericApplicationContext 中的真实引用
    private final DefaultListableBeanFactory beanFactory;
​
    @Override
    public Object getBean(String name) {
        return getBeanFactory().getBean(name);   // 委托
    }
​
    @Override
    public <T> Map<String, T> getBeansOfType(Class<T> type) {
        return getBeanFactory().getBeansOfType(type);  // 委托
    }
}

2.2 两者的实际差异

维度 BeanFactory ApplicationContext
单例预加载 懒加载,首次 getBean() 才创建 启动时预创建所有非懒加载单例
事件机制 无 ApplicationEventPublisher + @EventListener
国际化 无 MessageSource
资源加载 无 ResourceLoader(classpath*: 等)
自动注册 BPP 需手动 addBeanPostProcessor 自动识别 BeanPostProcessor / BeanFactoryPostProcessor
环境抽象 无 Environment / Profile

结论 :ApplicationContext = BeanFactory + 企业服务 + 自动化的生命周期驱动。生产环境一律用 ApplicationContext。

2.3 关键类职责速查

类 职责
DefaultListableBeanFactory 持有 beanDefinitionMap,实现 preInstantiateSingletons()
AbstractAutowireCapableBeanFactory createBean / doCreateBean / populateBean / initializeBean
DefaultSingletonBeanRegistry 三级缓存、单例锁、getSingleton()
AutowiredAnnotationBeanPostProcessor 处理 @Autowired / @Value / @Inject
CommonAnnotationBeanPostProcessor 处理 @PostConstruct / @PreDestroy / @Resource
AnnotationAwareAspectJAutoProxyCreator 在初始化后生成 AOP 代理
ConfigurationClassPostProcessor 最重要的 BFPP:解析 @Configuration / @ComponentScan / @Bean / @Import
SimpleInstantiationStrategy 无参构造反射实例化
CglibSubclassingInstantiationStrategy 需要方法注入(@Lookup)时用 CGLIB 生成子类

3. 元数据层:BeanDefinition

3.1 三种配置形态 → 统一元数据

无论你用 XML、@Component 还是 @Bean,最终都被统一翻译成 BeanDefinition。这是容器能"运行期决定创建什么"的前提:

java 复制代码
XML <bean>          ──┐
@Component / @Service──┼──→ BeanDefinition ──→ beanDefinitionMap
@Bean 方法           ──┘        (配方)

3.2 BeanDefinition 关键字段

arduino 复制代码
public interface BeanDefinition extends AttributeAccessor, BeanMetadataElement {
​
    // 作用域
    String SCOPE_SINGLETON = "singleton";
    String SCOPE_PROTOTYPE = "prototype";
​
    void setScope(String scope);
    String getScope();
​
    // 目标类(反射实例化的依据)
    void setBeanClassName(String beanClassName);
    String getBeanClassName();
​
    // 构造参数值
    ConstructorArgumentValues getConstructorArgumentValues();
​
    // 属性值(XML <property> 或 @Bean 的属性)
    MutablePropertyValues getPropertyValues();
​
    // 初始化 / 销毁方法名
    void setInitMethodName(String initMethodName);
    void setDestroyMethodName(String destroyMethodName);
​
    // 自动装配模式(byType / byName / constructor)
    void setAutowireMode(int autowireMode);
​
    // 懒加载 / 主候选 / 抽象
    void setLazyInit(boolean lazyInit);
    boolean isPrimary();
    boolean isAbstract();
}

注意 getBeanClassName() 返回的是 String 而非 Class。这是刻意为之:容器可以在类尚未被 ClassLoader 加载 时就持有配方,实现懒解析,也支撑 Spring 7 的 AOT(编译期生成 BeanDefinition 而无需反射扫描)。

3.3 注册中心

typescript 复制代码
// DefaultListableBeanFactory
private final Map<String, BeanDefinition> beanDefinitionMap =
        new ConcurrentHashMap<>(256);
​
private volatile List<String> beanDefinitionNames = new ArrayList<>(256);
​
@Override
public void registerBeanDefinition(String beanName, BeanDefinition beanDefinition) {
    BeanDefinition existingDefinition = this.beanDefinitionMap.get(beanName);
    if (existingDefinition != null) {
        // 默认允许覆盖(Spring Boot 2.1+ 默认关闭,由 spring.main.allow-bean-definition-overriding 控制)
        this.beanDefinitionMap.put(beanName, beanDefinition);
    } else {
        this.beanDefinitionMap.put(beanName, beanDefinition);
        this.beanDefinitionNames.add(beanName);
    }
}

4. 启动主流程:refresh() 十二步

AbstractApplicationContext.refresh() 是整个容器的启动主心骨。理解这一段,等于理解了 Spring 启动的全部:

scss 复制代码
@Override
public void refresh() throws BeansException, IllegalStateException {
    synchronized (this.startupShutdownMonitor) {
        // 1. 准备:记录启动时间、设置 active 标志、初始化 PropertySource、校验必需环境变量
        prepareRefresh();
​
        // 2. 获取(或刷新)BeanFactory;XML 系在此处解析 XML 生成 BeanDefinition
        ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
​
        // 3. 装配 BeanFactory 基础设施:ClassLoader、SpEL 解析器、
        //    PropertyEditorRegistrar、ApplicationContextAwareProcessor、
        //    注册环境 Bean(environment / systemProperties / systemEnvironment)、
        //    忽略 ApplicationContextAware 等接口的自动注入
        prepareBeanFactory(beanFactory);
​
        try {
            // 4. 留给子类的钩子(如 AbstractRefreshableWebApplicationContext 注册 Servlet 相关作用域)
            postProcessBeanFactory(beanFactory);
​
            // 5. ★ 执行 BeanFactoryPostProcessor:修改 BeanDefinition 元数据
            invokeBeanFactoryPostProcessors(beanFactory);
​
            // 6. ★ 注册 BeanPostProcessor(注意:只是注册,不执行)
            registerBeanPostProcessors(beanFactory);
​
            // 7. 初始化 MessageSource(国际化)
            initMessageSource();
​
            // 8. 初始化 ApplicationEventMulticaster(事件广播器)
            initApplicationEventMulticaster();
​
            // 9. 子类钩子:如 SpringBoot 在此启动内嵌 Web 服务器
            onRefresh();
​
            // 10. 注册 ApplicationListener 到广播器
            registerListeners();
​
            // 11. ★★ 实例化所有非懒加载单例 ------ Bean 生命周期的真正发生地
            finishBeanFactoryInitialization(beanFactory);
​
            // 12. 完成:初始化 LifecycleProcessor、触发 onRefresh 回调、
            //     发布 ContextRefreshedEvent
            finishRefresh();
        }
        catch (RuntimeException | Error ex) {
            // 销毁已创建的单例,重置 active 标志
            destroyBeans();
            cancelRefresh(ex);
            throw ex;
        }
    }
}

4.1 第 5 步与第 6 步的顺序为什么不能颠倒

这是设计上的硬约束:

  • BeanFactoryPostProcessor 操作的是 BeanDefinition(元数据) ,作用于所有 Bean,必须在任何 Bean 实例化之前
  • BeanPostProcessor 操作的是 Bean 实例(对象)

如果先注册 BPP 再执行 BFPP,BFPP 可能会在扫描时新增 BeanDefinition,导致部分 Bean 无法被已注册的 BPP 处理。所以 Spring 强制:先跑完所有 BFPP,再统一收集并注册 BPP。

4.2 第 11 步:预实例化单例

scss 复制代码
// DefaultListableBeanFactory
@Override
public void preInstantiateSingletons() throws BeansException {
    List<String> beanNames = new ArrayList<>(this.beanDefinitionNames);
​
    for (String beanName : beanNames) {
        RootBeanDefinition bd = getMergedLocalBeanDefinition(beanName);
​
        // 非抽象 + 单例 + 非懒加载 → 立即创建
        if (!bd.isAbstract() && bd.isSingleton() && !bd.isLazyInit()) {
            if (isFactoryBean(beanName)) {
                // FactoryBean 特殊处理:创建 FactoryBean 本身,必要时再创建 getObject() 产物
                Object bean = getBean(FACTORY_BEAN_PREFIX + beanName);  // "&xxx"
                if (bean instanceof SmartFactoryBean<?> smartFactoryBean
                        && smartFactoryBean.isEagerInit()) {
                    getBean(beanName);   // 立即创建真实产物
                }
            } else {
                getBean(beanName);       // ★ 触发完整生命周期
            }
        }
    }
​
    // ★ 所有单例创建完毕后,回调 SmartInitializingSingleton
    for (String beanName : beanNames) {
        Object singletonInstance = getSingleton(beanName);
        if (singletonInstance instanceof SmartInitializingSingleton smartSingleton) {
            smartSingleton.afterSingletonsInstantiated();
        }
    }
}

关键点 :SmartInitializingSingleton 回调发生在全部单例创建完成之后。这是"全局就绪"语义最强的钩子,后面实战会重点用。


5. 单例创建核心链路:doGetBean 到 doCreateBean

5.1 doGetBean:三级缓存查找 + 递归创建

scss 复制代码
// AbstractBeanFactory
protected <T> T doGetBean(String name, Class<T> requiredType, Object[] args, boolean typeCheckOnly) {
​
    String beanName = transformedBeanName(name);   // 处理 "&" 前缀(FactoryBean)
​
    // ① 尝试从缓存拿(含早期引用)
    Object sharedInstance = getSingleton(beanName);
    if (sharedInstance != null && args == null) {
        // 可能是 FactoryBean,需要取 getObject() 产物
        return adaptBeanInstance(beanName, sharedInstance, requiredType);
    }
​
    // ② prototype 类型的循环依赖直接报错
    if (isPrototypeCurrentlyInCreation(beanName)) {
        throw new BeanCurrentlyInCreationException(beanName);
    }
​
    // ③ 父容器委派(Spring MVC 父子容器场景)
    BeanFactory parentBeanFactory = getParentBeanFactory();
    if (parentBeanFactory != null && !containsBeanDefinition(beanName)) {
        return parentBeanFactory.getBean(beanName, requiredType);
    }
​
    // ④ 取出合并后的 BeanDefinition(合并父定义的属性)
    RootBeanDefinition mbd = getMergedLocalBeanDefinition(beanName);
​
    // ⑤ 先创建当前 Bean 依赖的 depends-on Bean
    String[] dependsOn = mbd.getDependsOn();
    if (dependsOn != null) {
        for (String dep : dependsOn) {
            getBean(dep);   // 保证 dep 先于当前 Bean 创建
        }
    }
​
    // ⑥ 按作用域创建
    if (mbd.isSingleton()) {
        sharedInstance = getSingleton(beanName, () -> {
            try {
                return createBean(beanName, mbd, args);   // ★ 真正创建
            } catch (BeansException ex) {
                destroySingleton(beanName);
                throw ex;
            }
        });
        return adaptBeanInstance(beanName, sharedInstance, requiredType);
    }
    else if (mbd.isPrototype()) {
        Object prototypeInstance = createBean(beanName, mbd, args);
        return adaptBeanInstance(beanName, prototypeInstance, requiredType);
    }
    else {
        // request / session / 自定义作用域 → 从 Scope 对象获取
        String scopeName = mbd.getScope();
        Scope scope = this.scopes.get(scopeName);
        return scope.get(beanName, () -> createBean(beanName, mbd, args));
    }
}

5.2 getSingleton:单例锁 + 创建中标记

typescript 复制代码
// DefaultSingletonBeanRegistry
public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) {
    synchronized (this.singletonObjects) {          // ★ 全局单例锁,保证双重检查安全
        Object singletonObject = this.singletonObjects.get(beanName);
        if (singletonObject == null) {
            beforeSingletonCreation(beanName);      // ★ 标记"创建中",用于检测循环依赖
            boolean newSingleton = false;
            try {
                singletonObject = singletonFactory.getObject();  // 回调 createBean
                newSingleton = true;
            }
            finally {
                afterSingletonCreation(beanName);   // ★ 移除"创建中"标记
            }
            if (newSingleton) {
                addSingleton(beanName, singletonObject);  // 放入一级缓存,清理二三级
            }
        }
        return singletonObject;
    }
}

protected void beforeSingletonCreation(String beanName) {
    if (!this.singletonsCurrentlyInCreation.add(beanName)) {
        throw new BeanCurrentlyInCreationException(beanName);   // ★ 循环依赖在此检出
    }
}

protected void addSingleton(String beanName, Object singletonObject) {
    synchronized (this.singletonObjects) {
        this.singletonObjects.put(beanName, singletonObject);  // 一级
        this.singletonFactories.remove(beanName);              // 清三级
        this.earlySingletonObjects.remove(beanName);           // 清二级
        this.registeredSingletons.add(beanName);
    }
}

singletonsCurrentlyInCreation 是一个 Set<String>,它既是循环依赖的检测器,也是 isSingletonCurrentlyInCreation() 判断"是否需要提前暴露早期引用"的依据。

5.3 doCreateBean:生命周期主干

scss 复制代码
// AbstractAutowireCapableBeanFactory
protected Object doCreateBean(String beanName, RootBeanDefinition mbd, Object[] args) {

    // ① 实例化:得到"空壳"对象(属性还没填)
    BeanWrapper instanceWrapper = createBeanInstance(beanName, mbd, args);
    Object bean = instanceWrapper.getWrappedInstance();

    // ② 执行 MergedBeanDefinitionPostProcessor:预解析 @Autowired / @Value 元数据并缓存
    applyMergedBeanDefinitionPostProcessors(mbd, beanType, beanName);

    // ③ 判断是否允许提前暴露早期引用(解决循环依赖的关键开关)
    boolean earlySingletonExposure = (mbd.isSingleton()
            && this.allowCircularReferences
            && isSingletonCurrentlyInCreation(beanName));

    if (earlySingletonExposure) {
        // ★ 把"能产生早期引用的工厂"放入三级缓存
        addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
    }

    Object exposedObject = bean;

    // ④ 属性填充:依赖注入发生地
    populateBean(beanName, mbd, instanceWrapper);

    // ⑤ 初始化:Aware 回调 + BPP-before + init 方法 + BPP-after(AOP 在此)
    exposedObject = initializeBean(beanName, exposedObject, mbd);

    // ⑥ 循环依赖一致性校验
    if (earlySingletonExposure) {
        Object earlySingletonReference = getSingleton(beanName, false);  // 不查三级
        if (earlySingletonReference != null) {
            if (exposedObject == bean) {
                // 没生成代理:直接用早期引用(同一个对象,无差别)
                exposedObject = earlySingletonReference;
            }
            else if (!this.allowRawInjectionDespiteWrapping && hasDependentBean(beanName)) {
                // 生成了代理,且已有 Bean 注入了原始对象 → 引用不一致,抛异常
                throw new BeanCurrentlyInCreationException(beanName, "...");
            }
        }
    }

    // ⑦ 注册 DisposableBean(用于容器关闭时的销毁回调)
    registerDisposableBeanIfNecessary(beanName, bean, mbd);

    return exposedObject;
}

5.4 createBeanInstance:反射实例化

scss 复制代码
protected BeanWrapper createBeanInstance(String beanName, RootBeanDefinition mbd, Object[] args) {

    Class<?> beanClass = resolveBeanClass(mbd, beanName);   // Class.forName 或已缓存的 Class

    // ① 实例化前的短路机会:BPP 可以直接返回对象,跳过后续全部流程
    Object bean = resolveBeforeInstantiation(beanName, mbd);
    if (bean != null) {
        return new BeanWrapperImpl(bean);
    }

    // ② 有 @Bean 工厂方法 → 调用该工厂方法
    if (mbd.getFactoryMethodName() != null) {
        return instantiateUsingFactoryMethod(beanName, mbd, args);
    }

    // ③ 构造器注入:@Autowired 标注的构造器 或 唯一有参构造器
    Constructor<?>[] ctors = determineConstructorsFromBeanPostProcessors(beanClass, beanName);
    if (ctors != null || mbd.getResolvedAutowireMode() == AUTOWIRE_CONSTRUCTOR
            || mbd.hasConstructorArgumentValues()) {
        return autowireConstructor(beanName, mbd, ctors, args);
    }

    // ④ 默认:无参构造反射 new
    return instantiateBean(beanName, mbd);
}

private Object instantiateBean(String beanName, RootBeanDefinition mbd) {
    // SimpleInstantiationStrategy
    Constructor<?> ctor = mbd.getBeanClass().getDeclaredConstructor();
    ReflectionUtils.makeAccessible(ctor);
    return ctor.newInstance();        // ★ 等价于运行期的 new,但类型是运行期决定的
}

这就是"控制反转"的底层落点 :业务代码里没有 new,容器用 Constructor.newInstance() 在运行期替你创建。

5.5 populateBean:依赖注入

ini 复制代码
protected void populateBean(String beanName, RootBeanDefinition mbd, BeanWrapper bw) {

    // ① 实例化后、注入前的短路机会(返回 false 则跳过所有注入)
    if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {
        for (InstantiationAwareBeanPostProcessor bp : getBeanPostProcessorCache().instantiationAware) {
            if (!bp.postProcessAfterInstantiation(bw.getWrappedInstance(), beanName)) {
                return;
            }
        }
    }

    PropertyValues pvs = (mbd.hasPropertyValues() ? mbd.getPropertyValues() : null);

    // ② 按名字 / 按类型的自动装配(XML autowire="byName|byType")
    int resolvedAutowireMode = mbd.getResolvedAutowireMode();
    if (resolvedAutowireMode == AUTOWIRE_BY_NAME || resolvedAutowireMode == AUTOWIRE_BY_TYPE) {
        // ... autowireByName / autowireByType
    }

    // ③ ★ 注解驱动的注入:@Autowired / @Value / @Resource / @Inject
    if (hasInstantiationAwareBeanPostProcessors()) {
        for (InstantiationAwareBeanPostProcessor bp : getBeanPostProcessorCache().instantiationAware) {
            PropertyValues pvsToUse = bp.postProcessProperties(pvs, bw.getWrappedInstance(), beanName);
            if (pvsToUse == null) return;
            pvs = pvsToUse;
        }
    }

    // ④ 把 PropertyValues 应用到对象(XML <property> 走这里)
    applyPropertyValues(beanName, mbd, bw, pvs);
}

@Autowired 的真正注入由 AutowiredAnnotationBeanPostProcessor 完成:

java 复制代码
// AutowiredAnnotationBeanPostProcessor.AutowiredFieldElement
@Override
protected void inject(Object bean, String beanName, PropertyValues pvs) throws Throwable {
    Field field = (Field) this.member;
    Object value;
    if (this.cached) {
        value = resolvedCachedArgument(beanName, this.cachedFieldValue);
    } else {
        DependencyDescriptor desc = new DependencyDescriptor(field, this.required);
        desc.setContainingClass(bean.getClass());
        // ★ 按类型解析,多个候选再按 @Primary / @Priority / 字段名匹配
        value = beanFactory.resolveDependency(desc, beanName, autowiredBeanNames, typeConverter);
    }
    if (value != null) {
        ReflectionUtils.makeAccessible(field);
        field.set(bean, value);      // ★★ 反射写入私有字段
    }
}

为什么 private 字段能被注入? 因为 ReflectionUtils.makeAccessible(field) 内部调用了 field.setAccessible(true),关闭了 JVM 的访问权限检查(access check) 。这是反射 API 提供的能力,也是字段注入在 Java 语言层面成立的唯一原因。

性能提示 :setAccessible 在 JDK 9+ 模块系统下会受 --illegal-access 与模块导出限制,且首次调用有一定开销。Spring 通过 ReflectionUtils 内部缓存 + InjectionMetadata 预解析来摊平这部分成本。

5.6 initializeBean:初始化与增强

scss 复制代码
protected Object initializeBean(String beanName, Object bean, RootBeanDefinition mbd) {

    // ① Aware 接口回调
    invokeAwareMethods(beanName, bean);
    // BeanNameAware.setBeanName
    // BeanClassLoaderAware.setBeanClassLoader
    // BeanFactoryAware.setBeanFactory

    Object wrappedBean = bean;

    // ② BPP 前置处理(★ @PostConstruct 在此被触发)
    wrappedBean = applyBeanPostProcessorsBeforeInitialization(wrappedBean, beanName);

    // ③ 初始化方法
    invokeInitMethods(beanName, wrappedBean, mbd);
    //   3a. InitializingBean.afterPropertiesSet()
    //   3b. 自定义 init-method(@Bean(initMethod="...") 或 XML init-method)

    // ④ BPP 后置处理(★ AOP 代理在此生成)
    wrappedBean = applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName);

    return wrappedBean;
}

一个经常被误解的细节 :@PostConstruct 不是 Spring 自己的注解,它由 CommonAnnotationBeanPostProcessor 在前置处理阶段(第 ② 步)扫描并反射调用。因此它的执行顺序是:

kotlin 复制代码
BPP.postProcessBeforeInitialization  ──→  内部触发 @PostConstruct
InitializingBean.afterPropertiesSet  ──→  其次
自定义 init-method                   ──→  最后

5.7 AOP 代理的生成时机

typescript 复制代码
// AbstractAutoProxyCreator
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) {
    if (bean != null) {
        Object cacheKey = getCacheKey(bean.getClass(), beanName);
        // ★ 如果早期引用阶段已经代理过,这里就不再代理,保证引用一致
        if (this.earlyProxyReferences.remove(cacheKey) != bean) {
            return wrapIfNecessary(bean, beanName, cacheKey);
        }
    }
    return bean;
}

代理技术的选择:

情况 技术 底层
目标类实现了接口(且未强制 CGLIB) JDK 动态代理 Proxy.newProxyInstance + InvocationHandler
目标类无接口 / 强制 CGLIB CGLIB 子类 通过 ASM 直接生成 .class 字节码,覆写非 final 方法

Spring 7 变化 :7.0 起全局默认代理方式统一为 CGLIB (与 Spring Boot 的默认行为对齐,此前 @Async 等处理器默认用 JDK 代理,行为不一致)。如需对单个 Bean 改用接口代理,用新注解 @Proxyable(ProxyType.INTERFACES);也可声明名为 AutoProxyUtils.DEFAULT_PROXY_CONFIG_BEAN_NAME 的 ProxyConfig Bean 来改全局默认值。

另外 Spring 7 对 Java 24+ 应用启用了新的 ClassFileMetadataReader,改用 JDK 的 Class-File API(JEP 484)读取字节码元数据,替代内部裁剪版 ASM。这对应用透明。


6. 三级缓存与循环依赖

6.1 三个 Map 的角色

swift 复制代码
// DefaultSingletonBeanRegistry
/** 一级:成品 Bean(已完成实例化 + 注入 + 初始化) */
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);

/** 二级:早期半成品引用(已实例化,未初始化);可能是原始对象,也可能是代理 */
private final Map<String, Object> earlySingletonObjects = new ConcurrentHashMap<>(16);

/** 三级:对象工厂(能按需产出"早期引用") */
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);

查找顺序(getSingleton(beanName, allowEarlyReference)):

kotlin 复制代码
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
    Object singletonObject = this.singletonObjects.get(beanName);      // ① 一级
    if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
        singletonObject = this.earlySingletonObjects.get(beanName);    // ② 二级
        if (singletonObject == null && allowEarlyReference) {
            synchronized (this.singletonObjects) {
                singletonObject = this.singletonObjects.get(beanName);
                if (singletonObject == null) {
                    singletonObject = this.earlySingletonObjects.get(beanName);
                    if (singletonObject == null) {
                        ObjectFactory<?> factory = this.singletonFactories.get(beanName);  // ③ 三级
                        if (factory != null) {
                            singletonObject = factory.getObject();     // ★ 按需生成早期引用
                            this.earlySingletonObjects.put(beanName, singletonObject);  // 升二级
                            this.singletonFactories.remove(beanName);                   // 清三级
                        }
                    }
                }
            }
        }
    }
    return singletonObject;
}

注意第 ② 步的 isSingletonCurrentlyInCreation(beanName) 前置判断:只有正在创建中的 Bean 才允许提前暴露。正常获取一个不存在的 Bean,不会误取到半成品。

6.2 完整时序推演(A ↔ B 循环依赖,且 A 需要 AOP 代理)

less 复制代码
getBean(A)
 ├─ 一级无 → beforeSingletonCreation(A) 标记 A 创建中
 ├─ instantiate(A) → A@raw(空壳,属性为空)
 ├─ earlySingletonExposure=true → addSingletonFactory(A, ObjectFactory)
 │     三级缓存: A → () -> getEarlyBeanReference(A)
 ├─ populateBean(A)
 │     └─ 需要 B → getBean(B)
 │            ├─ 一级无 → beforeSingletonCreation(B)
 │            ├─ instantiate(B) → B@raw
 │            ├─ 三级缓存: B → () -> getEarlyBeanReference(B)
 │            ├─ populateBean(B)
 │            │     └─ 需要 A → getSingleton(A, true)
 │            │            ├─ 一级:无
 │            │            ├─ 二级:无
 │            │            ├─ 三级:命中 → factory.getObject()
 │            │            │     └─ getEarlyBeanReference(A) → A@proxy(提前生成代理!)
 │            │            │     └─ earlyProxyReferences.put(A, A@proxy)  ← 记录,防重复代理
 │            │            └─ 二级缓存: A → A@proxy,清三级 A
 │            │     └─ field.set(B, A@proxy)   ← B 持有的是代理,不是原始对象
 │            ├─ initializeBean(B) → B 完成
 │            └─ addSingleton(B) → B 进一级缓存
 │     └─ field.set(A, B)          ← A 拿到完整 B
 ├─ initializeBean(A)
 │     └─ postProcessAfterInitialization(A)
 │            └─ earlyProxyReferences.remove(A) == A@proxy ≠ A@raw
 │               → 判定"已代理过",直接返回 A@raw,不再重复代理
 ├─ ⑥ 一致性校验:earlySingletonReference(A)=A@proxy ≠ exposedObject(A@raw)
 │     但 exposedObject == bean(原始对象本身),所以 exposedObject = A@proxy  ★ 统一为代理
 └─ addSingleton(A) → A@proxy 进一级缓存

结果 :B 里的 A 和容器里的 A 是同一个代理对象 A@proxy,AOP 完全生效。

6.3 为什么三级缓存必须是 ObjectFactory,而不是直接存对象

这是整个设计中最精巧的一点,本质是一个延迟决策(lazy decision) :

typescript 复制代码
protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {
    Object exposedObject = bean;
    if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {
        for (SmartInstantiationAwareBeanPostProcessor bp :
                getBeanPostProcessorCache().smartInstantiationAware) {
            // ★ 只有真正被别人提前引用时,才在这里决定是否生成代理
            exposedObject = bp.getEarlyBeanReference(exposedObject, beanName);
        }
    }
    return exposedObject;
}

如果退化成二级缓存(实例化后立刻把对象放进去),会面临两难:

方案 问题
二级存原始对象 若 Bean 需要 AOP,B 注入的是原始对象 → 与最终成品不一致 → AOP 失效
二级存提前生成的代理 必须在每个 Bean 实例化后就跑一遍 AOP 判断 → 绝大多数 Bean 根本不会被循环依赖,白白付出代理扫描与生成的性能代价

三级缓存的 ObjectFactory 把"是否生成代理"这个决策推迟到真正有人来取早期引用的那一刻:

  • 无循环依赖 → 三级缓存的 getObject() 从不被调用 → 代理照常在初始化末尾生成,零额外开销
  • 有循环依赖 → 只在被提前引用时才生成代理 → 且用 earlyProxyReferences 记录,避免后续重复代理

一句话:三级缓存不是为了解决循环依赖,而是为了在解决循环依赖的同时,不破坏 AOP 代理的一致性,且不为不需要的 Bean 付出代价。

6.4 三种"破功"场景与解法

场景 报错/现象 根因 解法
构造器注入循环依赖 BeanCurrentlyInCreationException 实例化阶段就要拿到完整依赖,连"空壳 A"都 new 不出来,三级缓存在第一步就无东西可放 ① 改 Setter/字段注入 ② 加 @Lazy 生成延迟代理 ③ 用 ObjectProvider<T> ④ 重构消除循环(推荐)
prototype 作用域循环依赖 BeanCurrentlyInCreationException prototype 不进三级缓存,无法提前暴露 改为单例,或手工管理生命周期
@Async + 循环依赖 不报错,但异步失效 @Async 的 AsyncAnnotationBeanPostProcessor 是普通 BPP,没有实现 getEarlyBeanReference,早期引用是原始对象 ① 消除循环依赖(推荐) ② 把异步逻辑发成 ApplicationEvent ③ 用 ObjectProvider 延迟获取

架构建议 :在 Spring 7 + AOT/GraalVM 原生镜像的趋势下,循环依赖的代价越来越大(AOT 要求编译期确定 Bean 关系)。写出循环依赖不应被视为"技术能力",而应视为架构缺陷。Spring 的三级缓存是兜底,不是鼓励。


7. Bean 生命周期全景图与回调顺序

7.1 主流程图

scss 复制代码
┌──────────────────────────────────────────────────────────────┐
│ 阶段一:容器启动准备                                            │
│  prepareRefresh → obtainFreshBeanFactory → prepareBeanFactory │
└──────────────────────────────────────────────────────────────┘
                            ↓
┌──────────────────────────────────────────────────────────────┐
│ 阶段二:元数据加工(无对象创建)                                 │
│  BeanDefinitionRegistryPostProcessor.postProcessBeanDefinition│
│  Registry()          ← 可注册新 BeanDefinition                │
│  BeanFactoryPostProcessor.postProcessBeanFactory()            │
│                      ← 可修改已有 BeanDefinition              │
│  registerBeanPostProcessors()  ← 仅注册 BPP,不执行           │
└──────────────────────────────────────────────────────────────┘
                            ↓
┌──────────────────────────────────────────────────────────────┐
│ 阶段三:单个 Bean 的创建(preInstantiateSingletons 循环触发)   │
│                                                               │
│  ① InstantiationAwareBeanPostProcessor                       │
│     .postProcessBeforeInstantiation()   ← 可短路返回自定义对象 │
│                            ↓                                  │
│  ② 实例化:构造器反射 / 工厂方法 / CGLIB                       │
│                            ↓                                  │
│  ③ MergedBeanDefinitionPostProcessor                          │
│     .postProcessMergedBeanDefinition()  ← 预解析注入元数据     │
│                            ↓                                  │
│  ④ addSingletonFactory()  ← 三级缓存暴露 ObjectFactory         │
│                            ↓                                  │
│  ⑤ InstantiationAwareBeanPostProcessor                        │
│     .postProcessAfterInstantiation()    ← 可中止注入           │
│                            ↓                                  │
│  ⑥ InstantiationAwareBeanPostProcessor                        │
│     .postProcessProperties()  ← ★ @Autowired / @Value / @Resource│
│                            ↓                                  │
│  ⑦ Aware 回调                                                  │
│     BeanNameAware → BeanClassLoaderAware → BeanFactoryAware   │
│     → EnvironmentAware → ApplicationContextAware ...          │
│                            ↓                                  │
│  ⑧ BeanPostProcessor.postProcessBeforeInitialization()         │
│     ★ @PostConstruct 在此被 CommonAnnotationBeanPostProcessor │
│       反射调用                                                 │
│                            ↓                                  │
│  ⑨ InitializingBean.afterPropertiesSet()                      │
│                            ↓                                  │
│  ⑩ 自定义 init-method                                          │
│                            ↓                                  │
│  ⑪ BeanPostProcessor.postProcessAfterInitialization()          │
│     ★ AOP 代理在此生成                                         │
│                            ↓                                  │
│  ⑫ 放入一级缓存 singletonObjects                               │
└──────────────────────────────────────────────────────────────┘
                            ↓
┌──────────────────────────────────────────────────────────────┐
│ 阶段四:全部就绪                                                │
│  SmartInitializingSingleton.afterSingletonsInstantiated()     │
│  LifecycleProcessor.onRefresh() / SmartLifecycle.start()      │
│  ContextRefreshedEvent                                        │
│  ApplicationRunner / CommandLineRunner                        │
│  ApplicationReadyEvent                                        │
└──────────────────────────────────────────────────────────────┘
                            ↓
┌──────────────────────────────────────────────────────────────┐
│ 阶段五:运行期(Bean 被使用)                                   │
└──────────────────────────────────────────────────────────────┘
                            ↓
┌──────────────────────────────────────────────────────────────┐
│ 阶段六:销毁(context.close() / JVM shutdown hook)           │
│  @PreDestroy(DestructionAwareBeanPostProcessor)             │
│  DisposableBean.destroy()                                     │
│  自定义 destroy-method                                        │
│  SmartLifecycle.stop()                                        │
│  ContextClosedEvent                                           │
└──────────────────────────────────────────────────────────────┘

7.2 完整回调顺序表

# 环节 扩展点 / 接口 此时 Bean 的状态
1 注册 BD BeanDefinitionRegistryPostProcessor 无对象
2 修改 BD BeanFactoryPostProcessor 无对象
3 实例化前 InstantiationAwareBeanPostProcessor#postProcessBeforeInstantiation 无对象,可短路
4 实例化 构造器 空壳
5 合并 BD 后处理 MergedBeanDefinitionPostProcessor 空壳
6 暴露早期引用 三级缓存 ObjectFactory 空壳
7 实例化后 postProcessAfterInstantiation 空壳,可中止注入
8 属性注入 postProcessProperties(@Autowired / @Value) 依赖已就位
9 Aware 回调 BeanNameAware / BeanFactoryAware / ApplicationContextAware 依赖已就位
10 初始化前 BeanPostProcessor#postProcessBeforeInitialization( @PostConstruct) 依赖已就位
11 初始化中 InitializingBean#afterPropertiesSet 依赖已就位
12 初始化方法 init-method 依赖已就位
13 初始化后 BeanPostProcessor#postProcessAfterInitialization(AOP 代理) 可能被替换成代理
14 全局就绪 SmartInitializingSingleton 全部单例已就绪
15 生命周期启动 SmartLifecycle#start(按 getPhase() 排序) 容器刷新完成
16 应用就绪 ApplicationRunner / CommandLineRunner 完全就绪
17 销毁前 @PreDestroy 运行中
18 销毁中 DisposableBean#destroy 运行中
19 销毁方法 destroy-method 运行中

8. 生命周期各环节实战(12 个真实场景)

这一节回答核心问题:每个环节到底该在什么业务里用。每个场景给出"为什么选这个环节"的理由,因为选错环节是生产事故的高发区。


场景 1:BeanDefinitionRegistryPostProcessor ------ 批量动态注册 Bean

业务问题:MyBatis 有几十个 Mapper 接口,每个都只有接口没有实现类,怎么变成 Bean?

为什么选这个环节 :这是唯一能在容器实例化任何 Bean 之前,批量往容器里塞 BeanDefinition 的时机。

typescript 复制代码
// MyBatis 的 MapperScannerConfigurer 就是这个思路的简化版
public class MapperScannerPostProcessor implements BeanDefinitionRegistryPostProcessor {

    @Override
    public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
        ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);
        scanner.addIncludeFilter(new AnnotationTypeMapperFilter());
        scanner.scan("com.example.**.mapper");   // 扫描包
    }

    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory bf) {
        // 无需操作实例,留空
    }
}

// 扫描器内部:把接口注册成 MapperFactoryBean(FactoryBean 模式)
class ClassPathMapperScanner extends ClassPathBeanDefinitionScanner {
    @Override
    protected Set<BeanDefinitionHolder> doScan(String... basePackages) {
        Set<BeanDefinitionHolder> holders = super.doScan(basePackages);
        for (BeanDefinitionHolder holder : holders) {
            GenericBeanDefinition definition = (GenericBeanDefinition) holder.getBeanDefinition();
            // 关键:把 beanClass 换成 FactoryBean,并把原接口名作为构造参数
            definition.getConstructorArgumentValues()
                      .addGenericArgumentValue(definition.getBeanClassName());
            definition.setBeanClass(MapperFactoryBean.class);
        }
        return holders;
    }
}

FactoryBean 是这里的点睛之笔:getBean("userMapper") 返回的是 MapperFactoryBean.getObject() 的代理产物;getBean("&userMapper") 才返回 FactoryBean 本身。

同类应用 :OpenFeign 的 @FeignClient 注册、Spring Cloud 的 @LoadBalanced RestTemplate 标记、Dubbo 的 @DubboReference。


场景 2:Spring 7 的 BeanRegistrar ------ 更干净的编程式注册

业务问题 :需要根据环境/配置条件化地注册多个 Bean ,且要避免 BeanFactoryPostProcessor 的陷阱(见场景 10)。

为什么选这个环节 :Spring 7 新增的 BeanRegistrar 就是为这类场景设计的一等公民,替代手写 BFPP。

kotlin 复制代码
class ClientsRegistrar implements BeanRegistrar {

    @Override
    public void register(BeanRegistry registry, Environment env) {
        // 无条件注册
        registry.registerBean("httpClientConfig", HttpClientConfig.class);

        // 条件注册
        if (env.matchesProfiles("eu")) {
            registry.registerBean(RegionRouter.class,
                    spec -> spec.supplier(ctx -> new RegionRouter("eu-west-1")));
        }
        // 带别名的注册
        registry.registerBean("primaryDs", DataSource.class,
                spec -> spec.supplier(ctx -> buildDataSource(env))
                            .primary()
                            .lazyInit());
    }
}

@Configuration
@Import(ClientsRegistrar.class)
class AppConfig { }

与 BFPP 的关键区别 :BeanRegistrar 在 ConfigurationClassPostProcessor 处理 @Import 时执行,不会提前触发普通 Bean 的创建,天然规避了 BFPP 的经典陷阱。Spring 7 新项目优先用它。


场景 3:BeanFactoryPostProcessor ------ 配置解密 / 占位符解析

业务问题 :数据库密码在配置中心是密文(ENC(xxx)),需要在注入前解密。

为什么选这个环节 :BFPP 能修改 BeanDefinition 的 PropertyValues,让后续注入直接拿到明文。

typescript 复制代码
@Component
public class DecryptingBeanFactoryPostProcessor implements BeanFactoryPostProcessor, EnvironmentAware {

    private Environment environment;

    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {
        String[] names = beanFactory.getBeanDefinitionNames();
        for (String name : names) {
            BeanDefinition bd = beanFactory.getBeanDefinition(name);
            for (PropertyValue pv : bd.getPropertyValues().getPropertyValueList()) {
                Object value = pv.getValue();
                if (value instanceof String s && s.startsWith("ENC(") && s.endsWith(")")) {
                    String cipher = s.substring(4, s.length() - 1);
                    pv.setConvertedValue(AesUtil.decrypt(cipher, getKey()));  // 原地替换为明文
                }
            }
        }
    }

    @Override
    public void setEnvironment(Environment environment) { this.environment = environment; }
}

注意 :这个实现只能处理 BeanDefinition 里显式声明的属性值。如果用 @Value("${db.password}") 注解注入,值解析发生在 postProcessProperties 阶段,BFPP 改不到。那种情况应该用 PropertySourcesPlaceholderConfigurer 自定义 PropertySource,或自定义 ConversionService。


场景 4:postProcessBeforeInstantiation ------ 短路创建,返回自定义对象

业务问题 :RPC 消费者 Bean 数量庞大(几百个 @RpcReference 接口),启动时全部建连太慢;希望注入的是一个惰性代理,首次调用时才建连。

为什么选这个环节 :这是唯一能在构造器执行之前就返回一个完全不同对象的时机,能彻底跳过 Spring 的实例化与初始化流程。

kotlin 复制代码
@Component
public class RpcReferencePostProcessor implements InstantiationAwareBeanPostProcessor {

    @Override
    public Object postProcessBeforeInstantiation(Class<?> beanClass, String beanName)
            throws BeansException {
        // 只处理带 @RpcReference 标记的接口
        if (!beanClass.isInterface() || !beanClass.isAnnotationPresent(RpcReference.class)) {
            return null;   // 返回 null = 走正常流程
        }

        RpcReference ann = beanClass.getAnnotation(RpcReference.class);

        // ★ 直接返回 JDK 动态代理,Spring 不再实例化/注入/初始化这个 Bean
        return Proxy.newProxyInstance(
                beanClass.getClassLoader(),
                new Class<?>[]{ beanClass },
                (proxy, method, args) -> {
                    if (method.getDeclaringClass() == Object.class) {
                        return method.invoke(proxy, args);
                    }
                    // 首次调用才真正解析服务地址、建立连接
                    RpcInvoker invoker = ServiceRegistry.lookup(ann.serviceName());
                    return invoker.invoke(method, args);
                });
    }
}

同类应用 :单元测试中注入 Mock 对象、AOP 的 TargetSource 机制(AbstractAutoProxyCreator 内部就是这个思路)、Spring 的 @Lookup 方法注入。


场景 5:postProcessProperties ------ 自定义注解注入

业务问题 :想做一个自己的注入注解 @ConfigValue("key"),从配置中心(Nacos/Apollo)取值并支持自动刷新。

为什么选这个环节 :这是 Spring 官方推荐的自定义注入 扩展点,比自己扫字段再 field.set 更规范------它能复用 Spring 的类型转换、依赖解析和元数据缓存。

scss 复制代码
@Component
public class ConfigValuePostProcessor implements InstantiationAwareBeanPostProcessor {

    @Autowired
    private ConfigurableBeanFactory beanFactory;

    @Autowired
    private NacosConfigService nacos;

    @Override
    public PropertyValues postProcessProperties(PropertyValues pvs, Object bean, String beanName) {
        ReflectionUtils.doWithLocalFields(bean.getClass(), field -> {
            ConfigValue ann = field.getAnnotation(ConfigValue.class);
            if (ann == null) return;

            ReflectionUtils.makeAccessible(field);

            // 初始值
            String raw = nacos.getConfig(ann.value(), ann.defaultValue());
            field.set(bean, beanFactory.getTypeConverter()
                                .convertIfNecessary(raw, field.getType()));

            // 注册配置变更监听,实现热更新
            nacos.addListener(ann.value(), newValue ->
                    field.set(bean, beanFactory.getTypeConverter()
                                        .convertIfNecessary(newValue, field.getType())));
        });
        return pvs;
    }
}

场景 6:@PostConstruct ------ 本 Bean 内部资源初始化

业务问题:一个本地字典缓存组件,需要在 Bean 自身依赖就位后加载字典数据。

为什么选这个环节 :@PostConstruct 执行时,当前 Bean 的所有注入已完成,可以安全使用自己的依赖。

typescript 复制代码
@Component
public class DictCache {

    @Autowired
    private DictMapper dictMapper;

    private volatile Map<String, String> cache = Map.of();

    // Spring 7 起:import jakarta.annotation.PostConstruct;
    @PostConstruct
    public void init() {
        Map<String, String> loaded = dictMapper.selectAll().stream()
                .collect(Collectors.toMap(Dict::getCode, Dict::getName));
        this.cache = Map.copyOf(loaded);
        log.info("字典缓存加载完成,共 {} 条", loaded.size());
    }

    public String getName(String code) {
        return cache.get(code);
    }
}

⚠️ 重要局限 :@PostConstruct 只保证当前 Bean 的依赖已注入,不保证其他 Bean 已初始化 。如果 init() 里调用了一个尚未创建的 Bean 的方法,可能拿到半初始化状态的对象。 需要"全局就绪"语义时,请改用场景 8 的 SmartInitializingSingleton 或场景 9 的 ApplicationRunner。


场景 7:InitializingBean / init-method ------ 配置校验与第三方客户端启动

业务问题 :Netty 服务端组件的端口、线程数配置不能靠人肉检查,错了要在启动阶段直接失败(fail-fast),而不是运行到一半崩。

为什么选这个环节 :afterPropertiesSet() 的语义就是"所有属性设置完毕后的校验与启动",抛异常会直接中断容器启动。

java 复制代码
@Component
public class NettyServerComponent implements InitializingBean, DisposableBean {

    @Value("${netty.port:9000}")
    private int port;

    @Value("${netty.boss-threads:1}")
    private int bossThreads;

    private EventLoopGroup bossGroup;
    private EventLoopGroup workerGroup;
    private Channel serverChannel;

    @Override
    public void afterPropertiesSet() throws Exception {
        // ★ fail-fast 校验
        Assert.isTrue(port > 1024 && port < 65535, "netty.port 必须在 (1024, 65535) 区间");
        Assert.isTrue(bossThreads > 0, "netty.boss-threads 必须大于 0");

        bossGroup   = new NioEventLoopGroup(bossThreads);
        workerGroup = new NioEventLoopGroup();

        ServerBootstrap b = new ServerBootstrap();
        b.group(bossGroup, workerGroup)
         .channel(NioServerSocketChannel.class)
         .childHandler(new ServerInitializer());

        ChannelFuture f = b.bind(port).sync();
        serverChannel = f.channel();
        log.info("Netty 服务启动于端口 {}", port);
    }

    @Override
    public void destroy() { /* 见场景 11 */ }
}

InitializingBean vs init-method 怎么选:

  • InitializingBean --- 与 Spring API 强耦合,不推荐用于业务代码
  • @Bean(initMethod = "start") --- 推荐 ,尤其集成第三方类时(无法改源码加注解)
  • @PostConstruct --- 推荐用于自研 Bean,标准注解,无 Spring 耦合

场景 8:SmartInitializingSingleton ------ 全局就绪后的缓存预热

业务问题 :商品详情页的热点缓存,预热时必须访问多个其他 Bean (价格服务、库存服务、促销服务)。用 @PostConstruct 有风险------那些 Bean 可能还没创建。

为什么选这个环节 :afterSingletonsInstantiated() 在 preInstantiateSingletons() 的第二轮循环 中被调用,此时所有非懒加载单例都已创建完毕。这是"全局就绪"语义最强的回调。

scss 复制代码
@Component
public class HotItemCacheWarmer implements SmartInitializingSingleton {

    @Autowired
    private PriceService priceService;
    @Autowired
    private StockService stockService;
    @Autowired
    private PromotionService promotionService;
    @Autowired
    private RedisTemplate<String, Object> redis;

    @Override
    public void afterSingletonsInstantiated() {
        // ★ 此刻所有依赖的 Bean 都已 100% 就绪,可以安全调用
        List<Long> hotItemIds = loadHotItemIds();

        hotItemIds.parallelStream().forEach(id -> {
            ItemSnapshot snapshot = new ItemSnapshot();
            snapshot.setPrice(priceService.getPrice(id));
            snapshot.setStock(stockService.getStock(id));
            snapshot.setPromotion(promotionService.getActivePromotion(id));
            redis.opsForValue().set("item:snapshot:" + id, snapshot, Duration.ofMinutes(30));
        });

        log.info("热点商品缓存预热完成,共 {} 个", hotItemIds.size());
    }
}

对比:@PostConstruct 在这里会有什么问题?

假设 PromotionService 本身还有一个 @PostConstruct 且它依赖 HotItemCacheWarmer 之外的东西------预热时 promotionService 可能处于"注入完成但未初始化"的状态,调用它的方法可能 NPE 或返回默认值。SmartInitializingSingleton 彻底规避了这个风险。


场景 9:SmartLifecycle ------ 控制启动/停止顺序的组件

业务问题 :MQ 消费者必须在所有业务 Bean 就绪后 才开始消费,否则消息进来时依赖的服务还没初始化,直接报错。容器关闭时,消费者也要先停止再关数据源。

为什么选这个环节 :Lifecycle 提供 start()/stop(),SmartLifecycle 额外提供 getPhase() 做相位排序:

  • phase 越小越先 start(),越后 stop()
  • 默认 SmartLifecycle 的 phase = Integer.MAX_VALUE(最后启动)
  • isAutoStartup() 返回 true 时容器刷新完成自动启动
typescript 复制代码
@Component
public class OrderMessageConsumer implements SmartLifecycle {

    private volatile boolean running = false;

    @Autowired
    private RocketMQListenerContainer container;
    @Autowired
    private OrderService orderService;   // 必须已就绪

    @Override
    public void start() {
        if (running) return;
        // 前置确认:依赖 Bean 确实可用
        Assert.notNull(orderService, "OrderService 未就绪");
        container.start();
        running = true;
        log.info("订单消息消费者已启动");
    }

    @Override
    public void stop() {
        // ★ 优雅停止:先拒绝新消息,再等待在途消息处理完
        container.pause();
        awaitInFlightMessages(30, TimeUnit.SECONDS);
        container.shutdown();
        running = false;
        log.info("订单消息消费者已优雅停止");
    }

    @Override
    public boolean isRunning() { return running; }

    @Override
    public boolean isAutoStartup() { return true; }

    @Override
    public int getPhase() {
        // 比默认值小一点 → 比大多数组件先启动
        // 数据源、连接池通常由 Spring Boot 管理,phase 更靠前
        return Integer.MAX_VALUE - 1000;
    }
}

典型应用:MQ 消费者、定时任务调度器、注册中心心跳、内嵌 Netty/Grpc 服务。


场景 10:BeanPostProcessor ------ 统一处理自定义注解(AOP 前置)

业务问题 :想做一个 @DistributedLock 注解,标在方法上自动加分布式锁。

为什么选这个环节 :postProcessAfterInitialization 是生成代理的标准时机,此时 Bean 已完成初始化,可以安全包装。

less 复制代码
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface DistributedLock {
    String key();
    long waitSeconds() default 3;
    long leaseSeconds() default 30;
}
ini 复制代码
@Component
public class DistributedLockPostProcessor implements BeanPostProcessor {

    @Autowired
    private RedissonClient redisson;

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) {
        Class<?> targetClass = AopProxyUtils.ultimateTargetClass(bean);

        // 快速判断:类里有没有标注该注解的方法,没有直接返回(性能)
        if (!hasLockedMethod(targetClass)) {
            return bean;
        }

        return ProxyFactory.getProxy(targetClass, (MethodInterceptor) invocation -> {
            Method method = invocation.getMethod();
            DistributedLock ann = AnnotationUtils.findAnnotation(method, DistributedLock.class);
            if (ann == null) {
                return invocation.proceed();
            }

            // 支持 SpEL 的锁 key
            String lockKey = parseSpel(ann.key(), method, invocation.getArguments());
            RLock lock = redisson.getLock(lockKey);

            boolean acquired = lock.tryLock(ann.waitSeconds(), ann.leaseSeconds(), TimeUnit.SECONDS);
            if (!acquired) {
                throw new BizException("获取锁失败,请重试: " + lockKey);
            }
            try {
                return invocation.proceed();
            } finally {
                lock.unlock();
            }
        });
    }
}

⚠️ 致命陷阱 :BeanPostProcessor 自身不能依赖普通 Bean 并用字段注入 。因为 BPP 必须在所有普通 Bean 之前实例化,此时 AutowiredAnnotationBeanPostProcessor 还没生效,注入会失败或静默为 null。

症状是 Spring Boot 启动日志的警告:

rust 复制代码
Bean 'xxxPostProcessor' of type [...] is not eligible for getting processed
by all BeanPostProcessors (for example: not eligible for auto-proxying)

解法 :① 改用构造器注入 ② 实现 ApplicationContextAware 手动 getBean ③ 用 ObjectProvider 延迟获取 ④ @Lazy 注入。


场景 11:@PreDestroy / DisposableBean ------ 优雅关闭资源

业务问题:容器关闭时,线程池里的任务不能粗暴中断,MQ 连接要优雅断开,注册中心要先注销再关闭。

为什么选这个环节 :这是官方提供的销毁钩子,@PreDestroy 在销毁链最前,能确保所有连接还可用。

java 复制代码
@Component
public class GracefulResourceHolder implements DisposableBean {

    private final ExecutorService bizPool = new ThreadPoolExecutor(
            8, 8, 0L, TimeUnit.MILLISECONDS,
            new LinkedBlockingQueue<>(10_000),
            new ThreadFactoryBuilder().setNameFormat("biz-pool-%d").build());

    @Autowired
    private ServiceRegistry registry;    // 注册中心客户端

    // Spring 7 起:import jakarta.annotation.PreDestroy;
    @PreDestroy
    public void deregister() {
        // ★ 最先执行:此时所有连接仍可用,先把自己从注册中心摘掉
        //   (摘掉后上游流量会先切走,再关本地资源,实现无损下线)
        try {
            registry.deregister(currentInstanceId());
            Thread.sleep(5_000);   // 等待上游感知,时间要大于注册中心心跳间隔
        } catch (Exception e) {
            log.warn("注销注册中心失败,继续关闭流程", e);
        }
    }

    @Override
    public void destroy() throws Exception {
        // ★ 后执行:优雅关闭线程池
        bizPool.shutdown();
        if (!bizPool.awaitTermination(30, TimeUnit.SECONDS)) {
            List<Runnable> dropped = bizPool.shutdownNow();
            log.warn("线程池超时未关闭,丢弃 {} 个任务", dropped.size());
        }
        log.info("业务线程池已关闭");
    }
}

销毁顺序速记 :@PreDestroy → DisposableBean.destroy() → destroy-method。

Spring Boot 优雅关闭配套配置(必须开启才生效):

yaml 复制代码
server:
  shutdown: graceful        # 内嵌 Web 服务器等待请求处理完
spring:
  lifecycle:
    timeout-per-shutdown-phase: 30s

场景 12:Aware 接口 ------ 拿到容器基础设施(谨慎使用)

业务问题:一个通用事件工具,需要在非 Bean 管理的类里发布 Spring 事件。

为什么选这个环节 :ApplicationContextAware 在 ApplicationContextAwareProcessor(一个普通的 BeanPostProcessor)中被回调,是获取容器句柄的标准方式。

typescript 复制代码
@Component
public class SpringContextHolder implements ApplicationContextAware {

    private static ApplicationContext CONTEXT;

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) {
        CONTEXT = applicationContext;
    }

    public static <T> T getBean(Class<T> type) {
        Assert.notNull(CONTEXT, "ApplicationContext 尚未初始化");
        return CONTEXT.getBean(type);
    }

    public static void publish(Object event) {
        CONTEXT.publishEvent(event);
    }

    public static String resolvePlaceholder(String key) {
        return CONTEXT.getEnvironment().getProperty(key);
    }
}

⚠️ 这是反模式 。静态持有 ApplicationContext 会:

  1. 把 IoC 又变回"主动查找"(Service Locator),破坏控制反转
  2. 让依赖关系在编译期不可见,重构时极易踩坑
  3. 单元测试时必须先启动容器,难以 mock

更好的方案:

  • 需要延迟获取 → ObjectProvider<T>
  • 需要解耦通信 → ApplicationEventPublisher 直接注入
  • 需要多个实现 → 注入 List<Interface> 或 Map<String, Interface>

只在框架代码 / 遗留系统适配中使用,业务代码应避免。


9. 扩展点选型决策表

你的需求 首选扩展点 备选 千万别用
批量注册 Bean(接口转代理) BeanDefinitionRegistryPostProcessor 或 Spring 7 BeanRegistrar ImportBeanDefinitionRegistrar 手动 new 后塞进 Map
修改已有 BeanDefinition BeanFactoryPostProcessor --- 在 BPP 里改 BD
替换整个 Bean 的创建 postProcessBeforeInstantiation FactoryBean ---
自定义注入注解 postProcessProperties 字段反射 + field.set 在 postProcessAfterInitialization 里注入
本 Bean 内部初始化 @PostConstruct @Bean(initMethod=...) 构造器里做重活
配置校验 / fail-fast InitializingBean / @PostConstruct + Assert --- 等到运行时才报错
依赖多个 Bean 的全局初始化 SmartInitializingSingleton ApplicationRunner @PostConstruct
需要启停顺序控制 SmartLifecycle(getPhase()) @EventListener(ContextRefreshedEvent) 普通 @PostConstruct
给 Bean 加代理 / 增强 postProcessAfterInitialization @Aspect 切面 ---
资源清理 / 优雅下线 @PreDestroy + DisposableBean ContextClosedEvent JVM Runtime.addShutdownHook
应用完全就绪后执行 ApplicationRunner / CommandLineRunner ApplicationReadyEvent ContextRefreshedEvent(会触发多次)

三个"就绪"钩子的精确区别

钩子 触发时机 触发次数 事务/Web 容器状态
SmartInitializingSingleton 所有单例创建完毕,但Web 服务器可能未启动 1 次 无事务上下文
ContextRefreshedEvent 容器刷新完成 可能多次(子容器刷新也会触发) Web 服务器已启动
ApplicationRunner / ApplicationReadyEvent Spring Boot 完全就绪 1 次 Web 服务器已监听端口

结论 :Spring Boot 应用做启动预热,优先用 ApplicationReadyEvent 或 ApplicationRunner。


10. 常见陷阱与排查清单

陷阱 1:自调用导致 AOP 失效

typescript 复制代码
@Service
public class OrderService {
    public void createOrder() {
        // ❌ this 是原始对象,不是代理!事务/缓存/锁全都不生效
        this.deductStock();
    }

    @Transactional
    public void deductStock() { ... }
}

根因 :Spring AOP 是代理模式,只有通过代理对象 调用才会走拦截器链。this.xxx() 绕过了代理。

解法(按推荐度排序):

  1. 拆成两个 Bean(最干净,也顺便消除了循环依赖隐患)
  2. 自注入代理:@Autowired private OrderService self; 然后 self.deductStock()
  3. AopContext.currentProxy()(需 @EnableAspectJAutoProxy(exposeProxy = true),侵入性强)

陷阱 2:private / final / static 方法上的 AOP 失效

  • private:CGLIB 无法覆写(子类访问不到)
  • final 方法 / final 类:CGLIB 无法继承覆写
  • static:不属于实例

Spring 7 提醒 :7.0 起默认代理改为 CGLIB,final 方法的坑会比以前更容易踩到。升级时需检查。

陷阱 3:BeanPostProcessor 过早依赖普通 Bean

见场景 10。症状是启动日志出现 is not eligible for getting processed by all BeanPostProcessors。解决用构造器注入或 ObjectProvider。

陷阱 4:单例里注入 prototype 失效

kotlin 复制代码
@Service
public class OrderService {
    @Autowired
    private OrderCommand command;   // ❌ prototype 只注入了一次,永远是同一个实例
}

解法:

  1. ObjectProvider<OrderCommand> + getObject()(推荐)
  2. @Lookup 方法注入(CGLIB 生成子类)
  3. @Scope(value = "prototype", proxyMode = ScopedProxyMode.TARGET_CLASS)(作用域代理)

陷阱 5:构造器注入的循环依赖

typescript 复制代码
@Service
public class A {
    public A(B b) { ... }      // ❌ 启动直接失败
}
@Service
public class B {
    public B(A a) { ... }
}

解法:

  1. 重构(推荐):抽出一个 C,让 A、B 都依赖 C
  2. 改 Setter 注入
  3. @Lazy 注解在构造参数上:public A(@Lazy B b) ------ 注入一个延迟代理
  4. ObjectProvider<B>:真正用到时才 getObject()

陷阱 6:在 @PostConstruct 里访问未就绪的 Bean

见场景 6 vs 场景 8。需要全局就绪语义时改用 SmartInitializingSingleton。

陷阱 7:@Transactional 的回滚误解

  • 默认只在 unchecked exception (RuntimeException / Error)时回滚,checked exception 会提交
  • 方法内部 try-catch 吞掉异常 → 照常提交
  • 内层 REQUIRED 方法失败标记 rollback-only,外层 catch 后正常返回 → UnexpectedRollbackException

线上排查速查清单

现象 首先检查
启动报 BeanCurrentlyInCreationException 看堆栈里的创建链;检查构造器注入与 @Async
AOP / 事务 / 缓存不生效 是否自调用?方法是否 private/final?是否被代理?
启动慢 组件扫描范围过大、BPP 过多、循环依赖触发三级缓存代理
NoSuchBeanDefinitionException 包扫描路径、是否有多个实现缺 @Primary、Bean 是否被条件装配排除
关闭时任务丢失 是否配置 server.shutdown=graceful + spring.lifecycle.timeout-per-shutdown-phase
配置注入为 null 是否用了 static 字段?是否在构造器里就用它?(构造器阶段注入尚未发生)

11. 一句话总结

Spring IoC 容器在 Java 底层就是一个基于反射与元数据的运行时对象工厂:

  • BeanDefinition + ConcurrentHashMap ------ 解决"运行期才知道要创建什么"
  • Constructor.newInstance() / Field.setAccessible(true) ------ 解决"如何动态创建与装配"
  • 三级缓存 + ObjectFactory 延迟决策 ------ 解决"循环依赖"与"AOP 代理一致性"的双重矛盾,且不为无关 Bean 付出代价
  • BeanPostProcessor / BeanFactoryPostProcessor ------ 解决"如何在生命周期各阶段插手",这是 Spring 生态(MyBatis、Feign、RocketMQ、Redisson...)能无缝集成的根本原因
  • ApplicationContext ------ 在 BeanFactory 之上叠加事件、环境、资源、国际化,并用 refresh() 驱动整条生命周期

理解了 refresh() → preInstantiateSingletons() → doGetBean() → doCreateBean() 这条主干,再看任何 Spring 生态的集成组件,你都能一眼看穿它在哪个环节做了什么手脚。


附:核心源码位置索引

想看什么 类和位置
启动主流程 org.springframework.context.support.AbstractApplicationContext#refresh
BD 注册中心 org.springframework.beans.factory.support.DefaultListableBeanFactory#registerBeanDefinition
预实例化单例 DefaultListableBeanFactory#preInstantiateSingletons
获取 Bean 主链路 AbstractBeanFactory#doGetBean
单例锁与三级缓存 DefaultSingletonBeanRegistry#getSingleton / addSingleton
单个 Bean 生命周期主干 AbstractAutowireCapableBeanFactory#doCreateBean
实例化 AbstractAutowireCapableBeanFactory#createBeanInstance
属性注入 AbstractAutowireCapableBeanFactory#populateBean
初始化与增强 AbstractAutowireCapableBeanFactory#initializeBean
@Autowired 注入 AutowiredAnnotationBeanPostProcessor.AutowiredFieldElement#inject
@PostConstruct 触发 CommonAnnotationBeanPostProcessor#postProcessBeforeInitialization
AOP 代理生成 AbstractAutoProxyCreator#postProcessAfterInitialization / getEarlyBeanReference
早期引用工厂 AbstractAutowireCapableBeanFactory#getEarlyBeanReference
配置类解析 ConfigurationClassPostProcessor#postProcessBeanDefinitionRegistry
相关推荐
天河归来19 小时前
第三方接口不稳定,如何让核心任务继续完成
java·大数据
Knight_AL20 小时前
Java 文件下载时文件名中的 `#` 被转换成 `%23`?一次 URL 编码与解码问题排查
java·开发语言
砚底藏山河21 小时前
python量化自定义周期 K 线合成
java·开发语言·数据库·python·金融
猿究院_夏燊21 小时前
MySQL 的四种隔离级别
java·数据库·mysql
码流子1 天前
移动收费怎么做?手持终端+App+收费中心的架构实践
java·开发语言·架构
霸道流氓气质1 天前
AI 流式对话与实时聊天平台:SSE 解码竞态、有状态会话隔离与背压取消的 Vue3 + Spring AI 生产级实战
java·人工智能·spring
云创智城-yuncitys1 天前
YunParking 4.0:JDK 17 与 Spring Boot 3.2 的停车云升级
java·开发语言·spring boot
文丑颜不良啊1 天前
LeetCode1661~1680题目解析
java·leetcode
子非鱼@Itfuture1 天前
关于前后端联调中登录状态/登录信息失效?
java·前端
郭小李1 天前
openFeign入门
java·开发语言·前端