全拆解)
Spring 的 IOC 容器在注解驱动的时代入口几乎都收敛到了AnnotationConfigApplicationContext这一个类上。不管你是写new AnnotationConfigApplicationContext(AppConfig.class)还是用AnnotationConfigApplicationContext(com.example)指定包路径最后都会落到这个类的初始化逻辑上。很多人用 Spring 用了好几年Component、Bean、Configuration这些注解张口就来但真要问一句“容器启动时这些 BeanDefinition 是怎么被收集起来的refresh()里那十几个步骤到底干了什么”能讲清楚的人并不多。这篇文章就专门把AnnotationConfigApplicationContext的初始化链路拆开看。从构造函数开始到register()注册配置类再到refresh()里最核心的几大步我会结合源码一行行说明白每一步为什么这么做、有哪些坑、以及调试时可以怎么验证。适合已经能熟练使用 Spring 但想真正走进源码层面的开发者也适合准备面试时被问到“Spring IOC 容器初始化流程”这类问题的人。1. 初始化前先搞懂设计骨架继承体系藏着哪些设计意图1.1 AnnotationConfigApplicationContext 到底继承了什么先看类声明这是理解一切的起点public class AnnotationConfigApplicationContext extends GenericApplicationContext implements AnnotationConfigRegistryGenericApplicationContext是 Spring 4.0 之后才引入的中间层它本身继承了AbstractApplicationContext。这里要说清楚一件事大多数文章会告诉你“IOC 容器就是DefaultListableBeanFactory”这句话对但不完整。DefaultListableBeanFactory是真正干活的BeanFactory实现里面维护着beanDefinitionMap、singletonObjects这些核心数据结构。而GenericApplicationContext做的事是在它内部持有并对外暴露这个DefaultListableBeanFactory同时把BeanFactory的生命周期和ApplicationContext的生命周期绑定在一起。为什么不让AnnotationConfigApplicationContext直接继承AbstractApplicationContext因为AbstractApplicationContext里的refresh()流程是可复用的骨架但每一种上下文获取BeanFactory的姿势不一样。比如ClassPathXmlApplicationContext是从 XML 里解析出 BeanDefinition 再注册而注解方式是想让用户直接注册配置类、靠扫描机制收集 BeanDefinition。GenericApplicationContext在中间起了一个“适配”作用它维护DefaultListableBeanFactory至于 BeanDefinition 怎么进来交给子类自己决定。从类声明还能看到这个类实现了AnnotationConfigRegistry接口这个接口只声明了两个方法register(Class?...)和scan(String...)。这个设计很妙它把“注解配置上下文”对外暴露的能力收敛到了两个动作上要么直接注册配置类要么扫描指定包路径。后面所有复杂的初始化逻辑都藏在这两个动作的背后。1.2 构造器里两个成员变量的分工打开这个类的字段定义你会看到两个极其重要的成员private final AnnotatedBeanDefinitionReader reader; private final ClassPathBeanDefinitionScanner scanner;reader负责把“注解标注的类”转换成BeanDefinition并注册到容器它是配置类进入容器的第一道门。scanner负责扫描指定包路径下的所有类找到符合条件的候选组件后再走reader的逻辑完成注册。这两个对象在无参构造里被一起初始化但这个“初始化”只是创建对象和注册一批内置后置处理器并不立即扫描任何类。这里有一个特别容易被忽略的细节scanner在无参构造里被创建但它默认的scan(String...)方法并不会在构造阶段自动执行。Spring 容器启动时真正承担包扫描任务的是另外一个角色——ConfigurationClassPostProcessor它会在refresh()阶段通过解析ComponentScan注解来触发扫描而不是直接用scanner。刚学源码的人经常在这里绕晕明明创建了一个 scanner结果扫描不是它干的严格说scanner是给编程式使用场景准备的比如你写context.scan(com.example)时它才出场而ComponentScan注解驱动的扫描走的是配置类解析流程两者最终都会把扫描到的候选类变成 BeanDefinition但触发机制完全不同。1.3 为什么还要单独抽出 AnnotationConfigUtils在AnnotatedBeanDefinitionReader的构造方法里有一行关键的调用AnnotationConfigUtils.registerAnnotationConfigProcessors(this.registry);AnnotationConfigUtils是一个静态工具类它做的事情非常朴素把 Spring 容器运行所必需的一批“基础设施 Bean”提前注册进容器。这些基础设施包括ConfigurationClassPostProcessor、AutowiredAnnotationBeanPostProcessor、CommonAnnotationBeanPostProcessor、EventListenerMethodProcessor、DefaultEventListenerFactory等。它们不是业务 Bean而是让容器支持Configuration、Autowired、EventListener这些注解能力的内置组件。把这段逻辑抽到一个独立的工具类里而不是写在 reader 内部背后的考虑是复用。XML 时代的容器也需要注册这些后置处理器在 XML 配置里你需要手写context:annotation-config/标签来触发注册注解驱动时代这一步被整合进了AnnotationConfigApplicationContext的构造过程。抽出工具类后不管是注解入口还是 XML 入口最终都能调用同一个registerAnnotationConfigProcessors方法保证注册逻辑一致。2. 构造函数链路从 new 到 reader 就绪的全部细节2.1 无参构造都做了什么AnnotationConfigApplicationContext有多个重载构造方法先看最核心的无参构造public AnnotationConfigApplicationContext() { StartStep step new StartStep(this, ApplicationStartup.DEFAULT); this.reader new AnnotatedBeanDefinitionReader(this); this.scanner new ClassPathBeanDefinitionScanner(this); step.end(); }两个关键点。第一AnnotatedBeanDefinitionReader和ClassPathBeanDefinitionScanner的构造参数都传了this也就是当前上下文自身。为什么要传自身因为这两个工具类最终要把解析出来的 BeanDefinition 注册到一个BeanDefinitionRegistry上而GenericApplicationContext本身就是一个BeanDefinitionRegistry的实现它内部委托给持有的DefaultListableBeanFactory完成注册。第二这里会间接触发一次DefaultListableBeanFactory的创建。GenericApplicationContext的构造函数里执行了this.beanFactory new DefaultListableBeanFactory()。所以当你new AnnotationConfigApplicationContext()的那一刻一个全新的、空的BeanFactory就已经诞生了只是里面还没有任何业务 BeanDefinition。2.2 AnnotatedBeanDefinitionReader 内部四件事AnnotatedBeanDefinitionReader的构造方法里其实藏了四层逻辑我按执行顺序理一遍校验并保存BeanDefinitionRegistry引用。获取或创建Environment对象用于后续条件评估和属性解析。创建ConditionEvaluator这是处理Conditional注解的核心评估器。调用AnnotationConfigUtils.registerAnnotationConfigProcessors(registry)注册内置后置处理器。第三点值得多说几句。ConditionEvaluator的存在意味着注解配置在“注册阶段”就会做一次条件判断。比如你在某个配置类上标注了Conditional(SomeCondition.class)当doRegisterBean()执行到条件评估这一步时它会读取Conditional注解里声明的所有条件类实例化并执行matches()方法如果条件不满足当前这个 BeanDefinition 直接丢弃连注册都不会发生。这和Profile的处理机制其实是同一套体系Profile本身就是Conditional的一个具体实现。2.3 ClassPathBeanDefinitionScanner 被创建的用意这个 scanner 的构造过程比 reader 稍复杂一点它要传入useDefaultFilters参数默认值是true也就是启用默认的扫描过滤器。默认过滤器匹配的是Component注解及其派生注解包括Repository、Service、Controller、Configuration。我之前提过ComponentScan驱动的扫描并不直接用这个 scanner 对象但编程式调用context.scan(com.example.pkg)时走的就是它的scan()方法。这个方法内部会创建一个ClassPathScanningCandidateComponentProvider按包路径找到所有 class 文件加载为AnnotatedGenericBeanDefinition再通过checkCandidate()方法判断是否已经注册、是否允许覆盖最后注册进容器。这里有一个实用经验如果你在单元测试里想手动加载某个包下的组件不一定要先定义配置类直接new AnnotationConfigApplicationContext()然后context.scan(com.example)就能完成。但要注意这种方式和ComponentScan注解方式有个细微差别编程式 scan 不会自动处理Configuration类里的Bean方法因为这些解析动作是由ConfigurationClassPostProcessor在refresh()阶段完成的而编程式 scan 只是把扫描到的类注册成了普通 BeanDefinition。你仍然可以手动调用refresh()触发后续处理但入口场景不同执行路径就有差异。2.4 registerAnnotationConfigProcessors 注册了哪些关键组件这一步注册的组件几乎决定了注解驱动容器的所有核心能力。看代码片段最有说服力public static SetBeanDefinitionHolder registerAnnotationConfigProcessors( BeanDefinitionRegistry registry, Nullable Object source) { DefaultListableBeanFactory beanFactory unwrapDefaultListableBeanFactory(registry); if (beanFactory ! null) { if (!(beanFactory.getDependencyComparator() instanceof AnnotationAwareOrderComparator)) { beanFactory.setDependencyComparator(AnnotationAwareOrderComparator.INSTANCE); } if (!(beanFactory.getAutowireCandidateResolver() instanceof ContextAnnotationAutowireCandidateResolver)) { beanFactory.setAutowireCandidateResolver(new ContextAnnotationAutowireCandidateResolver()); } } SetBeanDefinitionHolder beanDefs new LinkedHashSet(8); if (!registry.containsBeanDefinition(CONFIGURATION_ANNOTATION_PROCESSOR_BEAN_NAME)) { RootBeanDefinition def new RootBeanDefinition(ConfigurationClassPostProcessor.class); def.setSource(source); beanDefs.add(registerPostProcessor(registry, def, CONFIGURATION_ANNOTATION_PROCESSOR_BEAN_NAME)); } // AutowiredAnnotationBeanPostProcessor、CommonAnnotationBeanPostProcessor... // EventListenerMethodProcessor、DefaultEventListenerFactory... }ConfigurationClassPostProcessor是这里面最重要的一位。它是一个BeanDefinitionRegistryPostProcessor在refresh()阶段的invokeBeanFactoryPostProcessors()中被调用负责解析所有标注了Configuration的类处理ComponentScan、Import、Bean方法等是整个注解驱动 IOC 的核心引擎。另外它还设置了两个基础设施把DependencyComparator换成AnnotationAwareOrderComparator这样Order注解和Ordered接口就能影响自动注入时的候选顺序把AutowireCandidateResolver换成ContextAnnotationAutowireCandidateResolver这样Lazy、Value这些注解才能在字段注入、构造器注入时被正确解析。3. register() 注册配置类BeanDefinition 是怎么进来的3.1 register 调用链与包裹逻辑来看register方法的源码Override public void register(Class?... componentClasses) { Assert.notEmpty(componentClasses, At least one component class must be specified); this.reader.register(componentClasses); // 版本差异部分版本会在 register 后触发 early 初始化 // 但核心逻辑都是交给 reader.register 去处理 }它做的事情非常纯粹参数校验后原样委托给AnnotatedBeanDefinitionReader.register()。reader.register()又做了一层包装public void register(Class?... componentClasses) { for (Class? componentClass : componentClasses) { registerBean(componentClass); } } public T void registerBean(ClassT beanClass) { doRegisterBean(beanClass, null, null, null, null); }到这里还没有真正生成 BeanDefinition只是把Class对象传给了doRegisterBean()。所有的处理和注册都发生在那个方法里。3.2 AnnotatedGenericBeanDefinition 与元数据的装载doRegisterBean()的第一行代码AnnotatedGenericBeanDefinition abd new AnnotatedGenericBeanDefinition(beanClass);这是一个非常关键的类型。它和 XML 时代常用的GenericBeanDefinition不同AnnotatedGenericBeanDefinition不仅保存了 Bean 的类信息还持有AnnotationMetadata。所谓AnnotationMetadata就是通过 ASM 读取 class 字节码得到的“注解元数据”它可以在不实例化类的情况下查询目标类上标注了哪些注解、注解的属性值是什么、类的访问修饰符是什么。这里有个值得一提的细节为什么用 ASM 而不是反射因为 Spring 在判断一个类是不是配置类、有没有Component注解时可能涉及几十上百个类的扫描如果用反射把每个类都加载进 JVM会产生巨大的类加载成本和额外的内存开销。ASM 直接读字节码效率高得多而且有些候选类可能因为条件不满足最终根本不会被实例化。3.3 doRegisterBean 完整执行流程继续往下追踪完整版本的doRegisterBean方法结构如下private T void doRegisterBean(ClassT beanClass, Nullable String name, Nullable Class? extends Annotation[] qualifiers, Nullable SupplierT supplier, Nullable BeanDefinitionCustomizer[] beanDefinitionCustomizers) { AnnotatedGenericBeanDefinition abd new AnnotatedGenericBeanDefinition(beanClass); if (this.conditionEvaluator.shouldSkip(abd.getMetadata())) { return; } abd.setInstanceSupplier(supplier); ScopeMetadata scopeMetadata this.scopeMetadataResolver.resolveScopeMetadata(abd); abd.setScope(scopeMetadata.getScopeName()); String beanName (name ! null ? name : this.beanNameGenerator.generateBeanName(abd, this.registry)); AnnotationConfigUtils.processCommonDefinitionAnnotations(abd); if (qualifiers ! null) { for (Class? extends Annotation qualifier : qualifiers) { if (Primary.class qualifier) { abd.setPrimary(true); } else if (Lazy.class qualifier) { abd.setLazyInit(true); } else { abd.addQualifier(new AutowireCandidateQualifier(qualifier)); } } } if (beanDefinitionCustomizers ! null) { for (BeanDefinitionCustomizer customizer : beanDefinitionCustomizers) { customizer.customize(abd); } } BeanDefinitionHolder definitionHolder new BeanDefinitionHolder(abd, beanName); definitionHolder AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry); BeanDefinitionReaderUtils.registerBeanDefinition(definitionHolder, this.registry); }逐个环节说明。先执行条件评估。如果类上的Conditional条件不满足整个注册直接中止。这里说的“不满足”指的是shouldSkip()返回true注意条件是“shouldSkip”它的语义是条件评估不通过就跳过这个 Bean。如果类上没有Conditional注解默认不跳过。然后是设置实例供应商。supplier允许你传入一个SupplierT回调替代常规的反射实例化。这是 Spring 5.0 引入的函数式 BeanDefinition 注册方式在AnnotationConfigApplicationContext.registerBean()的重载中可以传入 Supplier比如context.registerBean(MyService.class, () - new MyService(xxx))。这种方式最大的好处是绕过了反射构造器调用对于需要自定义构造参数或做复杂初始化的场景非常方便。接下来是解析作用域。scopeMetadataResolver默认实现是AnnotationScopeMetadataResolver它会扫描类上的Scope注解读取 value 属性决定是singleton、prototype还是自定义 scope。如果没标注Scope默认是singleton。这里要注意Scope不只影响单例或多例它还支持scopedProxyMode也就是代理模式。如果你设置proxyMode ScopedProxyMode.TARGET_CLASSSpring 会为这个 Bean 生成一个代理对象注册到容器里实际依赖注入的是代理。AnnotationConfigUtils.applyScopedProxyMode()就是处理这个的。接着是生成 BeanName。默认的beanNameGenerator是AnnotationBeanNameGenerator。它的规则可以概括为如果类上有Component(name)等显式指定名称的注解就用注解里的 value 值否则把类名首字母小写作为默认 beanName。有一个细节容易踩坑如果你注册的配置类没有显式命名默认 beanName 就是配置类的简单类名首字母小写但这个 beanName 只影响配置类自身作为 Bean 的名称不影响它内部Bean方法的名称。然后是补全通用注解的元数据AnnotationConfigUtils.processCommonDefinitionAnnotations(abd);这个方法会检查类上的Lazy、Primary、DependsOn、Role、Description等注解并把对应的属性写入 BeanDefinition。比如类上标了Primary这个 Bean 在多个候选注入时会被优先选择。最后是注册动作。BeanDefinitionReaderUtils.registerBeanDefinition()内部会调用DefaultListableBeanFactory.registerBeanDefinition()把BeanDefinition存进beanDefinitionMap里同时维护beanDefinitionNames这个有序列表。这里有一个关于覆盖行为的细节如果容器里已经存在同名的 BeanDefinition会走allowBeanDefinitionOverriding的判断逻辑默认允许覆盖只是会打印一条日志。在某些严格场景下比如外部化配置不允许重复定义可以把这个开关关掉。3.4 ConditionEvaluator 的条件装配评估条件评估是注解驱动容器一个很有代表性的能力值得单独拆开理解。ConditionEvaluator.shouldSkip()的大致逻辑是public boolean shouldSkip(AnnotatedTypeMetadata metadata) { return shouldSkip(metadata, null); } public boolean shouldSkip(Nullable AnnotatedTypeMetadata metadata, Nullable ConfigurationPhase phase) { if (metadata null || !metadata.isAnnotated(Conditional.class.getName())) { return false; } // 收集 Conditional 注解中声明的所有 Condition 类 // 排序后依次实例化并执行 matches() // 如果其中一个返回 false则 shouldSkip 返回 true }Spring Boot 的ConditionalOnClass、ConditionalOnProperty等注解本质上就是在Conditional的基础上做了一层封装最终走的都是这同一套ConditionEvaluator评估链路。理解这一点你就会明白为什么配置类上的条件判断是在 BeanDefinition 注册阶段就生效的——它根本没给你创建 Bean 的机会条件不满足时连 BeanDefinition 都不会进入容器。4. refresh() 才是真正的主角12步初始化全拆解4.1 prepareRefresh obtainFreshBeanFactory回到AbstractApplicationContext.refresh()。如果你之前只学过概念没有真正对照过源码可以先把整段结构看一遍Override public void refresh() throws BeansException, IllegalStateException { synchronized (this.startupShutdownMonitor) { // 准备刷新设置启动时间、活跃状态、初始化属性源 prepareRefresh(); // 获取刷新后的 BeanFactory ConfigurableListableBeanFactory beanFactory obtainFreshBeanFactory(); // 给 BeanFactory 补充容器级基础设施 prepareBeanFactory(beanFactory); try { // 子类扩展点允许在 BeanFactory 已准备但 Bean 创建前修改 postProcessBeanFactory(beanFactory); // 执行 BeanDefinitionRegistryPostProcessor 和 BeanFactoryPostProcessor invokeBeanFactoryPostProcessors(beanFactory); // 注册 BeanPostProcessor等待 Bean 创建时发挥作用 registerBeanPostProcessors(beanFactory); // 初始化 MessageSource支持国际化 initMessageSource(); // 初始化事件广播器 initApplicationEventMulticaster(); // 子类扩展点刷新时初始化其他特殊 Bean onRefresh(); // 注册事件监听器 registerListeners(); // 实例化所有非懒加载的单例 Bean finishBeanFactoryInitialization(beanFactory); // 完成刷新发布 ContextRefreshedEvent 事件 finishRefresh(); } catch (BeansException ex) { destroyBeans(); cancelRefresh(ex); throw ex; } finally { resetCommonCaches(); } } }prepareRefresh()做的事情比较轻量设置容器的启动时间、激活状态、关闭状态初始化属性源PropertySource并准备一批用于审计的早期事件。它不会创建任何 Bean但它是整个刷新过程中的“状态开关”。obtainFreshBeanFactory()在GenericApplicationContext里的实现几乎等于一个空操作因为它继承的GenericApplicationContext在构造函数里已经创建好DefaultListableBeanFactory了这里只是把它包装一层返回。但如果你是跟着ClassPathXmlApplicationContext的路径走这个方法会重新创建 BeanFactory 并加载 XML 中的所有 BeanDefinition。两种上下文在这里的分叉正好验证了前面说的“GenericApplicationContext适配不同 BeanDefinition 来源”的设计意图。4.2 prepareBeanFactory 里那些容易被忽略的配置prepareBeanFactory是给刚创建的 BeanFactory 做“基础环境配置”的步骤它做的事足够冷门但非常重要。核心代码大致如下protected void prepareBeanFactory(ConfigurableListableBeanFactory beanFactory) { beanFactory.setBeanClassLoader(getClassLoader()); beanFactory.setBeanExpressionResolver(new StandardBeanExpressionResolver()); beanFactory.addPropertyEditorRegistrar(new ResourceEditorRegistrar(this, getEnvironment())); beanFactory.addBeanPostProcessor(new ApplicationContextAwareProcessor(this)); beanFactory.ignoreDependencyInterface(EnvironmentAware.class); beanFactory.ignoreDependencyInterface(EmbeddedValueResolverAware.class); beanFactory.ignoreDependencyInterface(ResourceLoaderAware.class); beanFactory.ignoreDependencyInterface(ApplicationEventPublisherAware.class); beanFactory.ignoreDependencyInterface(MessageSourceAware.class); beanFactory.ignoreDependencyInterface(ApplicationContextAware.class); beanFactory.registerResolvableDependency(BeanFactory.class, beanFactory); beanFactory.registerResolvableDependency(ResourceLoader.class, this); beanFactory.registerResolvableDependency(ApplicationEventPublisher.class, this); beanFactory.registerResolvableDependency(ApplicationContext.class, this); beanFactory.addBeanPostProcessor(new ApplicationListenerDetector(this)); // ... 其他组件 }这里面有一个面试高频考点为什么注册ApplicationContextAwareProcessor的同时要ignoreDependencyInterface一堆 Aware 接口原因是 Spring 在完成Autowired字段注入时会自动寻找所有可注入的依赖类型如果不排除那些 Aware 接口那么一个实现ApplicationContextAware的 Bean 在提取注入点时就可能被误判为“需要注入 ApplicationContext 类型的依赖”。这会造成重复处理。正确路径是ApplicationContextAwareProcessor作为一个BeanPostProcessor在 Bean 实例化后、初始化前专门负责回调 Aware 方法的赋值逻辑而普通依赖注入阶段则跳过这些接口。还有一行容易被忽略beanFactory.registerResolvableDependency(BeanFactory.class, beanFactory);这行代码的效果是当你的业务类里声明了Autowired private BeanFactory beanFactory;Spring 能直接注入当前容器内部的DefaultListableBeanFactory。类似的ApplicationContext、ResourceLoader、ApplicationEventPublisher也能被直接注入因为AnnotationConfigApplicationContext本身就是这些接口的实现类。4.3 invokeBeanFactoryPostProcessors 与 ConfigurationClassPostProcessor 的高光时刻这一步是整个注解驱动初始化流程中最精彩、最复杂的一段。invokeBeanFactoryPostProcessors的逻辑需要区分两种处理器BeanDefinitionRegistryPostProcessor它本身也是BeanFactoryPostProcessor但多了一个postProcessBeanDefinitionRegistry()方法可以有意识地修改 BeanDefinition 注册表。普通BeanFactoryPostProcessor只提供postProcessBeanFactory()方法可以在 Bean 实例化前修改 BeanFactory 的配置。执行顺序有讲究先执行所有BeanDefinitionRegistryPostProcessor再执行普通BeanFactoryPostProcessor。而在BeanDefinitionRegistryPostProcessor内部又分成“先执行实现了PriorityOrdered的再执行实现了Ordered的最后执行剩余的”。ConfigurationClassPostProcessor就是一个优先级极高的BeanDefinitionRegistryPostProcessor它实现了PriorityOrdered所以在这一轮会被最先执行。它内部最关键的方法是processConfigBeanDefinitions()public void processConfigBeanDefinitions(BeanDefinitionRegistry registry) { ListBeanDefinitionHolder configCandidates new ArrayList(); String[] candidateNames registry.getBeanDefinitionNames(); // 遍历所有 BeanDefinition找出配置类候选 for (String beanName : candidateNames) { BeanDefinition beanDef registry.getBeanDefinition(beanName); if (ConfigurationClassUtils.isFullConfigurationCandidate(beanDef.getMetadata()) || ConfigurationClassUtils.isLiteConfigurationCandidate(beanDef.getMetadata())) { configCandidates.add(new BeanDefinitionHolder(beanDef, beanName)); } } // 没有配置类候选则直接返回 if (configCandidates.isEmpty()) { return; } // 排序初始化 ConfigurationClassParser // 解析每个配置类处理 ComponentScan、Import、ImportResource、Bean 方法 // 每解析出一个新的 BeanDefinition加入容器后继续迭代解析 // 直到没有新的配置类产生 }ConfigurationClassParser是真正干活的组件。它会遍历每个配置类上的所有注解如果发现ComponentScan就调用扫描逻辑扫描指定包路径下的候选类如果发现Import把导入的类也当作配置或普通组件处理如果类中有Bean方法为每个Bean方法生成一个BeanMethod对应的 BeanDefinition。解析过程中产生的所有新 BeanDefinition 都会被注册进容器然后继续迭代解析直到没有新的配置类产生。这里给想调试源码的人一个建议在ConfigurationClassParser.doProcessConfigurationClass()方法里打断点你能亲眼看到每一个ComponentScan是如何被展开成成百上千个 BeanDefinition 的。对应关系非常直观。4.4 registerBeanPostProcessors 与后置处理器注册注意这一步和上一步的名称区别上一步是在“调用”BeanFactoryPostProcessor这一步是在“注册”BeanPostProcessor。BeanPostProcessor负责的是 Bean 实例化之后、初始化前后的拦截逻辑典型代表是AutowiredAnnotationBeanPostProcessor——Autowired的注入就是它完成的。Spring 会把容器中所有实现了BeanPostProcessor接口的 Bean 先实例化出来再按优先级注册进beanFactory的beanPostProcessors列表。这里有个先后顺序的陷阱BeanPostProcessor必须在普通 Bean 创建之前注册完毕否则它对 Bean 生命周期的拦截能力就会失效。这也是为什么registerBeanPostProcessors被放在finishBeanFactoryInitialization之前。还有一个冷知识如果你自己定义了一个类实现了BeanPostProcessor但同时又依赖了其他普通 Bean 来完成初始化这时要格外小心——BeanPostProcessor会被提前实例化它依赖的那些 Bean 也会被提前创建这可能会打破你原有的初始化顺序预期甚至引发循环依赖。源码注释里其实明确写了“BeanPostProcessor 不应该依赖于未初始化的 Bean”。4.5 finishBeanFactoryInitialization单例预实例化与循环依赖这一步的名场面是“预实例化所有非懒加载的单例 Bean”。DefaultListableBeanFactory.preInstantiateSingletons()的流程大致是public void preInstantiateSingletons() throws BeansException { ListString beanNames new ArrayList(this.beanDefinitionNames); for (String beanName : beanNames) { RootBeanDefinition bd getMergedLocalBeanDefinition(beanName); if (!bd.isAbstract() bd.isSingleton() !bd.isLazyInit()) { if (isFactoryBean(beanName)) { // 先处理 FactoryBean 本体的创建 } else { getBean(beanName); } } } // 所有单例 Bean 创建完成后触发 SmartInitializingSingleton 回调 for (String beanName : beanNames) { Object singletonInstance getSingleton(beanName); if (singletonInstance instanceof SmartInitializingSingleton) { ((SmartInitializingSingleton) singletonInstance).afterSingletonsInstantiated(); } } }getBean()之后会进入doGetBean()然后经过createBean()→doCreateBean()的漫长链路。在这个链路里有一个核心机制值得单独说循环依赖的三级缓存。DefaultSingletonBeanRegistry维护了三个MapsingletonObjects一级缓存保存已经完成初始化的单例 Bean。earlySingletonObjects二级缓存保存早期暴露的 Bean 引用这个 Bean 已经实例化但还没完成属性填充。singletonFactories三级缓存保存ObjectFactory?可以在需要时提前生成早期引用。处理循环依赖的思路可以这样理解A 依赖 BA 先创建发现需要 BB 创建过程中又依赖 A此时容器把 A 刚实例化完的“半成品”提前暴露出来给 B 用等 B 创建完成后 A 再拿到完整的 B。三级缓存存在的必要性是为了处理“属性填充发生在循环依赖场景下”的时序问题。这里必须强调一个限制Spring 的循环依赖解决机制只对“单例 非构造器注入”的场景有效。如果是构造器注入的循环依赖直接抱团失败如果是原型作用域的循环依赖也没有缓存可用直接报错。网上很多“Spring 支持循环依赖”的说法其实过于绝对准确的说法是“Spring 通过三级缓存可以解决部分单例作用域下的循环依赖”。4.6 finishRefresh上下文刷新收尾的收尾要确保对初始化流程的认知完整finishRefresh()这一步也值得理解。它主要做了四件事清空ResourceLoader缓存确保后续资源解析拿到的是最新数据。初始化生命周期处理器LifecycleProcessor。调用getLifecycleProcessor().onRefresh()通知所有SmartLifecycle组件启动。发布ContextRefreshedEvent事件。最后这一条在业务中的意义很大。如果你在代码里监听ContextRefreshedEvent它会在容器完整刷新后触发适合做一些“所有 Bean 都就绪后”的启动后置动作比如缓存预热、数据初始化、定时任务注册等。5. 注解方式与 XML 方式的路程差异5.1 入口不同但终点一致很多从 XML 时代过来的开发者总觉得注解方式和 XML 方式是完全不同的两套实现。实际上两种方式共用了同一个AbstractApplicationContext.refresh()骨架最后的 BeanFactory 都是DefaultListableBeanFactory所有 Bean 生命周期机制BeanPostProcessor、BeanFactoryPostProcessor、事件发布完全相同。不同的只是 BeanDefinition 的收集来源XML 方式XmlBeanDefinitionReader读取 XML 文件遇到bean标签就构建一个GenericBeanDefinition注册进容器。注解方式AnnotatedBeanDefinitionReader注册配置类再由ConfigurationClassPostProcessor递归解析出所有 BeanDefinition。两者注册进容器之后的命运完全相同都要经过invokeBeanFactoryPostProcessors、registerBeanPostProcessors、finishBeanFactoryInitialization这三大阶段。5.2 注解驱动多出来的一层“配置解析”注解方式比 XML 方式多出来的核心环节就是“配置类解析”。XML 里你显式写了每一个 Bean信息是完整的、静态的而注解方式里你只给了容器一个配置类入口容器需要自己去扫注解、找Bean方法、处理Import和Conditional这是一个动态的、递归的解析过程。ConfigurationClassPostProcessor和ConfigurationClassParser就是为这一层解析而生的。这也是为什么注解驱动的启动过程通常比 XML 略慢——不是慢在反射创建 Bean而是慢在配置解析阶段的扫描和注解元数据读取。在 Bean 数量足够大时这个差异会更明显。6. 常见问题与排查技巧实录6.1 配置类没有被代理一个常见的怪问题明明在配置类里定义了两个Bean其中一个调用了另一个Bean方法结果每次调用都 new 出了新对象单例失效。原因很可能是配置类只加了Component而不是Configuration。Configuration类在解析时被标记为“full 配置类”Spring 会为它生成 CGLIB 代理Bean方法之间的调用走的是代理对象从而保证单例语义而“lite 配置类”比如标注了Component、Import但没标Configuration的类不会被代理方法调用就是普通 Java 调用每次都会执行方法体导致Bean方法内的对象重复创建。排查方法很简单在ConfigurationClassUtils.isFullConfigurationCandidate()和isLiteConfigurationCandidate()两个方法上打断点观察你的配置类走的是哪条判断分支。6.2 包扫描不生效使用context.scan(com.example.pkg)时扫描不到任何 Bean大概率是包路径写错但有时候包路径完全正确还是不生效。这时候先确认你的pom.xml或构建配置里有没有引入spring-context依赖再确认目标包里的类是不是真的标了Component或其派生注解。还有一个很隐蔽的原因类被编译后的目录中不包含对应注解的CLASS文件比如被某些插件过滤了。经验建议真调试时在ClassPathScanningCandidateComponentProvider.findCandidateComponents()里打断点看它扫描的Resource列表到底覆盖了哪些路径可以在classpath*相关的加载逻辑处直接看到结果。6.3 单例容器里的循环依赖项目里一旦出现BeanCurrentlyInCreationException说明循环依赖没被解决。先不要急着加Lazy先判断是构造器注入还是 setter/属性注入。如果是构造器注入的循环依赖最靠谱的方案是重构设计把依赖关系改成 setter 注入或使用Lazy打破循环。如果一定要保留构造器注入Lazy会让 Spring 先注入一个代理对象等真正调用时才去获取目标 Bean但这个做法会增加一层隐式延迟别滥用。另外Spring Boot 2.6 之后默认关闭了循环依赖支持如果你在 Boot 项目中遇到循环依赖异常还需要检查配置项spring.main.allow-circular-references有没有被显式打开。6.4 调试源码的三个建议最后分享三个我实际调试 Spring 源码时觉得很有用的习惯。第一不要直接在refresh()的入口点一路单步走到底那样会迷失在成千上万的调用栈里。更好的方式是在关键节点打断点比如ConfigurationClassParser.doProcessConfigurationClass()、doCreateBean()、invokeBeanFactoryPostProcessors()然后结合beanDefinitionMap.size()的变化来观察容器状态。第二利用beanDefinitionNames列表观察注册顺序。在DefaultListableBeanFactory.registerBeanDefinition()方法里打断点看一眼beanDefinitionNames的顺序你会直观感受到配置类解析的先后顺序。很多“为什么我的 Bean 先执行后执行”的困惑看这个列表基本就能解开。第三注意 Spring 版本的差异。不同版本里AnnotationConfigApplicationContext内部代码有出入比如很多版本会在构造阶段调用StartStep做启动性能记录但在 5.0 之前的版本根本没有这些逻辑。调试时以你本地依赖的源码版本为准理论上相近版本之间核心流程差异不大。我自己在阅读这套源码时最大的体会是不要把 IOC 容器想象成什么高深莫测的东西它本质上就是一个强化版的MapString, Object只不过这个 Map 在创建对象的过程中加入了复杂的生命周期管理。AnnotationConfigApplicationContext初始化链路的每一步都是在往这个 Map 里填充数据或增强未来填充数据的能力。搞懂了这条链路Spring 的大半个世界基本就打开了一半。剩下的那些 AOP、事务、事件机制都建立在这条初始化链路的框架之上理解起来会顺畅得多。