news 2026/10/14 5:17:57

Spring IOC容器加载流程源码解析:从refresh到Bean生命周期

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Spring IOC容器加载流程源码解析:从refresh到Bean生命周期

说实话,Spring的IOC容器大家每天都在用,但问到底层Bean是怎么从配置变成可用的实例、容器启动时到底干了哪些事,不少人就卡壳了。我早期啃这块源码的时候也差点放弃——断点一打下去,调用链深得吓人,一个getBean能追出十几层方法。但摸清楚主干之后会发现,整套加载流程的设计脉络非常清晰,而且理清它对排查循环依赖、Bean生命周期顺序、AOP代理时机这些实际问题都特别有帮助。

这篇文章我准备按照自己当时的源码阅读路径来写,目标就是一句话:让你跟着走一遍从容器启动到Bean创建完成的完整加载流程,看懂每一步在做什么、为什么这么做。适合用过Spring但想深入源码、一直被Bean生命周期和循环依赖绕晕的开发者,也适合准备面试需要系统讲清IOC原理的学习者。

1. 加载流程全景:先建立宏观认知再动手读源码

很多同学读源码失败,不是因为看不懂某个方法,而是因为一上来就钻进了细节,结果丢失了全局感。我先带你把全景图铺开,后面每一步都能对号入座。

1.1 一次加载的起点和终点分别是什么

从使用者视角来看,一次完整的IOC加载通常是这样触发的:

AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class); UserService userService = context.getBean(UserService.class);

第一行代码创建容器,第二行代码从容器拿Bean。但实际上,真正的Bean创建在第二行之前就已经发生了——AnnotationConfigApplicationContext的构造器里会自动执行容器的启动流程,把所有非懒加载的单例Bean全部创建好。第二行的getBean更多是从缓存里取值。

这就像开一家餐厅:你拿着菜单(配置类)进场,后厨(容器)会提前把常用菜(单例Bean)备好。客人点单(调用getBean)时,已经备好的菜直接端出来就行,而不是现场才开始洗菜切菜。

1.2 源码阅读的两条主干线和四个阶段

读IOC源码,我建议分成两条线来看。

第一条线是容器启动线,入口是AbstractApplicationContext.refresh()。这个方法被誉为Spring源码的"总指挥",12个步骤层层推进,完成整个容器的初始化。

第二条线是Bean创建线,入口是finishBeanFactoryInitialization(),往下走就是getBean → doGetBean → createBean → doCreateBean这条主链。

这两条线合在一起就是一个完整的加载周期,大致可以分成四个阶段:

  • 配置加载阶段:把XML或注解配置类解析成一个个BeanDefinition
  • 容器增强阶段:执行BeanFactoryPostProcessor修改Bean定义,注册BeanPostProcessor
  • Bean创建阶段:实例化、属性填充、初始化,产出可用的Bean
  • 容器完善阶段:完成事件广播、生命周期回调等收尾工作

1.3 三个必须先搞清楚的底层概念

读代码之前有三块基石必须打牢,否则后面每一步都是云里雾里。

第一个是BeanDefinition。它就是Bean的"图纸",里面记录了类名、作用域、是否懒加载、依赖关系、初始化方法等元信息。容器启动时先解析配置得到图纸,再根据图纸来施工创建Bean。注意它不是Bean本身,而是描述Bean的对象。

第二个是BeanFactory和ApplicationContext的区别。BeanFactory是最基础的容器接口,负责Bean的获取和管理;ApplicationContext在它之上增加了事件发布、国际化、环境抽象等能力。我们平时用的AnnotationConfigApplicationContext就是一个ApplicationContext实现,底层组合了一个DefaultListableBeanFactory。

第三个是后置处理器机制。Spring最核心的扩展点就是两类后置处理器:BeanFactoryPostProcessor操作BeanDefinition,BeanPostProcessor操作Bean实例。可以说,IOC容器的一切高级功能——占位符解析、注解注入、AOP代理——都是靠它们实现的。

