☰
Spring容器初始化源码解析:从refresh到循环依赖的完整路径
2026/10/2 9:38:39 网站建设 项目流程

1. 开篇:为什么要啃Spring初始化这堆"废话"

做Java开发的人,一开始用Spring的时候基本都有这种感觉:配置文件一写,ClassPathXmlApplicationContext一new,Bean就自己出来了,Service、DAO、Controller全都被安排得明明白白。这玩意到底是咋做到的?我当时也是稀里糊涂用了两三年,直到有一天面试官问我"Spring的三级缓存是怎么解决循环依赖的",我才发现自己除了会背singletonObjects、earlySingletonObjects、singletonFactories这三个Map的名字之外,啥都说不清楚。

后来我把Spring容器的初始化源码从头到尾跟了一遍,从refresh()方法开始,一步步断点往下追,把BeanDefinition的加载、PostProcessor的执行、代理的创建、循环依赖的解决全都梳理了一遍。说实话,这个过程非常痛苦,因为Spring的类太多了,光refresh()这一个方法里调用的子方法就十几个,每个子方法背后又是一大堆类。但啃完一遍之后,你再看Spring就完全不一样了,很多面试题不用背也能答上来,排查起问题来也顺手得多。

这篇文章就是把我的源码阅读过程做个整理,从refresh()这棵大树的主干开始,一层层扒到getBean()和getSingleton()的细节里,最后再看Spring Boot把这一切包装成了什么样。内容会按主线逻辑展开,每个阶段都标上对应的源码类和方法名,方便你自己打开IDE对照着看。

2. Spring容器的启动入口:refresh()方法不像你想的那么简单

2.1 整个容器的起点

想要看Spring的初始化源码,第一步就是把AbstractApplicationContext.refresh()打开。这个方法在org.springframework.context.support包下,是所有ApplicationContext(包括ClassPathXmlApplicationContext、AnnotationConfigApplicationContext)启动时都要走的主流程。

