ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

Spring容器初始化源码解析:从refresh到循环依赖的完整路径

Spring容器初始化源码解析:从refresh到循环依赖的完整路径 1. 开篇为什么要啃Spring初始化这堆废话做Java开发的人一开始用Spring的时候基本都有这种感觉配置文件一写ClassPathXmlApplicationContext一newBean就自己出来了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 MapString, BeanDefinition beanDefinitionMap new ConcurrentHashMap(256); /** List of bean definition names, in registration order. */ private volatile ListString beanDefinitionNames new ArrayList(256);注册的核心方法是registerBeanDefinition()它做了这几件事判断当前是否允许修改BeanDefinition如果在BeanFactory已被冻结configurationFrozen时还想注册会有校验逻辑检查beanDefinitionMap里是否已经存在同名Bean如果有看allowBeanDefinitionOverriding属性允许不允许覆盖Spring Boot里默认允许Bean覆盖XML如果没有同名的就把它放进Map并把beanName添加到beanDefinitionNames列表一个容易忽略的点Bean方法上如果带Primary或者优先级标记注册的时候会被处理成对应的bean定义属性。还有泛型相关的信息比如ResolvableType会在解析阶段被记录下来这样后面按照泛型类型注入的时候才能匹配上比如ListHandler这种集合注入。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类里注入BB类里注入A。如果没有任何特殊机制创建A的时候发现需要B于是去创建B而B又需要A于是又回到创建A——直接死循环。Spring的解决方案是提前曝光。具体来说就是在A的实例化完成以后、属性填充之前先把A的早期引用放进一个缓存里这样B创建的时候能拿到A的引用哪怕A当时还没完全初始化完。但这里有个问题如果A被AOP代理了呢B里注入的应该是A的代理对象而不是A的原生对象。如果我们在早期曝光的时候只放原生对象后面再生成代理B拿到的引用就不是代理了如果一开始就生成代理又违背了代理应该发生在Bean初始化之后的OOP设计逻辑而且影响性能。为了解决这个矛盾三级缓存的设计出现了// 一级缓存存放完备的、可直接使用的单例Bean private final MapString, Object singletonObjects new ConcurrentHashMap(256); // 二级缓存存放早期暴露的Bean可能还不是最终形态 private final MapString, Object earlySingletonObjects new ConcurrentHashMap(16); // 三级缓存存放创建单例Bean的工厂 private final MapString, 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而是一个ObjectFactorySpring在这里通过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-referencestrue。这里补一个我实际项目里踩过的坑如果用了某些增强库比如把Bean做成事务代理的BeanNameAutoProxyCreator它可能在早期就创建了代理导致后面真正该生成代理的时候反而拿不到原生对象最终出现类型不匹配的奇怪错误。排查的时候记得检查是否有多个BeanPostProcessor在三级缓存的getObject()阶段做了互相干扰的操作。5. Bean实例化从getBean到完整对象5.1 getBean的模板方法链refresh()的最后核心阶段是finishBeanFactoryInitialization()它会遍历beanDefinitionNames把所有非懒加载、非抽象的单例Bean预创建出来实际走的就是getBean(beanName)。AbstractBeanFactory.getBean()最终都会落到doGetBean()这个方法的执行骨架大致是先尝试从getSingleton(beanName)取已经创建好的单例如果取到就直接返回顺便检查FactoryBean要返回getObject()的结果还是FactoryBean本身没取到就拿到BeanDefinition看它有没有依赖depends-on属性需要先创建并都放进容器按照作用域分支处理单例的走createBean()原型的走prototypeInstance createBean()自定义作用域走scope.get(...)创建完成后把结果放进缓存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()这里做了四件事执行invokeAwareMethods()如果Bean实现了BeanNameAware、BeanClassLoaderAware、BeanFactoryAware在这里即时回调执行applyBeanPostProcessorsBeforeInitialization()把实现了BeanPostProcessor.postProcessBeforeInitialization()的逻辑先跑一遍比如ApplicationContextAwareProcessor会在这里注入各种Aware接口执行invokeInitMethods()先看Bean有无InitializingBean接口有就调用afterPropertiesSet()再查BeanDefinition里配置的initMethod比如bean init-methodinit/或者Bean(initMethod init)执行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下初始化排查常用的几个开关实际开发中大家最需要的能力是定位哪个自动化配置被加载了、哪个没生效。我常用这几个手段写配置debugtrue启动时控制台会打印自动配置报告列出哪些自动配置匹配成功、哪些因条件不匹配被跳过用/actuator/conditions端点需要引入actuator动态查每个Condtion的匹配情况在ContextRefreshedEvent事件里看getBeanDefinitionNames()判断哪些Bean被注册进来了如果你发现一个配置类整体没生效重点排查两类包扫描路径是否覆盖到了主启动类所在包的basePackageConditionalOnClass判断依赖不存在导致配置被跳过。这些排查手段比去看几百行的启动日志高效得多。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失效代理不生效初始化顺序问题就拥有了第一手的排查地图。
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进