可以把这几个概念放在一张对照表里方便记忆:

概念操作对象生命周期阶段典型应用
BeanDefinitionBean的元数据定义配置加载阶段描述类的信息、依赖、作用域
BeanFactoryPostProcessorBeanDefinition容器启动阶段修改Bean定义、解析占位符
BeanPostProcessorBean实例Bean创建阶段注解注入、AOP代理、初始化回调

2. 从refresh()开始:一步一步拆解容器启动骨架

refresh()是理解整个IOC的钥匙。这个方法在AbstractApplicationContext中定义,名字取得非常传神——容器的创建、增强、完善都在这一个方法里"刷新"完成。我把它的核心步骤拆给你看。

2.1 为什么refresh()是源码的总入口

AbstractApplicationContext.refresh()本身就是一个模板方法,它只定义骨架,部分步骤交给子类实现。这个设计保证了容器启动流程的稳定性——不管你是XML配置还是注解配置,启动顺序都一样,只有某些步骤的实现不同。

完整的方法体大致如下:

public void refresh() throws BeansException, IllegalStateException { // 1. 容器刷新前的准备,记录启动时间、设置容器状态 prepareRefresh(); // 2. 获取新的BeanFactory,并加载BeanDefinition(XML和注解路径不同) ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory(); // 3. 给BeanFactory补充Spring内置的组件,比如类加载器、表达式解析器 prepareBeanFactory(beanFactory); // 4. 子类可以在这里对BeanFactory做额外配置 postProcessBeanFactory(beanFactory); // 5. 执行BeanFactoryPostProcessor(重点:注解配置解析就在这) invokeBeanFactoryPostProcessors(beanFactory); // 6. 注册BeanPostProcessor(注意此时只是注册,还没执行) registerBeanPostProcessors(beanFactory); // 7. 初始化消息源组件(国际化) initMessageSource(); // 8. 初始化事件广播器 initApplicationEventMulticaster(); // 9. 模板方法,留给子类做特殊处理,Web容器在这里启动 onRefresh(); // 10. 注册事件监听器 registerListeners(); // 11. 实例化所有非懒加载的单例Bean(这条线最重要) finishBeanFactoryInitialization(beanFactory); // 12. 完成刷新,发布容器刷新完成事件 finishRefresh(); }

我第一次看这段代码时,最大的感受是:Spring把"启动容器"这件事拆得特别细,每一步都有明确的职责边界。这也提醒我们,读大项目源码时先抓主方法的每一步做了什么,比逐行深挖某个方法重要得多。

2.2 注解配置类的BeanDefinition加载路径

拿AnnotationConfigApplicationContext来说,它在构造器中就注册了AnnotatedBeanDefinitionReader和ClassPathBeanDefinitionScanner两个组件。但真正把AppConfig配置类解析成BeanDefinition的动作,并不是在构造器中完成的,而是延迟到了refresh()的第五步。

这里有个容易困惑的点:AppConfig类本身也会被注册为一个BeanDefinition,然后由内置的ConfigurationClassPostProcessor去解析。它实现了BeanDefinitionRegistryPostProcessor接口,优先级最高,会先于其他后置处理器执行。它的核心工作包括:

  • 解析@Configuration类上的@ComponentScan注解,扫描指定包下的类
  • 解析@Import引入的类和@Bean方法
  • 将扫描到的每一个类封装成ScannedGenericBeanDefinition并注册到容器

所以你会看到beanDefinitionMap里既有你自己写的业务类,也有很多Spring内部类。可以打开调试器,在registerBeanDefinition方法上打断点,你会看到注册流程是怎么一条一条把BeanDefinition放入DefaultListableBeanFactory的beanDefinitionMap中的。

2.3 BeanFactoryPostProcessor的执行时机和典型应用

invokeBeanFactoryPostProcessors这一步专门用来执行所有BeanFactoryPostProcessor。它先处理BeanDefinitionRegistryPostProcessor(它属于子接口,比普通的后置处理器更早执行),再执行普通的后置处理器。

为什么设计两个层次?因为BeanDefinitionRegistryPostProcessor可以继续注册新的BeanDefinition,Spring需要先让它们有机会注册完,再让其他后置处理器统一处理所有BeanDefinition。

这一步最常见的实际应用就是PropertySourcesPlaceholderConfigurer,它负责把@Value("${db.username}")中的占位符解析成配置项的值,而它的实现机制就是在BeanDefinition层面替换掉占位符字符串。所以如果你遇到@Value注入为null,优先查看这一步是否有执行。

2.4 BeanPostProcessor的注册流程

第六步registerBeanPostProcessors只负责"注册",不负责"执行"。Spring会从beanDefinitionMap中找到所有实现BeanPostProcessor接口的BeanDefinition,然后按优先级排序注册到beanFactory的beanPostProcessors列表里。

排序规则是先处理PriorityOrdered,再是Ordered,最后是无顺序的。这一步注册的处理器,包括我们熟知的AutowiredAnnotationBeanPostProcessor、CommonAnnotationBeanPostProcessor等,会在后面Bean创建的各个阶段被依次调用。

有个细节要特别注意:这些BeanPostProcessor自身也是Bean,它们不是等到第11步统一创建,而是在这一步就被提前实例化了。原因很简单——如果不提前创建它们,后面创建普通Bean时就没有处理器可用了。所以Spring在代码中专门做了判断,beanPostProcessors里的Bean不会在后续的preInstantiateSingletons中重复创建。

3. Bean实例化全链路:从getBean到initializeBean

容器骨架搭好后,就到了最核心的部分——Bean的创建。这一步的方法是finishBeanFactoryInitialization,它会遍历所有非懒加载的单例Bean名称,逐个调用getBean来触发创建。这里才是真正考验你阅读耐心的地方。

3.1 preInstantiateSingletons如何决定Bean创建顺序

preInstantiateSingletons是第11步的核心方法。它先拿到所有BeanDefinition的名称列表,然后过滤出非抽象、非懒加载、单例的Bean,依次调用getBean。

这里有一个重要的实现细节:它不是先创建一个Bean再取下一个,而是先把所有单例Bean用getBean触发一遍(第一轮循环专注于创建),所有Bean创建完成之后再进行第二轮循环,调用SmartInitializingSingleton接口的回调。这个设计的目的是保证所有单例Bean都已经创建完毕,扩展接口可以安全地依赖任何Bean。

Bean的创建顺序有两个因素影响:一是beanDefinitionNames列表的顺序,它由BeanDefinition的注册顺序决定;二是@DependsOn注解显式声明的依赖关系,Spring会优先创建被依赖的Bean。如果你发现某个Bean创建顺序不符合直觉,多半就是这两个原因之一。

3.2 doGetBean的三级缓存查询与单例创建

getBean内部调用doGetBean,这个方法最核心的逻辑就在开头:

protected <T> T doGetBean(...) { // 第一关:尝试从缓存获取,这就是一级、二级、三级缓存查询的地方 Object sharedInstance = getSingleton(beanName); if (sharedInstance != null && args == null) { bean = getObjectForBeanInstance(sharedInstance, name, beanName, null); } else { // 缓存没命中,进入真正的创建逻辑 if (isPrototypeCurrentlyInCreation(beanName)) { throw new BeanCurrentlyInCreationException(beanName); } // 查找父容器... // 进入创建单例的模板方法 if (mbd.isSingleton()) { sharedInstance = getSingleton(beanName, () -> { return createBean(beanName, mbd, args); }); bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd); } } }

getSingleton(beanName)这个方法里实现了三级缓存的读取逻辑,我先按下不表,放到第4节专门讲。

单例Bean的创建是通过getSingleton(String, ObjectFactory)方法完成的。这个方法在被调用时会先把当前Bean标记为"正在创建中",然后调用ObjectFactory.getObject()实际执行createBean,创建完成后把成品放入一级缓存,最后清除"正在创建"标记。这里用到了模板方法模式,createBean的具体实现在AbstractAutowireCapableBeanFactory中。

3.3 createBeanInstance:三种实例化方式与循环依赖标记时机

AbstractAutowireCapableBeanFactory.createBean是真正负责"造Bean"的方法。它前面有一小段扩展逻辑:先调用resolveBeforeInstantiation,让InstantiationAwareBeanPostProcessor有机会在实例化前直接返回一个代理对象。大多数场景下这个钩子不会生效,但AOP相关组件会利用这个机制在某些特定场景下提前代理。

之后进入doCreateBean,这才是主流程。createBeanInstance负责实例化,它里面有三种方式:

  • 构造器自动装配:根据构造参数自动解析依赖,适合有参构造
  • 工厂方法实例化:调用@Bean方法或自定义工厂的静态方法创建对象
  • 无参构造简单实例化:最普通的方式,直接调用默认构造器

对于注解配置的@Bean方法,实例化走的是SimpleInstantiationStrategy中的工厂方法逻辑。对于@Component类,如果没有构造参数就是无参构造。对于有参构造的,会进入ConstructorResolver去解析构造参数。

在实例化完成后,doCreateBean还会做一件事:判断当前Bean是否可能被循环依赖引用,如果满足"单例 && 允许循环引用 && 正在创建中"三个条件,就会提前把一个ObjectFactory放入三级缓存。注意一级缓存放的是singletonObjects,三级缓存放的是singletonFactories,两者放的内容完全不一样。这个准备动作的意义,我会在循环依赖那一节展开。

3.4 populateBean:属性填充的完整顺序

实例化只是创建了一个空壳对象,属性还是null,接下来进入populateBean。这个方法刚开始时会再次调用InstantiationAwareBeanPostProcessor的postProcessAfterInstantiation,如果它返回false,就直接跳过属性填充阶段——这是给扩展点留下的"阻止自动注入"的开关。

正常情况继续往下走,核心逻辑是找到所有InstantiationAwareBeanPostProcessor并执行它们的postProcessProperties。这里最关键的实现是AutowiredAnnotationBeanPostProcessor,它会解析Bean内的@Autowired、@Value注解,并执行注入。

注入完成后才轮到XML配置里<property>标签或代码中手动设置的属性值,通过applyPropertyValues方法复制进去。所以你在使用Spring时要注意:@Autowired声明的注入优先级高于XML手动配置的property,如果两种方式配置了同一个字段,@Autowired注入的结果会被property覆盖。

3.5 initializeBean:初始化阶段那些容易被忽略的回调顺序

属性填充完毕,Bean还不是最终状态,需要执行初始化。initializeBean的完整执行顺序非常讲究,我按源码顺序列一下:

protected Object initializeBean(String beanName, Object bean, RootBeanDefinition mbd) { // 第一步:Aware接口回调 invokeAwareMethods(beanName, bean); // 第二步:BeanPostProcessor前置处理 Object wrappedBean = bean; wrappedBean = applyBeanPostProcessorsBeforeInitialization(wrappedBean, beanName); // 第三步:执行初始化方法 invokeInitMethods(beanName, wrappedBean, mbd); // 第四步:BeanPostProcessor后置处理(AOP代理的主要时机) wrappedBean = applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName); return wrappedBean; }

invokeAwareMethods只处理三个Aware接口:BeanNameAware、BeanClassLoaderAware、BeanFactoryAware。这里有很多人会困惑,ApplicationContextAware去哪了?其实它不在这个方法里,而是通过ApplicationContextAwareProcessor这个BeanPostProcessor在第二步的回调中执行的。Spring这样做的原因是,ApplicationContextAware需要访问ApplicationContext的完整能力,而这个能力容器启动后期才具备,所以放到后置处理器中执行更稳妥。

第三步invokeInitMethods也有顺序讲究:先判断Bean是否实现InitializingBean接口,是则调用afterPropertiesSet();随后再调用自定义的initMethod。这里有一个隐藏优先级:接口回调总是先于@Bean(initMethod = "...")指定的方法执行。如果你同时配了两种初始化方式,要先想清楚谁先谁后。

第四步的applyBeanPostProcessorsAfterInitialization是AOP代理的关键位置。AbstractAutoProxyCreator正是在这个方法中检查当前Bean是否有匹配的切面,如果匹配则生成代理对象返回。所以你现在应该能理解,为什么AOP代理发生在属性注入之后、Bean真正对外使用之前。

3.6 注册销毁方法与Bean生命周期对照

初始化完成后,registerDisposableBeanIfNecessary会检查Bean是否实现了DisposableBean接口或者配置了destroyMethod,如果满足条件,就注册到disposableBeans中,等待容器关闭时统一回调。

这个阶段完成后,单例Bean就被放入一级缓存singletonObjects,getBean就能直接拿到了。为了更直观,我把Bean从生到死的全过程整理成一个对照表:

阶段关键扩展点典型回调
实例化前InstantiationAwareBeanPostProcessor.postProcessBeforeInstantiation提前创建代理对象
实例化createBeanInstance构造器/工厂方法/无参构造
合并BeanDefinitionMergedBeanDefinitionPostProcessor.postProcessMergedBeanDefinition预解析@Autowired注入点
属性填充InstantiationAwareBeanPostProcessor.postProcessProperties@Autowired、@Value注入
初始化前BeanPostProcessor.postProcessBeforeInitializationApplicationContextAware回调
初始化InitializingBean.afterPropertiesSet / init-method自定义初始化逻辑
初始化后BeanPostProcessor.postProcessAfterInitializationAOP代理创建
销毁DisposableBean.destroy / destroy-method资源清理、连接关闭

4. 循环依赖与三级缓存:整个加载流程中最硬核的设计

聊IOC加载流程绕不开循环依赖。什么是循环依赖?A依赖B,B又依赖A。如果Spring不做任何处理,创建A时需要B,创建B时需要A,双方互相等待,必然死锁。Spring用三级缓存巧妙化解了这个问题。

4.1 三级缓存各自存放什么

DefaultSingletonBeanRegistry中有三个成员变量:

// 一级缓存:存放成品Bean private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256); // 二级缓存:存放早期暴露的半成品Bean private final Map<String, Object> earlySingletonObjects = new ConcurrentHashMap<>(16); // 三级缓存:存放ObjectFactory工厂 private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);

一级缓存大家最熟悉,getBean通常直接命中这里。二级缓存用于存放从三级缓存中提前取出的半成品Bean,它解决的是"同一个半成品只能被取一次"的问题。三级缓存存放的是ObjectFactory,这个工厂被调用时执行getEarlyBeanReference方法,让后置处理器有机会对早期引用进行包装(比如提前创建AOP代理)。

4.2 源码中循环依赖是如何一步步破解的

我拿A依赖B、B依赖A的场景走一遍完整流程:

创建A时,A的createBean执行完毕,实例化出A对象,然后A把getEarlyBeanReference包装为ObjectFactory放入三级缓存。接着进入populateBean填充属性,此时发现需要注入B,于是调用getBean(B)。

创建B时,和A一样,实例化后放入三级缓存,填充属性时发现需要A,再次调用getBean(A)。这次进入doGetBean时,先执行getSingleton("A"),查一级缓存没有,查二级缓存没有,查三级缓存——找到了!于是调用工厂方法得到A的早期引用,放入二级缓存,删除三级缓存条目。

B顺利拿到A的早期引用对象,完成属性填充和初始化,最终作为成品放入一级缓存。回到A的创建现场,它正在等着B,现在B已经创建完成,A获取到B的引用,继续完成自己的属性填充和初始化,最后也存入一级缓存。

整个过程的关键在于:三级缓存在A还没完成初始化时就能拿到A的引用(哪怕属性还没填),只要B消费的是同一个引用对象,最终就能保持一致。

4.3 为什么三级缓存必须是三级而不是两级

这是面试高频问题。你可以从代码中发现,二级缓存里存的是已经从三级工厂里取出的早期Bean,原因在于如果只有二级缓存,那么所有Bean在实例化后都要立刻执行AOP代理,即使它并未参与循环依赖,也会白白经历代理创建。

Spring这里采用懒惰策略:只有当真正发生循环引用、需要提前暴露对象时,才调用三级缓存的getEarlyBeanReference让后置处理器生成代理。没有循环依赖的Bean则等到最终阶段再走AOP代理。

如果去掉二级缓存直接使用三级,每次获取都要重新调用工厂方法,可能产生多个代理实例。二级缓存把早期引用缓存住,保证同一个Bean多次获取得到的是同一个对象。

4.4 构造器循环依赖为什么无法解决

三级缓存只能解决属性注入(setter/字段注入)的循环依赖,对构造器循环依赖无能为力。原因在于:构造器循环中,A实例化时因为构造参数需要B,B实例化时又因为构造参数需要A,可A在构造完成之前根本没有机会把三级缓存放进去。三级缓存里没有A,B就无法拿到A的引用。

用生活化的场景类比:你和室友互相等对方先出门拿钥匙,结果两人都堵在门口,谁也没法先挪一步。Spring对这个场景的处理就是抛异常,提示你调整设计。见过太多人在遇到BeanCurrentlyInCreationException时一头雾水,其实排查思路很简单:先看异常信息里提到的Bean,分析它是在构造器阶段还是属性注入阶段发生的循环引用,如果是前者,除了重构依赖关系没有别的路。

5. 源码阅读实操方法与问题排查

读完前面的流程拆解,你还需要一套能落地的调试方法和排查思路。我把自己实际调试源码时积累的经验和踩过的坑分享出来。

5.1 高效调试源码的断点配置清单

强烈建议用IDE的条件断点和表达式计算功能来辅助理解调用链。以下几个断点位置对于复现一次完整加载流程特别有价值:

断点位置观察目标调试价值
refresh() 方法开头容器启动整体步骤观察12个步骤的顺序
registerBeanDefinitionbeanDefinitionMap的变化观察每个类的注册情况
invokeBeanFactoryPostProcessors后置处理器的执行顺序看注解配置解析过程
doCreateBean 方法内当前创建的Bean名称配合条件断点只看某个Bean
populateBean 方法内属性填充的对象观察依赖注入过程
getSingleton 方法内三级缓存的存取时机剖析循环依赖核心逻辑

调试技巧方面,我有一个很实用的习惯:判断某个Bean到底在执行哪一步时,可以不用从头跑到尾,而是在doCreateBean上打条件断点,条件的写法类似beanName.equals("userService")。这样每次创建userService这个Bean时IDEA会精准停下,然后使用"Step Over"逐行走完整个方法,你就能看到这个Bean从实例化到初始化的全过程。

还有一个小技巧是善用IDE的"Evaluate Expression"功能。断点在populateBean停下后,你可以直接执行表达式查看当前beanFactory的beanDefinitionMap中有哪些BeanDefinition,或者查看getSingleton(beanName, true)的返回值,省去逐层翻找堆栈的时间。

5.2 高频问题排查思路

我给自己带过的开发者总结了几个最高频的排查方向,写成速查表:

现象可能原因排查方向
@Value注入为null占位符未被解析检查PropertySourcesPlaceholderConfigurer是否被注册且位于正确的priority
BeanPostProcessor未生效注册时机太晚确认它本身被Spring管理,且不是懒加载
BeanCurrentlyInCreationException构造器循环依赖查看异常中Bean名称,分析构造器依赖链
AOP代理未生效代理时机不对确认方法调用是从容器获取的Bean,而非内部this调用
Bean初始化方法没执行init-method拼写错误检查BeanDefinition中的initMethodName与方案名是否一致

有一个真实案例我记得很清楚。某同学在项目里定义了一个BeanPostProcessor,想在Bean初始化前修改某个字段,但调试时发现回调从未执行。后来发现这个后置处理器是在@Configuration类里通过@Bean方式注册的,而配置类本身在扫描阶段才被处理,注册后置处理器时它还不在beanDefinitionMap中,导致Spring根本没发现它。把他改成@Component放在扫描路径下,或者手动register到容器,问题才解决。

5.3 一条简单规则记住完整加载流程

最后分享一个帮助记忆的口诀:配置变图纸,图纸变定义,定义变实例,实例变代理。

配置类里的每一个类最终被解析成BeanDefinition,这一步对应"配置变图纸";BeanDefinitionRegistry把图纸注册到容器,对应"图纸变定义";finishBeanFactoryInitialization阶段按图纸创建实例并完成属性填充,对应"定义变实例";最后通过BeanPostProcessor完成AOP等增强,对应"实例变代理"。

只要你能把这条串联讲清楚,再往里填细节就水到渠成了。

我个人在实际阅读源码时最大的体会是:千万不要试图一次读透所有分支,盯住主链路就够了。先跑通一个最简单的配置,把refresh方法完整跑一遍,把每个阶段打印出来,再逐步深入二级细节。这样下来,你不仅能画出从AnnotationConfigApplicationContext到DefaultListableBeanFactory的调用路径,还能真正理解为什么Spring能把"创建对象"这一件事做得如此优雅。

最后再分享一个建议:源码学习一定要搭配调试实战。找个周末,打开IDE,建一个最简单的@Configuration加一个@Component,然后跟着refresh()的12个步骤走一遍,亲眼看一次beanDefinitionMap从空到满、singletonObjects从无到有的过程。这种直观感受,比读任何文章都更值得。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/14 5:17:44

Windows C开发必读:MessageBox细节与实战避坑指南

做Windows C开发的朋友&#xff0c;不管你是写上位机软件、做课程设计&#xff0c;还是业余倒腾点小工具&#xff0c;基本都躲不过MessageBox。可以说&#xff0c;它是Windows平台里最平易近人的一个交互接口&#xff1a;弹出一个小窗口&#xff0c;显示几行文字&#xff0c;等…

作者头像 李华
网站建设 2026/10/14 5:16:58

HCIA路由配置核心:静态路由与默认路由的原理、陷阱与排障

1. 项目概述&#xff1a;这不是一份普通的学习笔记&#xff0c;而是一张通往网络工程实操能力的路线图“HCIA_R&S-学习_Day03的副本-1”——光看这个标题&#xff0c;你可能以为它只是某位学员随手保存的课件备份。但在我带过几十期网络技术实训、参与过多个中小型企业网络…

作者头像 李华
网站建设 2026/10/14 5:16:57

SAP免费交货全流程详解:业务场景、底层逻辑与避坑指南

SAP 里的“免费交货”&#xff0c;做过物流和财务顾问的人基本都会碰到。它不是某一个 T-code 能讲完的独立功能&#xff0c;而是一条横跨销售订单、采购订单、库存移动、发票结算和财务凭证的完整业务链&#xff1a;货照发、账为零、库存照动。我第一次在项目里把这四个字真正…

作者头像 李华
网站建设 2026/10/14 5:16:43

价值投资核心理念与实战操作指南

1. 价值投资的核心理念解析沃伦巴菲特的投资哲学建立在"以合理价格买入优秀企业并长期持有"这一简单而深刻的理念之上。这位奥马哈先知最著名的比喻是&#xff1a;"如果你不愿意持有一只股票十年&#xff0c;那就不要持有它十分钟。"这种长期视角从根本上区…

作者头像 李华