@Override public void refresh() throws BeansException, IllegalStateException { synchronized (this.startupShutdownMonitor) { // 1. 准备上下文,记录启动时间、设置状态标志 prepareRefresh(); // 2. 获取BeanFactory,并准备读取配置文件 ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory(); // 3. 给BeanFactory配置一些标准特性 prepareBeanFactory(beanFactory); try { // 4. 允许子类对BeanFactory做后置处理 postProcessBeanFactory(beanFactory); // 5. 执行BeanFactoryPostProcessor invokeBeanFactoryPostProcessors(beanFactory); // 6. 注册BeanPostProcessor registerBeanPostProcessors(beanFactory); // 7. 初始化消息源(国际化) initMessageSource(); // 8. 初始化事件广播器 initApplicationEventMulticaster(); // 9. 模板方法,留给子类刷新其他bean onRefresh(); // 10. 注册事件监听器 registerListeners(); // 11. 预实例化所有非懒加载的单例bean finishBeanFactoryInitialization(beanFactory); // 12. 完成刷新,发布事件 finishRefresh(); } catch (BeansException ex) { // 销毁已创建的bean,重置容器 destroyBeans(); cancelRefresh(ex); throw ex; } finally { resetCommonCaches(); } } }

这段代码就是整个Spring容器的"总舵",所有初始化动作都从这12个步骤里展开。要注意的是,refresh()的命名其实挺有迷惑性——它不只是在"刷新",而是在容器第一次启动的时候完成全部的初始化工作。我把这12步分成了三大块:准备阶段(1-4步)、后处理器阶段(5-6步)、核心初始化阶段(7-12步)。下面一步步说。

2.2 prepareRefresh和obtainFreshBeanFactory到底干了什么

第一步prepareRefresh()做的事比较少,主要就是设置启动时间、关闭状态、初始化PropertySources环境变量。

第二步就重要了。obtainFreshBeanFactory()里调用refreshBeanFactory(),这个方法是分派给子类的。你如果用的ClassPathXmlApplicationContext,它的祖先类AbstractXmlApplicationContext就会在这里创建一个DefaultListableBeanFactory,然后调用loadBeanDefinitions()去解析XML配置文件。解析出来的每一个<bean>标签都会变成一条BeanDefinition,注册到这个BeanFactory的beanDefinitionMap里。

protected final void refreshBeanFactory() throws BeansException { // 如果已经存在BeanFactory,先销毁旧的 if (hasBeanFactory()) { destroyBeans(); closeBeanFactory(); } // 创建新的DefaultListableBeanFactory DefaultListableBeanFactory beanFactory = createBeanFactory(); beanFactory.setSerializationId(getId()); customizeBeanFactory(beanFactory); // 加载BeanDefinition loadBeanDefinitions(beanFactory); this.beanFactory = beanFactory; }

这个阶段的核心产出物就是BeanDefinition——你可以把它理解为"Bean的出生说明书",里面写了这个Bean的类名、作用域、懒不懒加载、依赖哪些属性、要不要走initMethod等等信息。这一步结束以后,容器已经知道自己需要创建哪些Bean了,但这个时候一个Bean实例都还没有创建,只是"图纸"就位了。

2.3 prepareBeanFactory和后处理器注册的机关

拿到BeanFactory之后,prepareBeanFactory()会给它配置一堆默认的东西:

  • 设置类加载器
  • 通过BeanExpressionResolver支持#{...}表达式
  • 添加ApplicationContextAwareProcessor这样的内置BeanPostProcessor,用来处理实现了Aware接口的Bean(比如实现ApplicationContextAware就能拿到容器本身)
  • 注册一些特殊Bean的依赖解析:BeanFactory、ResourceLoader、ApplicationEventPublisher、ApplicationContext等等,这些都是通过RESOLVABLE_DEPENDENCIES这个Map直接映射的
  • 检测并注册ApplicationListener类型的Bean

接下来是invokeBeanFactoryPostProcessors()和registerBeanPostProcessors(),这两个方法容易搞混。我的记忆方法是:

  • BeanFactoryPostProcessor:操作BeanDefinition的。它能在Bean实例化之前修改Bean的定义,比如修改某个Bean的作用域、给它加个属性值。像PropertySourcesPlaceholderConfigurer就是实现这个接口去替换${...}占位符的。
  • BeanPostProcessor:操作Bean实例的。它能在Bean实例化之后、初始化前后对Bean进行增强。像AutowiredAnnotationBeanPostProcessor就靠它来执行@Autowired的注入,AbstractAutoProxyCreator就靠它去生成AOP代理。

在invokeBeanFactoryPostProcessors()里头,有个很绕的逻辑:要先从beanFactory.getBeanNamesForType(BeanFactoryPostProcessor.class)查出所有已注册的BeanFactoryPostProcessor,然后按照优先级依次执行。这一步还会顺带把配置里的@Configuration类解析掉(通过ConfigurationClassPostProcessor),这就是注解驱动配置能被Spring识别的关键。

到了registerBeanPostProcessors(),容器会把所有BeanPostProcessor类型的Bean实例取出来(注意这里会触发它们的创建),然后按照PriorityOrdered、Ordered、无顺序这种优先级排好序,注册进beanFactory的beanPostProcessors列表里,供后面Bean实例化的时候调用。

3. BeanDefinition是怎么被解析和注册的

3.1 XML和注解两条路线的解析

前面提到loadBeanDefinitions()是XML时代的核心入口,这里展开看一下。如果你用的ClassPathXmlApplicationContext,它内部是由XmlBeanDefinitionReader来干活儿的,关键方法链是这样的:

AbstractXmlApplicationContext.loadBeanDefinitions() -> XmlBeanDefinitionReader.loadBeanDefinitions() -> AbstractBeanDefinitionReader.loadBeanDefinitions(Resource) -> XmlBeanDefinitionReader.doLoadBeanDefinitions() -> registerBeanDefinitions()

到了registerBeanDefinitions(),就交给DefaultBeanDefinitionDocumentReader去遍历XML的<beans>节点,遇到一个<bean>就解析成一个BeanDefinition,然后登记到BeanDefinitionRegistry里。

注解路线的解析大致类似,核心入口是ClassPathBeanDefinitionScanner.doScan()。扫描器会把指定包路径下的所有.class文件读出来,通过MetadataReader读取类上的注解元信息,判断这个类是不是候选组件(有没有@Component家族注解),然后把它组装成AnnotatedGenericBeanDefinition注册进容器。

现代项目里大多数是注解配置,此处多说一句:很多人以为@Configuration类里的@Bean方法走的也是扫描,其实不是。@Configuration类是通过ConfigurationClassPostProcessor(一个BeanFactoryPostProcessor)来处理的,它在后置阶段里解析出@Bean方法,然后把这些方法对应的Bean也注册成FactoryMethodBeanDefinition。

3.2 BeanDefinition在容器里到底怎么放的

DefaultListableBeanFactory里有几个Map值得记住:

/** Map of bean definition objects, keyed by bean name. */ private final Map<String, BeanDefinition> beanDefinitionMap = new ConcurrentHashMap<>(256); /** List of bean definition names, in registration order. */ private volatile List<String> beanDefinitionNames = new ArrayList<>(256);

注册的核心方法是registerBeanDefinition(),它做了这几件事:

  1. 判断当前是否允许修改BeanDefinition(如果在BeanFactory已被冻结(configurationFrozen)时还想注册,会有校验逻辑)
  2. 检查beanDefinitionMap里是否已经存在同名Bean,如果有,看allowBeanDefinitionOverriding属性允许不允许覆盖(Spring Boot里默认允许@Bean覆盖XML)
  3. 如果没有同名的,就把它放进Map,并把beanName添加到beanDefinitionNames列表

一个容易忽略的点:@Bean方法上如果带@Primary或者优先级标记,注册的时候会被处理成对应的bean定义属性。还有泛型相关的信息,比如ResolvableType,会在解析阶段被记录下来,这样后面按照泛型类型注入的时候才能匹配上,比如List<Handler>这种集合注入。

3.3 配置类和条件注解的处理时机

ConfigurationClassPostProcessor是一个很特殊的存在,它虽然不是用户手工声明注册的,但Spring会在refresh()的invokeBeanFactoryPostProcessors()阶段找到它,然后调用它的processConfigBeanDefinitions()方法。

这个方法里会做一件很多初学者不理解的事:它会二次读取配置类,把进口的、扫描到的、@Bean标注的、@Import引入的全部类给"展开",并且扫描配置类上有没有@Conditional注解。如果你的配置类或@Bean方法上写了@ConditionalOnProperty之类的条件,这里就是条件判断的现场。只有条件成立,对应的BeanDefinition才会被注册下去,否则整个分支都被跳过。

也正因为这个机制,你调beanFactory.getBeanDefinitionNames()的时候,看到的beanName列表里不仅有你手写的类名,还有Spring自动生成的内部名称,比如org.springframework.context.annotation.internalConfigurationAnnotationProcessor这种。

4. 三级缓存:循环依赖与代理对象的博弈

4.1 为什么需要三级缓存而不是两级

说到Spring初始化,绕不开的一定是循环依赖。举个最常见的例子:A类里注入B,B类里注入A。如果没有任何特殊机制,创建A的时候发现需要B,于是去创建B,而B又需要A,于是又回到创建A——直接死循环。

Spring的解决方案是提前曝光。具体来说,就是在A的实例化完成以后、属性填充之前,先把A的"早期引用"放进一个缓存里,这样B创建的时候能拿到A的引用,哪怕A当时还没完全初始化完。

但这里有个问题:如果A被AOP代理了呢?B里注入的应该是A的代理对象,而不是A的原生对象。如果我们在早期曝光的时候只放原生对象,后面再生成代理,B拿到的引用就不是代理了;如果一开始就生成代理,又违背了代理应该发生在Bean初始化之后的OOP设计逻辑,而且影响性能。为了解决这个矛盾,三级缓存的设计出现了:

// 一级缓存:存放完备的、可直接使用的单例Bean private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256); // 二级缓存:存放早期暴露的Bean(可能还不是最终形态) private final Map<String, Object> earlySingletonObjects = new ConcurrentHashMap<>(16); // 三级缓存:存放创建单例Bean的工厂 private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);

第一级是最终形态的Bean;第二级是"半成品"但已经能拿引用了;第三级存的是一个ObjectFactory,它在被调用的时候才去决定返回原生对象还是代理对象。

4.2 getSingleton里面的判断逻辑

DefaultSingletonBeanRegistry.getSingleton(String beanName, boolean allowEarlyReference)这段代码是解决循环依赖现场的关键:

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<?> singletonFactory = this.singletonFactories.get(beanName); if (singletonFactory != null) { singletonObject = singletonFactory.getObject(); this.earlySingletonObjects.put(beanName, singletonObject); this.singletonFactories.remove(beanName); } } } } } } return singletonObject; }

这个流程用大白话说就是:先从一级缓存找,找不到而且这个Bean正在创建中,就从二级缓存找,再找不到,就从三级缓存取出工厂,调用工厂的getObject(),结果放进二级缓存并移除三级缓存。

之所以有三级缓存,精髓就在这个**"取出工厂的那一刻才决定生成什么"**。三级缓存里存的不是Bean,而是一个ObjectFactory,Spring在这里通过SmartInstantiationAwareBeanPostProcessor(比如AOP内部的AbstractAutoProxyCreator)有机会对早期的Bean做"预告处理"——返回早期代理或早期暴露。

4.3 三级缓存之外的限制:不是所有循环依赖都能解

刷到这个位置,很多人会误以为三级缓存能解决所有循环依赖。实际上它只解决单例且非构造器注入的循环依赖。

  • 构造器注入的循环依赖没法解决。因为构造器注入要求创建对象的那一步就必须有依赖,此时连"早期引用"都搞不出来,容器直接会报BeanCurrentlyInCreationException。
  • 原型(prototype)作用域的循环依赖也没法解决。prototype的Bean每次都是新创建,根本不存在提前缓存同一个引用的概念。
  • @Async代理做循环依赖也有坑,因为@Async要求提前生成代理,时机上会有冲突。
  • Spring Boot 2.6之后默认禁止循环依赖了,启动的时候直接给你抛异常,报出The dependencies of some of the beans in the application context form a cycle,所以现在新项目里其实没什么机会再"享受"三级缓存的红利,老项目如果必须保留这个能力,需要自己在配置里加一句spring.main.allow-circular-references=true。

这里补一个我实际项目里踩过的坑:如果用了某些增强库(比如把Bean做成事务代理的BeanNameAutoProxyCreator),它可能在早期就创建了代理,导致后面真正该生成代理的时候反而拿不到原生对象,最终出现"类型不匹配"的奇怪错误。排查的时候记得检查是否有多个BeanPostProcessor在三级缓存的getObject()阶段做了互相干扰的操作。

5. Bean实例化:从getBean到完整对象

5.1 getBean的模板方法链

refresh()的最后核心阶段是finishBeanFactoryInitialization(),它会遍历beanDefinitionNames,把所有非懒加载、非抽象的单例Bean预创建出来,实际走的就是getBean(beanName)。AbstractBeanFactory.getBean()最终都会落到doGetBean(),这个方法的执行骨架大致是:

  1. 先尝试从getSingleton(beanName)取已经创建好的单例,如果取到就直接返回(顺便检查FactoryBean要返回getObject()的结果还是FactoryBean本身)
  2. 没取到就拿到BeanDefinition,看它有没有依赖(depends-on属性)需要先创建并都放进容器
  3. 按照作用域分支处理:单例的走createBean(),原型的走prototypeInstance = createBean(),自定义作用域走scope.get(...)
  4. 创建完成后,把结果放进缓存

5.2 创建实例的三条路线

AbstractAutowireCapableBeanFactory.createBeanInstance()里面创建对象的"招式"有三套:

// 1. 如果有工厂方法(比如@Bean方法调用),走instantiateUsingFactoryMethod() // 2. 如果存在带@Autowired构造器或有多个构造器参数,走autowireConstructor() // 3. 否则走instantiateBean() -> 默认无参构造器

这三条路线的选择逻辑有些讲究:Spring会先看当前Bean定义里头有没有声明工厂方法,如果有就用工厂方法;否则检查构造器,通过ConstructorResolver判断最佳构造器。@Autowired标注在有参构造器上的场景,就会走自动装配构造器;普通的POJO基本上都会无参构造器。instantiateBean()底层会用BeanUtils.instantiateClass(),通过反射Constructor.newInstance()创建实例。

这段有点绕的是:Spring为了性能,默认允许立即创建无参对象,但对有参构造器会做复杂的ConstructorResolver匹配——包括参数个数匹配、类型匹配、是否可以用默认值兜底等等,如果匹配不出来会报BeanInstantiationException。

5.3 populateBean:依赖注入的现场

对象创建出来后,立刻进入populateBean(),这一步有两种情况:

如果Bean实现了InstantiationAwareBeanPostProcessor接口,Spring会给它机会拦截处理:比如applyBeanPostProcessorsBeforeInstantiation返回非null的话,实例化都还没发生就直接返回了。但更多情况是把控制权交给后处理器来"决定要不要用某种方式注入属性"。

常规路径是执行PropertyValues的填充,然后通过AutowiredAnnotationBeanPostProcessor来执行@Autowired字段注入、Setter注入、方法注入。这里有个值得说的"坑":

  • 如果你在AOP切点里面对@Autowired字段做了增强,并且切面表达的@Pointcut是匹配到这个字段上,理论上@Autowired注入完成之后需要再触发一轮后置处理器才能让代理替代原生Bean,但这在字段上是没有的。这也是为什么"基于字段注入的Bean"在AOP场景下有时会出现奇怪问题的原因之一——构造器注入反而更安全。

populateBean()里还会处理@Value占位符的填充,走的是StringValueResolver。

5.4 initializeBean:初始化和回调的最终现场

最后一步是initializeBean(),这里做了四件事:

  1. 执行invokeAwareMethods():如果Bean实现了BeanNameAware、BeanClassLoaderAware、BeanFactoryAware,在这里即时回调
  2. 执行applyBeanPostProcessorsBeforeInitialization():把实现了BeanPostProcessor.postProcessBeforeInitialization()的逻辑先跑一遍(比如ApplicationContextAwareProcessor会在这里注入各种Aware接口)
  3. 执行invokeInitMethods():先看Bean有无InitializingBean接口,有就调用afterPropertiesSet();再查BeanDefinition里配置的initMethod(比如<bean init-method="init"/>或者@Bean(initMethod = "init"))
  4. 执行applyBeanPostProcessorsAfterInitialization():这一步对AbstractAutoProxyCreator来说就是创建AOP代理的最后机会

一个完整的Bean初始化顺序可以总结为:对象实例化 -> 属性填充 -> Aware回调 -> BeanPostProcessor前置处理 -> InitializingBean -> initMethod -> BeanPostProcessor后置处理。面试的时候把这个链条背顺,基本上Spring Bean生命周期这个板块就稳了。实际排查问题时,你要习惯在initializeBean()的各个方法上打断点,比如发现某个Bean初始化异常,立刻能定位是后置处理器干的还是initMethod干的。

6. Spring Boot把初始化流程包装成了什么样

6.1 SpringApplication.run的一路狂奔

Spring Boot入局之后,初始化入口从AbstractApplicationContext.refresh()被搬到了SpringApplication.run()上。它做了一堆自动推断和硬编码的前置准备:判断Web应用类型(REACTIVE还是SERVLET还是NONE)、从META-INF/spring.factories里加载ApplicationContextInitializer和ApplicationListener、创建ConfigurableEnvironment并绑定命令行参数等等。

到了真正创建容器的步骤,Boot会根据webApplicationType去ApplicationContextFactory里找对应的工厂,然后SpringApplication.refreshContext()会先把配置类(主启动类)作为AnnotatedBeanDefinitionReader的解析对象注册进去,才走我们前面分析的那套AbstractApplicationContext.refresh()流程。

6.2 自动配置的底层实现

很多人管Spring Boot叫"魔法",但扒开之后其实全靠三个机制:

  • @EnableAutoConfiguration-> 通过@Import(AutoConfigurationImportSelector.class)引入一个处理自动配置类加载的选择器
  • AutoConfigurationImportSelector-> 把META-INF/spring.factories里所有EnableAutoConfiguration配置类的名单读出来,再用排除项、条件判断过滤掉不合适的
  • Condition体系-> 像@ConditionalOnClass、@ConditionalOnMissingBean这些注解,本质上是在运行期做类加载器级别的检查

所以Spring Boot启动时,refresh()内部那套流程还在,只不过BeanDefinition的"图纸"来源变了:不仅有用户写的@Component扫描,还有自动配置类里用@Bean办法注册的一大堆组件。

6.3 Spring Boot下初始化排查常用的几个开关

实际开发中,大家最需要的能力是定位"哪个自动化配置被加载了、哪个没生效"。我常用这几个手段:

  • 写配置debug=true,启动时控制台会打印自动配置报告,列出哪些自动配置匹配成功、哪些因条件不匹配被跳过
  • 用/actuator/conditions端点(需要引入actuator)动态查每个Condtion的匹配情况
  • 在ContextRefreshedEvent事件里看getBeanDefinitionNames(),判断哪些Bean被注册进来了

如果你发现一个配置类整体没生效,重点排查两类:包扫描路径是否覆盖到了主启动类所在包的basePackage;@ConditionalOnClass判断依赖不存在导致配置被跳过。这些排查手段比去看几百行的启动日志高效得多。

7. 源码阅读中的常见问题与排查技巧

7.1 看起来"没执行refresh"是怎么回事

排查的时候很容易发现:在某些场景下refresh()没有走完整流程。例如,AnnotationConfigServletWebServerApplicationContext在构造器里会直接调refresh(),而SpringApplication.run()内部会提前创建好容器实例再主动调refresh。如果你在一个未定义webApplicationType的普通模式下,可能根本走的不是同一个容器类。所以排查前先确认容器类型,再谈流程。

另一种情况是在registerShutdownHook场景下,容器关闭后再次调用getBean()会发现没有顺序问题,因为refresh()失败时destroyBeans()已经帮你清理过了,但这时的BeanFactory里残留的状态还得额外处理,建议直接重新new一个容器而不是复用旧的。

7.2 循环依赖报错但代码里看起来没循环

这个问题我遇到过好几次。表面上看只是A依赖B、B依赖C、C又依赖A,但排查时发现纯构造器不知道哪里出了问题,或者AOP代理导致看似解耦的类其实还持有彼此引用。这里给一个非常实用的排查手段:把isCreatingBeanNames集合打出来。在DefaultSingletonBeanRegistry里有个inCreationCheckExclusions和singletonsCurrentlyInCreation集合,断点看一眼就知道当前正卡在哪个Bean的创建中。一般异常信息里也会列出Bean的三条依赖链条:

The dependencies of some of the beans in the application context form a cycle: ┌─────┐ | aService (field private bService com.example.aService.b) ↑↓ bService (field private aService com.example.bService.a) └─────┘

顺着链条找就能定位。不要死盯业务代码,先看是构造器循环还是字段循环。

7.3 Bean初始化阶段顺序"错乱"的奇怪现象

有些人喜欢在@Component的构造器里干太多事,然后在@PostConstruct里又做一遍,结果发现事务代理、AOP一些回调顺序“不对”。原因其实并不奇怪:@PostConstruct是通过CommonAnnotationBeanPostProcessor执行的,这个后处理器是InstantiationAwareBeanPostProcessor的实现,而AOP代理的AbstractAutoProxyCreator也是类似类型,注册顺序和BeanPostProcessor优先级决定了谁先跑谁后跑。

如果你想控制某个后处理器的执行顺序,可以实现PriorityOrdered或Ordered接口,并注意同类接口之间按优先级排;想知道当前容器里BeanPostProcessor的执行顺序,在registerBeanPostProcessors()之后的beanFactory断点里查beanPostProcessors列表,一目了然。

7.4 源码阅读效率提升的几个建议

源码这东西,硬读很痛苦,容易被类名绕晕。我的经验是三个字:跟主线。第一遍只跟refresh()->finishBeanFactoryInitialization()->getBean()->doCreateBean()->initializeBean()这一条主路,其它分支(事件、消息源、自定义作用域)统统先不管。第二遍再回头看BeanPostProcessor扫描和循环依赖。第三遍才去研究AOP代理和事务的拦截链。

断点方面有个小技巧:直接在AbstractAutowireCapableBeanFactory.doCreateBean()里打断点,然后每见到一个Bean就看一遍beanName,看几层你就对容器的创建顺序有感觉了。配合Idea的"Evaluate Expression",可以随时执行beanFactory.getBeanDefinitionNames()来观察容器当前注册了哪些Bean。

7.5 老项目改造和升级时的两个提醒

一是升级到Spring Boot 2.6+之后,默认循环依赖被禁,老项目里如果满屏循环依赖,大概率能在启动时看到直白的报错,这时别直接改配置开白名单,要趁这个机会把某个中间层抽出去,或者改用构造器注入以外的方式解耦。二是注意BeanFactoryPostProcessor和BeanPostProcessor的执行顺序在版本历史中发生过调整,升级大版本之后某些"隐式依赖顺序"的项目会出现初始化偶发问题,这时候优先看官方迁移指南,别盲目改代码。

8. 源码分析收尾:把这条路走通以后能收获什么

我个人的体会是,源码分析切忌贪多求快。刚开始看Spring初始化,最容易犯的错是每个类都想去点进去看一眼,结果一头扎进BeanUtils、ReflectionUtils的深坑里,最后出不来。我的做法是每轮阅读只定一个很小的目标:第一轮只看容器怎么把BeanDefinition收集齐,第二轮只看单例Bean创建的三个缓存如何配合,第三轮再解决"初始化方法在什么时候被调用"这种具体问题。每次带着问题去refresh()的某个子方法里面找答案,返回的时候顺手把连带的类名记下来,几轮下来自然能串成完整图景。

另外想提醒的是,如果你所在的团队正好在折腾Spring Boot的自动装配、或者写自己的BeanPostProcessor/BeanFactoryPostProcessor,建议把源码解读成"团队内部分享PPT"的形式讲一遍。讲给同事听比自己闷头看一遍有效得多——很多你以为懂了的地方,一开口讲会发现其实还是模糊的。

这条路走通以后,对你理解Spring Security、事务管理、@Async这些上层框架会有直接帮助。它们的实现本质都是往Bean的生命周期里塞后处理器和代理逻辑而已。你掌握了主线之后,再遇到什么奇怪的"Bean失效""代理不生效""初始化顺序问题",就拥有了第一手的排查地图。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询