Spring Framework 7.0.9 深度剖析-03-IoC 容器核心:BeanFactory 与 BeanDefinition 体系
一、本章导读
如果说整个 Spring Framework 是一台巨大的机器,那么 BeanFactory 就是这台机器的主轴。开发者日常写的 @Service、@Autowired、@Configuration,最终都要落到这条主轴上:先有一份描述"这个对象该怎么造、叫什么、依赖谁"的元数据(BeanDefinition),再由一个工厂把这份元数据兑现成真正的 Java 对象。本章要做的,就是把这条主轴从接口到数据结构、从注册到查找、从单例缓存到依赖候选筛选,逐层拆开看清楚。
本章的全部源码都来自仓库 spring-beans 模块,核心入口目录是 spring-beans/src/main/java/org/springframework/beans/factory/,以及它下面的 factory/support/(实现类)与 factory/config/(配置面接口)。涉及的核心类包括:
- 接口族:
BeanFactory、HierarchicalBeanFactory、ListableBeanFactory、AutowireCapableBeanFactory、ConfigurableBeanFactory、ConfigurableListableBeanFactory,以及辅助接口FactoryBean、SmartFactoryBean、AutowireCandidateResolver、DependencyDescriptor、InjectionPoint。 - 实现族:
DefaultSingletonBeanRegistry(单例注册表)、AbstractBeanFactory(doGetBean主流程)、AbstractAutowireCapableBeanFactory(实例化与属性填充的骨架,本章只点到为止,生命周期细节留给后续章节)、DefaultListableBeanFactory(最终的全功能工厂,也是ApplicationContext内部真正持有的那个工厂)。 - 元模型族:
BeanDefinition接口及其实现AbstractBeanDefinition、GenericBeanDefinition、RootBeanDefinition、ChildBeanDefinition,以及注解驱动场景下的AnnotatedGenericBeanDefinition、ScannedGenericBeanDefinition。
需要的前置知识很简单:知道什么是依赖注入、写过 @Autowired、见过 ApplicationContext.getBean() 即可。读完本章你应当能回答这些问题:
BeanFactory那一大坨接口到底谁管什么?为什么不把所有方法塞到一个接口里?DefaultListableBeanFactory内部到底用了哪几个 Map、哪几个 List 来存"bean 定义"和"单例实例"?注册一个新定义时,并发安全是怎么保证的?同名重复注册会发生什么?- 一次
getBean("userService")调用,从接口方法到最终拿到对象,中间经过了哪几层?名字里的&、别名、父工厂回退、单例缓存命中分别在哪一步处理? @Autowired UserService userService;在容器里是怎么找到那个合适的 bean 的?候选有多个时,@Primary、@Priority、字段名分别在什么优先级上起作用?- 为什么
BeanDefinition有Generic、Root、Child这么多子类?"合并后的 BeanDefinition"(MergedBeanDefinition)到底合并了什么、缓存在哪里?
本章聚焦"工厂接口族 + 注册表数据结构 + BeanDefinition 元模型 + 依赖解析入口"。bean 的创建、属性填充、初始化回调、循环依赖的三级缓存细节,属于生命周期范畴,本章只在 doGetBean 必经之路上点到为止,不展开。
一个贯穿全章的版本事实先放在这里:本章所有代码均来自 Spring Framework 7.0.9 (gradle.properties 中 version=7.0.9)。7.0 在预实例化阶段引入了后台并行实例化 能力(bootstrapExecutor、isBackgroundInit()、PreInstantiation 线程标记),单例锁也从 6.x 的 synchronized 块演进为 ReentrantLock 的 tryLock 快速失败策略------这些与网上常见的 5.x/6.0 教程描述不同,本章会在对应位置明确点出。
二、核心概念:先建立一张全景图
2.1 BeanFactory:IoC 容器的"最小客户视图"
BeanFactory(路径 spring-beans/src/main/java/org/springframework/beans/factory/BeanFactory.java)是访问 Spring bean 容器的根接口 。它的类注释里有一句关键定位:"This is the basic client view of a bean container"------它是给使用方 看的最小视图。也就是说,业务代码拿到一个 BeanFactory 引用时,它能做的事情被刻意限制在"查、取、判断"这三类:
- 取对象:
getBean家族; - 查存在:
containsBean; - 查属性:
isSingleton、isPrototype、getType、isTypeMatch、getAliases。
它不包含 任何"注册一个 bean 定义进去"的方法,也不包含"枚举所有 bean"的方法。这种刻意的窄接口设计,是 Spring 接口隔离原则(ISP)的典型体现:客户不应该看见它用不到的东西。
这个接口里定义了一个全容器最重要的常量(见 BeanFactory.java 第 131-137 行):
java
String FACTORY_BEAN_PREFIX = "&";
/**
* Character variant of {@link #FACTORY_BEAN_PREFIX}.
* @since 6.2.6
*/
char FACTORY_BEAN_PREFIX_CHAR = '&';
& 前缀是 FactoryBean 机制的协议基石:按名字取 bean 时,名字带 & 表示"我要那个 FactoryBean 本身",不带 & 表示"我要 FactoryBean 生产出来的那个对象"。7.0.9 额外提供了一个 char 型变体 FACTORY_BEAN_PREFIX_CHAR(自 6.2.6 起),用于避免在热路径上频繁做字符串拼接------这是一个微小但真实存在的性能优化点,后文讲 transformedBeanName 时会再遇到。
2.2 接口族的继承关系
BeanFactory 并不是孤立的,它周围有一组"按用途拆分"的子接口。7.0.9 中这条继承链是:
java
BeanFactory(根:取/查/判)
▲
┌─────────────┼──────────────────┐
│ │ │
HierarchicalBeanFactory ListableBeanFactory (无父)
│ │
└──────┬──────┘
│
ConfigurableBeanFactory(可配置面:scope/类加载器/类型转换/占位符)
▲
│
AutowireCapableBeanFactory(外部创建 bean + 注入能力)
▲
│
ConfigurableListableBeanFactory(聚合:可枚举 + 可配置 + 预实例化)
它们各自"管什么",用一张职责边界表说清楚(路径均相对于 spring-beans/src/main/java/org/springframework/beans/factory/):
| 接口 | 路径 | 一句话职责 | 典型方法 |
|---|---|---|---|
BeanFactory |
BeanFactory.java |
最小客户视图:按名/按类型取单个 bean、判断单例原型、查别名 | getBean、containsBean、isSingleton、getType、getAliases |
HierarchicalBeanFactory |
HierarchicalBeanFactory.java |
父子工厂层次:拿到父工厂、只查本地不回溯祖先 | getParentBeanFactory、containsLocalBean |
ListableBeanFactory |
ListableBeanFactory.java |
可枚举:批量列出 bean 名、按类型批量取 Map | containsBeanDefinition、getBeanDefinitionNames、getBeanNamesForType、getBeansOfType |
AutowireCapableBeanFactory |
config/AutowireCapableBeanFactory.java |
对容器外部 new 出来的对象执行完整 bean 生命周期 | createBean(Class)、autowireBean、initializeBean、applyBeanPostProcessorsBefore/AfterInitialization |
ConfigurableBeanFactory |
config/ConfigurableBeanFactory.java |
工厂的"配置面板":设父工厂、注册 scope、加 BeanPostProcessor、类型转换器、占位符解析器、注册可解析依赖类型 | setParentBeanFactory、registerScope、addBeanPostProcessor、setBeanClassLoader、resolveEmbeddedValue |
ConfigurableListableBeanFactory |
config/ConfigurableListableBeanFactory.java |
聚合上述能力 + 冻结配置 + 预实例化单例 | ignoreDependencyInterface、registerResolvableDependency、resolveDependency、freezeConfiguration、preInstantiateSingletons |
这张表是本章的"地图"。后面每一节,我们都会回到这张表,看某个方法究竟落在哪个接口、由哪个实现类兑现。
2.3 三个必须先搞懂的核心抽象
在进入源码之前,先把三个贯穿全章的抽象立起来:
(1)BeanDefinition(bean 定义) 。它是"造一个 bean 的配方",不是 bean 本身。接口在 config/BeanDefinition.java,描述了:这个 bean 叫什么类(getBeanClassName)、什么作用域(getScope)、是否懒加载(isLazyInit)、依赖谁(getDependsOn)、有哪些属性值和构造参数、初始化/销毁方法叫什么(getInitMethodName/getDestroyMethodName)、角色是什么(getRole)、父定义是谁(getParentName)。BeanDefinition 本身不持有实例,它只是元数据。真正的对象是工厂在运行时根据这份配方"new"出来的。
(2)Singleton(单例注册表)与 BeanDefinition(定义注册表)是两张不同的表 。新手最容易混淆的一点:beanDefinitionMap 存的是配方 (BeanDefinition 对象),singletonObjects 存的是成品 (已经造好的 Java 对象)。一个是"图纸",一个是"仓库里的货"。getBean("userService") 第一次走"按图纸造货入库",之后走"直接取货"。本章会反复回到这两张表的配合上。
(3)FactoryBean(工厂 bean)是一种特殊的 bean 。普通 bean 本身就是它要暴露的对象;FactoryBean 是一个"能生产别的 bean 的 bean"。它自己也存在容器里(名字不带 & 时它就是一个普通 singleton),但 getBean("xxx") 拿到的却是它 getObject() 生产出来的对象。& 前缀就是用来在这两者之间切换的开关。
把这三件事记住,后面的源码读起来就不会迷路。
三、源码剖析(一):BeanFactory 接口族逐个拆解
这一节我们不看实现,只看接口本身。接口是契约,把契约读懂,实现类做什么就一目了然。
3.1 BeanFactory:getBean 家族与"查"方法
打开 BeanFactory.java,除了 & 前缀常量,它一共定义了五组 getBean 重载、一组 getBeanProvider、以及 containsBean/isSingleton/isPrototype/isTypeMatch/getType/getAliases。先看 getBean 家族(第 155-232 行):
java
Object getBean(String name) throws BeansException;
<T> T getBean(String name, Class<T> requiredType) throws BeansException;
Object getBean(String name, @Nullable Object @Nullable ... args) throws BeansException;
<T> T getBean(Class<T> requiredType) throws BeansException;
<T> T getBean(Class<T> requiredType, @Nullable Object @Nullable ... args) throws BeansException;
逐组解读:
- 按名取 :
getBean(String name)是最经典的入口。注释里写明三件事:会把别名翻译回规范名;本地找不到会问父工厂;返回值永不为 null(但可能是一个代表 null 的NullBean占位,用equals(null)判断)。 - 按名 + 按类型校验 :
getBean(String, Class<T>)在按名取到之后多一步类型检查,类型不符抛BeanNotOfRequiredTypeException,而不是让调用方在强转时才炸ClassCastException。 - 按名 + 显式构造参数 :
getBean(String, Object...)的args只在新建实例时生效 ------也就是说它只对 prototype(或尚未创建的 singleton)有意义。注释里专门警告:如果给了args但目标 bean 不是 prototype,会抛BeanDefinitionStoreException。原因很简单:singleton 只造一次,你第二次传不同的构造参数进去,到底听谁的?容器选择直接报错而不是悄悄忽略。 - 按类型取 :
getBean(Class<T>)是 3.0 引入的"by-type"入口。它的语义注释很关键------"goes into ListableBeanFactory by-type lookup territory",也就是说这个方法虽然定义在根接口上,实际行为要靠ListableBeanFactory的批量能力来支撑。它会在容器里找"唯一匹配该类型的 bean";找不到抛NoSuchBeanDefinitionException,找到多个抛NoUniqueBeanDefinitionException。 - 按类型 + 显式参数 :
getBean(Class<T>, Object...)是 4.1 的组合版。
注意一个细节:所有这些 getBean 在根接口上都只是声明 ,真正的实现落在 AbstractBeanFactory.doGetBean(按名)和 DefaultListableBeanFactory.resolveBean(按类型)里。接口族设计的精髓就在这里------根接口只暴露行为签名,把"按名"和"按类型"两条路线的实现分别下沉到不同的抽象层。
再看 getBeanProvider(第 243-282 行):
java
<T> ObjectProvider<T> getBeanProvider(Class<T> requiredType);
<T> ObjectProvider<T> getBeanProvider(ResolvableType requiredType);
@since 7.0
<T> ObjectProvider<T> getBeanProvider(ParameterizedTypeReference<T> requiredType);
ObjectProvider 是 4.0 引入、5.1 完善的"延迟查找句柄"。它允许你拿到一个 provider,在真正需要时才去容器里取,而且支持 getIfAvailable()(没有就返回 null 而不是抛异常)、getIfUnique()(唯一才返回)、ifAvailable(Consumer) 等宽容语义。7.0.9 新增了第三个重载:接受 ParameterizedTypeReference<T>,让你能写出 getBeanProvider(new ParameterizedTypeReference<List<Foo>>() {}) 这样的带泛型精确匹配------这是为了让编程式取带泛型的 bean 时不必丢失泛型信息。
"查"方法组里最值得讲的是 getType 的重载(第 385-405 行):
java
@Nullable Class<?> getType(String name) throws NoSuchBeanDefinitionException;
@since 5.2
@Nullable Class<?> getType(String name, boolean allowFactoryBeanInit) throws NoSuchBeanDefinitionException;
第二个重载的 allowFactoryBeanInit 参数是 5.2 加的。为什么需要它?因为对一个 FactoryBean 来说,要知道它生产出来的对象类型,理想情况是读它 getObjectType() 的返回值;但如果这个 FactoryBean 自己还没初始化、getObjectType() 又写得很笨(必须等到 getObject() 跑过才能确定类型),那类型探测就可能被迫把整个 FactoryBean 初始化出来。allowFactoryBeanInit=false 就是给"类型检查场景"开的绿灯变红灯:宁可返回 null 也不要为了看一眼类型而触发副作用初始化。这个参数在后面 getBeanNamesForType 的批量扫描里至关重要。
3.2 HierarchicalBeanFactory:父子层次的"只读切面"
这个接口极其短小,全文 52 行,只有两个方法(HierarchicalBeanFactory.java 第 39-50 行):
java
@Nullable BeanFactory getParentBeanFactory();
boolean containsLocalBean(String name);
它存在的意义是把"层次结构"从根接口里剥离出来。根接口 BeanFactory 的注释里说:"如果本地找不到,会问父工厂"------但这种"自动向上回溯"的行为,只对部分方法 成立。HierarchicalBeanFactory 补了一个反向操作:containsLocalBean(name) 只查本地、忽略祖先。这在框架内部很有用:比如注册一个 bean 定义时,必须确认本地没有重名,不能被父工厂里的同名 bean 干扰。
setParentBeanFactory() 并不在这个接口上,而是在 ConfigurableBeanFactory 上------这又是一次读写分离:HierarchicalBeanFactory 只读父引用,设置父工厂属于"配置面"。
3.3 ListableBeanFactory:可枚举的批量视图
ListableBeanFactory extends BeanFactory(第 60 行)。它的类注释开宗明义:它是"可以枚举所有 bean、而不是客户一个个按名查"的工厂。典型实现是 XML 驱动的工厂(事先把所有定义都加载进来了)。
它的核心方法分三组。第一组是"定义表"的直接计数与列举(第 71-90 行):
java
boolean containsBeanDefinition(String beanName);
int getBeanDefinitionCount();
String[] getBeanDefinitionNames();
注意这里的语义边界:这些方法只看本地 、只看 bean 定义 ,忽略父工厂、也忽略 registerSingleton 手动塞进来的单例。也就是说,你手工 registerSingleton("foo", obj) 一个对象,getBeanDefinitionCount() 不会把它算进去,但 getBeanNamesForType/getBeansOfType 会把它算进去------类注释第 40-48 行专门解释了这种不对称。
第二组是按类型列举名字:getBeanNamesForType(...) 有四个重载(Class/ResolvableType × 无参/带 includeNonSingletons, allowEagerInit)。第三组是按类型批量取实例:getBeansOfType(...) 返回 Map<String,T>。这两组方法的注释里反复强调一件事:它们只内省顶层 bean,不检查嵌套的内部 bean ;而且返回的名字/实例应当尽量按定义顺序排列 。这个"按定义顺序"的承诺非常重要,后文讲 List 注入顺序时会用到。
这个接口还有一个 5.3 加的便捷方法(第 110 行):<T> ObjectProvider<T> getBeanProvider(Class<T> requiredType, boolean allowEagerInit),让批量场景也能拿到 provider。
3.4 AutowireCapableBeanFactory:把生命周期开放给容器外
AutowireCapableBeanFactory extends HierarchicalBeanFactory(注意它继承的是 HierarchicalBeanFactory 而不是 ListableBeanFactory)。它解决的是一个特殊问题:当你手里有一个自己 new 出来的对象(不是容器造的),想让容器对它执行一遍完整的注入 + 初始化回调,该怎么办?
它的关键方法(config/AutowireCapableBeanFactory.java):
java
<T> T createBean(Class<T> beanClass) throws BeansException; // 137 行
Object createBean(Class<?> beanClass, int autowireMode, boolean dependencyCheck); // 194 行
Object initializeBean(Object existingBean, String beanName); // 286 行
Object applyBeanPostProcessorsBeforeInitialization(Object existingBean, String beanName); // 305 行
Object applyBeanPostProcessorsAfterInitialization(Object existingBean, String beanName); // 325 行
void destroyBean(Object existingBean); // 336 行
以及几个装配模式常量:AUTOWIRE_NO = 0(第 71 行)、AUTOWIRE_BY_NAME = 1(第 79 行)。
这个接口是 Spring 与第三方框架集成的桥梁。比如一个 Servlet 容器里 new 出来的 Filter,没法走 Spring 的构造注入,但只要拿到 AutowireCapableBeanFactory,调一次 autowireBean(filter),就能让它身上的 @Autowired 字段被填上。它和"容器内部造 bean"共用同一套 BeanPostProcessor 流水线,区别只是入口------内部走 createBean(String beanName, RootBeanDefinition mbd, ...)(带定义),外部走 createBean(Class)(无定义、纯类)。
3.5 ConfigurableBeanFactory:工厂的"配置面板"
ConfigurableBeanFactory extends HierarchicalBeanFactory, SingletonBeanRegistry。它把"运行中的工厂"可以被调整的那些旋钮集中起来。主要能力:
- 层次 :
setParentBeanFactory(设置父工厂,与HierarchicalBeanFactory.getParentBeanFactory配对)。 - 类加载 :
setBeanClassLoader/getBeanClassLoader,工厂加载 bean class 时用哪个 ClassLoader。 - BeanPostProcessor 注册表 :
addBeanPostProcessor、getBeanPostProcessorCount。 - 作用域注册 :
registerScope(scopeName, Scope),web 环境的request/sessionscope 就是这么塞进来的。 - 类型转换 :
setTypeConverter、getTypeConverter,以及addPropertyEditorRegistrar。 - 占位符解析 :
resolveEmbeddedValue(String),把${...}解析掉。 - 临时类加载器 :
setTempClassLoader(用于类型匹配时的临时类加载,不真正加载到主 ClassLoader)。 - 缓存开关 :
setCacheBeanMetadata、isCacheBeanMetadata。
它还继承了 SingletonBeanRegistry(路径 config/SingletonBeanRegistry.java),这个接口定义了单例注册表的对外面:registerSingleton、getSingleton、containsSingleton、getSingletonNames、destroySingletons。也就是说,"单例表"是一个独立的接口,被 ConfigurableBeanFactory 继承进来------这解释了为什么 DefaultListableBeanFactory 一族从 DefaultSingletonBeanRegistry 一路往上继承时,单例表能力是最底层。
3.6 ConfigurableListableBeanFactory:最后的聚合接口
ConfigurableListableBeanFactory extends ListableBeanFactory, AutowireCapableBeanFactory, ConfigurableBeanFactory。它把前面所有能力聚合成"一个全功能工厂应当长什么样"。它新增的关键方法(config/ConfigurableListableBeanFactory.java):
java
void ignoreDependencyInterface(Class<?> ifc); // 64 行
void registerResolvableDependency(Class<?> dependencyType, @Nullable Object autowiredValue); // 82 行
BeanDefinition getBeanDefinition(String beanName); // 110 行
void clearMetadataCache(); // 136 行
void freezeConfiguration(); // 146 行
boolean isConfigurationFrozen(); // 154 行
void preInstantiateSingletons() throws BeansException; // 178 行
逐个点一下:
ignoreDependencyInterface:告诉容器"凡是实现了这个接口的字段,不要尝试按类型自动装配"。AbstractApplicationContext.prepareBeanFactory用它把一堆*Aware接口(EnvironmentAware、ApplicationContextAware等)排除在自动装配之外------因为这些 Aware 回调是容器自己通过BeanPostProcessor显式塞值的,不应该走@Autowired的候选筛选流程。registerResolvableDependency:往一个"特殊可解析依赖表"里登记一对 (类型, 值)。后面讲到findAutowireCandidates时会看到,BeanFactory、ResourceLoader、ApplicationEventPublisher、ApplicationContext这四个"内建可注入对象"就是靠这个方法注册的。freezeConfiguration/isConfigurationFrozen:冻结配置。冻结之后,bean 定义表被视为不可变,工厂可以放心地缓存按类型查询的结果(allBeanNamesByType等缓存就是靠冻结开关激活的)。preInstantiateSingletons:触发"所有非懒加载单例的预实例化"------这是 Spring 容器启动的最后一步,也是本章要重点剖析的方法。
至此,接口族讲完了。下一节进入真正的实现:DefaultListableBeanFactory 的数据结构。
四、源码剖析(二):DefaultListableBeanFactory 的数据结构
DefaultListableBeanFactory(路径 spring-beans/src/main/java/org/springframework/beans/factory/support/DefaultListableBeanFactory.java,全文 2813 行)是整个 spring-beans 模块的"最终答卷":
java
public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFactory
implements ConfigurableListableBeanFactory, BeanDefinitionRegistry, Serializable {
它的继承链是:
bash
DefaultSingletonBeanRegistry(单例表)
→ FactoryBeanRegistrySupport(FactoryBean 产物缓存)
→ AbstractBeanFactory(doGetBean 主流程 + 合并定义缓存)
→ AbstractAutowireCapableBeanFactory(createBean/populate/initialize)
→ DefaultListableBeanFactory(定义表 + 按类型筛选 + 预实例化)
它实现了三个接口:ConfigurableListableBeanFactory(全功能工厂面)、BeanDefinitionRegistry(定义注册表面,在 support/BeanDefinitionRegistry.java)、Serializable。下面把它自己声明的字段逐个拆开------这些字段就是"工厂内部到底存了什么"的答案。
4.1 定义表:beanDefinitionMap 与 beanDefinitionNames
看第 190-209 行:
java
/** Map of bean definition objects, keyed by bean name. */
private final Map<String, BeanDefinition> beanDefinitionMap = new ConcurrentHashMap<>(256);
/** Map from bean name to merged BeanDefinitionHolder. */
private final Map<String, BeanDefinitionHolder> mergedBeanDefinitionHolders = new ConcurrentHashMap<>(256);
/** Map of bean definition names with a primary marker plus corresponding type. */
private final Map<String, Class<?>> primaryBeanNamesWithType = new ConcurrentHashMap<>(16);
/** Map of singleton and non-singleton bean names, keyed by dependency type. */
private final Map<Class<?>, String[]> allBeanNamesByType = new ConcurrentHashMap<>(64);
/** Map of singleton-only bean names, keyed by dependency type. */
private final Map<Class<?>, String[]> singletonBeanNamesByType = new ConcurrentHashMap<>(64);
/** List of bean definition names, in registration order. */
private volatile List<String> beanDefinitionNames = new ArrayList<>(256);
/** List of names of manually registered singletons, in registration order. */
private volatile Set<String> manualSingletonNames = new LinkedHashSet<>(16);
逐字段讲:
beanDefinitionMap:核心中的核心。ConcurrentHashMap,key 是 bean 名,value 是BeanDefinition配方。它是"图纸仓库"。之所以用ConcurrentHashMap而不是HashMap,是因为注册定义和查找定义可能发生在不同线程(启动时注册在主线程,业务线程可能并发getBean)。beanDefinitionNames:一个volatile List<String>,按注册顺序 保存所有 bean 名。为什么除了 Map 还要一个 List?因为ConcurrentHashMap不保证遍历顺序,而ListableBeanFactory承诺"返回的名字按定义顺序",预实例化时也必须按注册顺序来(顺序对循环依赖排查、对@Order排序都有影响)。注意它是volatile且是引用 volatile------一旦启动后还要新增定义,整个 List 会被替换成一个新的ArrayList(后面registerBeanDefinition会看到这个"写时复制"动作),而不是原地 add。manualSingletonNames:volatile Set<String>(LinkedHashSet),记录那些不是通过 bean 定义、而是通过registerSingleton手动塞进来的单例名。为什么要单独记一份?因为preInstantiateSingletons遍历的是beanDefinitionNames,手动注册的单例没有定义、不需要"预实例化",但getBeanNamesForType这类批量查询又必须把它们包含进去------单独记账,方便区分两条来源。allBeanNamesByType/singletonBeanNamesByType:两个"按类型索引"的缓存。key 是依赖类型,value 是该类型对应的所有 bean 名数组。这是getBeanNamesForType的加速缓存:一旦配置冻结,按类型查就不必每次都遍历全表做isTypeMatch,直接查这个索引即可。primaryBeanNamesWithType:记录哪些 bean 标了@Primary。注册时(第 1329-1331 行)如果beanDefinition.isPrimary(),就往这里放一条。这是 7.0 为加速determinePrimaryCandidate做的小优化。mergedBeanDefinitionHolders:缓存 bean 名到BeanDefinitionHolder的映射,用于注解配置场景下的合并定义持有者。
4.2 依赖装配相关字段
再看第 174-188 行:
java
/** Whether to allow re-registration of a different definition with the same name. */
private @Nullable Boolean allowBeanDefinitionOverriding;
/** Whether to allow eager class loading even for lazy-init beans. */
private boolean allowEagerClassLoading = true;
private @Nullable Executor bootstrapExecutor;
/** Optional OrderComparator for dependency Lists and arrays. */
private @Nullable Comparator<Object> dependencyComparator;
/** Resolver to use for checking if a bean definition is an autowire candidate. */
private AutowireCandidateResolver autowireCandidateResolver = SimpleAutowireCandidateResolver.INSTANCE;
/** Map from dependency type to corresponding autowired value. */
private final Map<Class<?>, Object> resolvableDependencies = new ConcurrentHashMap<>(16);
allowBeanDefinitionOverriding:是否允许同名覆盖。注意它是@Nullable Boolean,默认 null;判断时用isAllowBeanDefinitionOverriding(),实现是!Boolean.FALSE.equals(this.allowBeanDefinitionOverriding)------也就是默认 true (允许覆盖),只有显式设为false才禁止。Spring Boot 2.1+ 把它改成了默认 false,那是 Boot 做的调整,不是 spring-beans 本身。bootstrapExecutor:7.0 的新字段!一个Executor。配了它之后,标了backgroundInit的 bean 可以被丢到后台线程去实例化。这是本章后面要重点讲的并行预实例化的开关。dependencyComparator:给List/数组注入用的排序比较器。通常被ApplicationContext设为AnnotationAwareOrderComparator,于是@Order/Ordered/@Priority注解才能生效。autowireCandidateResolver:策略接口,判断"某个 bean 定义能不能当某个注入点的候选"。默认是SimpleAutowireCandidateResolver.INSTANCE(只看isAutowireCandidate()标志位);到了 spring-context 环境会被换成ContextAnnotationAutowireCandidateResolver,加上@Qualifier、@Lazy代理等扩展。resolvableDependencies:内建可注入对象表。前面提过的BeanFactory、ResourceLoader等就登记在这里。它的查找逻辑在findAutowireCandidates里------候选筛选时,先遍历这张表,凡是类型能赋值给依赖类型的,直接作为候选加进去。
4.3 冻结与并发控制字段
第 211-221 行:
java
/** Cached array of bean definition names in case of frozen configuration. */
private volatile String @Nullable [] frozenBeanDefinitionNames;
/** Whether bean definition metadata may be cached for all beans. */
private volatile boolean configurationFrozen;
/** Name prefix of main thread: only set during pre-instantiation phase. */
private volatile @Nullable String mainThreadPrefix;
private final NamedThreadLocal<PreInstantiation> preInstantiationThread =
new NamedThreadLocal<>("Pre-instantiation thread marker");
configurationFrozen:配置冻结标志。冻结之后,beanDefinitionNames被快照成frozenBeanDefinitionNames数组(不可变),allBeanNamesByType等按类型缓存才被认为是安全的。mainThreadPrefix+preInstantiationThread:这一对是 7.0 并行预实例化的"线程身份识别"机制。预实例化开始时记录主线程名前缀(比如main),如果之后冒出一个同名前缀的线程,就认为它是"外部启动线程",要走严格锁;普通后台线程则用宽松锁。PreInstantiation是一个枚举(MAIN/BACKGROUND),用NamedThreadLocal绑定到当前线程。
另外还有一个类级常量(第 147 行):
java
public static final String STRICT_LOCKING_PROPERTY_NAME = "spring.locking.strict";
以及对应的字段(第 168 行):
java
private final @Nullable Boolean strictLocking = SpringProperties.checkFlag(STRICT_LOCKING_PROPERTY_NAME);
这个系统属性是 6.2.6 引入的:默认情况下,工厂会根据线程名推断要不要严格加锁;显式设 spring.locking.strict=true/false 可以强制开关。这是 7.0 并发模型的一个调节阀。
把这几十个字段归类一下,DefaultListableBeanFactory 内部其实维护了四组表:
- 定义表 :
beanDefinitionMap+beanDefinitionNames(配方仓库); - 单例表 :继承自
DefaultSingletonBeanRegistry的singletonObjects/earlySingletonObjects/singletonFactories(成品仓库,下一章细讲); - 按类型索引缓存 :
allBeanNamesByType/singletonBeanNamesByType/primaryBeanNamesWithType; - 特殊依赖表 :
resolvableDependencies。
理解了这四组表的分工,registerBeanDefinition 和 getBean 就只是"在这几张表上做增删查"而已。下一节我们就从写操作 registerBeanDefinition 开始。
五、源码剖析(三):registerBeanDefinition 全流程
注册一个 bean 定义,是容器启动期最频繁的写操作。DefaultListableBeanFactory.registerBeanDefinition(第 1251-1332 行)的完整源码如下,我们逐段拆。
5.1 方法签名与定义校验
java
@Override
public void registerBeanDefinition(String beanName, BeanDefinition beanDefinition)
throws BeanDefinitionStoreException {
Assert.hasText(beanName, "Bean name must not be empty");
Assert.notNull(beanDefinition, "BeanDefinition must not be null");
if (beanDefinition instanceof AbstractBeanDefinition abd) {
try {
abd.validate();
}
catch (BeanDefinitionValidationException ex) {
throw new BeanDefinitionStoreException(beanDefinition.getResourceDescription(), beanName,
"Validation of bean definition failed", ex);
}
}
// ......
}
第一段做三件事:断言 bean 名非空、断言定义非空、如果这个定义是 AbstractBeanDefinition 家族的,先调一次 validate()。validate() 在 AbstractBeanDefinition.java 第 1235-1244 行:
java
public void validate() throws BeanDefinitionValidationException {
if (hasMethodOverrides() && getFactoryMethodName() != null) {
throw new BeanDefinitionValidationException(
"Cannot combine factory method with container-generated method overrides: " +
"the factory method must create the concrete bean instance.");
}
if (hasBeanClass()) {
prepareMethodOverrides();
}
}
校验规则很朴素:方法覆写(lookup-method/replaced-method,即 CGLIB 增强的那套)不能与工厂方法并存------因为工厂方法已经决定了实例怎么造,容器没法再往里插方法覆写;如果 beanClass 已经解析成了 Class,就把方法覆写逐个 prepareMethodOverride,核对类里到底有没有这个方法名(见第 1265-1274 行:方法数为 0 报错;方法数为 1 则标记"非重载",避免运行时做参数类型检查的开销)。
一个版本事实 :任务书里提到"last-modified 检查",但在 7.0.9 的 registerBeanDefinition 里并没有 任何基于时间戳或 lastModified 的比较逻辑。同名覆盖的判断只看"是否允许覆盖"这个布尔开关,不看两个定义的新旧。这一点后面"常见问题与坑"还会再强调。
5.2 同名覆盖:existingDefinition != null 分支
java
BeanDefinition existingDefinition = this.beanDefinitionMap.get(beanName);
if (existingDefinition != null) {
if (!isBeanDefinitionOverridable(beanName)) {
throw new BeanDefinitionOverrideException(beanName, beanDefinition, existingDefinition);
}
else {
logBeanDefinitionOverriding(beanName, beanDefinition, existingDefinition);
}
this.beanDefinitionMap.put(beanName, beanDefinition);
}
如果 beanDefinitionMap 里已经有同名定义:
- 先问
isBeanDefinitionOverridable(beanName)。这个方法在第 1447-1449 行,实现就是return isAllowBeanDefinitionOverriding();------即看那个allowBeanDefinitionOverriding布尔开关。如果不允许覆盖,直接抛BeanDefinitionOverrideException(这个异常类在support/BeanDefinitionOverrideException.java,4.1 引入,专门承载"旧定义 + 新定义"两份信息,方便排障)。 - 允许覆盖,则调
logBeanDefinitionOverriding打日志(第 1334-1370 行),日志分三档:旧定义角色 < 新定义角色(用户 bean 被框架 bean 替换)、两个定义equals不同、两个定义equals相同,分别用 info/debug/trace 输出。注意它不报错、不返回 ,直接beanDefinitionMap.put覆盖。 - 覆盖完成后,
beanDefinitionNames不需要动(名字本来就在表里),但要在后面触发resetBeanDefinition。
这里有个设计要点:覆盖是"后注册的赢" 。谁后 registerBeanDefinition,谁就留在 Map 里。这也是为什么 Spring Boot 允许你用一个自定义 bean 去覆盖 Spring Boot 自动配置里注册的那个同名 bean------只要你注册得更晚。
5.3 新名字:别名冲突与"启动期 vs 启动后"
java
else {
if (isAlias(beanName)) {
String aliasedName = canonicalName(beanName);
if (!isBeanDefinitionOverridable(aliasedName)) {
if (containsBeanDefinition(aliasedName)) { // alias for existing bean definition
throw new BeanDefinitionOverrideException(
beanName, beanDefinition, getBeanDefinition(aliasedName));
}
else { // alias pointing to non-existing bean definition
throw new BeanDefinitionStoreException(beanDefinition.getResourceDescription(), beanName,
"Cannot register bean definition for bean '" + beanName +
"' since there is already an alias for bean '" + aliasedName + "' bound.");
}
}
else {
if (logger.isInfoEnabled()) {
logger.info("Removing alias '" + beanName + "' for bean '" + aliasedName +
"' due to registration of bean definition for bean '" + beanName + "': [" +
beanDefinition + "]");
}
removeAlias(beanName);
}
}
// ......
}
新名字分支首先要处理"撞别名":如果这个名字当前是别人的别名(isAlias(beanName) 为 true),那注册新定义就等于"抢占"这个名字。如果不允许覆盖,要分两种情况报错------别名指向一个已存在的定义(抛 BeanDefinitionOverrideException),别名指向一个还不存在的定义(抛 BeanDefinitionStoreException)。允许覆盖则 removeAlias(beanName),把别名删掉,日志说明原因。这一段保证了"名字资源"的一致性:一个名字在同一时刻要么是定义名、要么是别名、要么是手动单例,不能两占。
接下来是并发安全的关键判断:
java
if (hasBeanCreationStarted()) {
// Cannot modify startup-time collection elements anymore (for stable iteration)
synchronized (this.beanDefinitionMap) {
this.beanDefinitionMap.put(beanName, beanDefinition);
List<String> updatedDefinitions = new ArrayList<>(this.beanDefinitionNames.size() + 1);
updatedDefinitions.addAll(this.beanDefinitionNames);
updatedDefinitions.add(beanName);
this.beanDefinitionNames = updatedDefinitions;
removeManualSingletonName(beanName);
}
}
else {
// Still in startup registration phase
this.beanDefinitionMap.put(beanName, beanDefinition);
this.beanDefinitionNames.add(beanName);
removeManualSingletonName(beanName);
}
this.frozenBeanDefinitionNames = null;
这是 7.0.9 注册路径里最能体现并发设计的一段:
hasBeanCreationStarted()判断"是否已经开始造 bean 了"。启动早期(所有定义还没注册完),单线程往里塞定义,直接put+add,无锁。- 一旦已经开始造 bean (比如
BeanFactoryPostProcessor在处理过程中又注册了新定义,或业务代码运行时热注册),就不能再原地修改beanDefinitionNames这个 ArrayList 了------因为别的线程可能正在遍历它做预实例化,原地add会触发ConcurrentModificationException。于是走synchronized (this.beanDefinitionMap)块,在锁内把整个 List 复制一份新的 ,追加新名字,然后替换引用 (this.beanDefinitionNames = updatedDefinitions)。这就是为什么beanDefinitionNames要声明成volatile:引用替换对其他线程立即可见。 - 不管哪个分支,都
removeManualSingletonName(beanName)------一个新定义注册进来,如果之前这个名字是手动单例,就把它从手动单例名单里划掉。 frozenBeanDefinitionNames = null:配置一旦变动,冻结时拍的快照就作废了。
5.4 重置旧状态:resetBeanDefinition
方法末尾(第 1321-1331 行):
java
if (existingDefinition != null || containsSingleton(beanName)) {
resetBeanDefinition(beanName);
}
else if (isConfigurationFrozen()) {
clearByTypeCache();
}
// Cache a primary marker for the given bean.
if (beanDefinition.isPrimary()) {
this.primaryBeanNamesWithType.put(beanName, Void.class);
}
如果这次注册是"覆盖了一个已有定义"或"这个名字已经有单例实例了",就必须调 resetBeanDefinition(beanName) 把之前缓存的派生状态清掉。resetBeanDefinition(第 1412-1439 行)做四件事:
clearMergedBeanDefinition(beanName)------把合并定义缓存里这个 bean 标记为 stale;destroySingleton(beanName)------把已经造好的同名单例销毁掉(不然新定义配出来的还是旧实例);primaryBeanNamesWithType.remove(beanName)------清掉 primary 标记;- 遍历所有
MergedBeanDefinitionPostProcessor,通知它们"这个定义被重置了";然后递归 地找所有以这个 bean 为 parent 的子定义,对它们也调resetBeanDefinition。
最后,如果新定义标了 primary,往 primaryBeanNamesWithType 放一条缓存。
把 registerBeanDefinition 串起来看,它是一个"读-校验-冲突检测-写表-清缓存-通知派生者"的完整事务。它的并发模型可以总结为一句话:启动期无锁快路径,启动后锁内写时复制。
六、源码剖析(四):preInstantiateSingletons 与 7.0 的并行预实例化
这是 Spring 容器启动的"开火"瞬间。DefaultListableBeanFactory.preInstantiateSingletons()(第 1101-1151 行)在 7.0.9 里的完整源码:
java
@Override
public void preInstantiateSingletons() throws BeansException {
if (logger.isTraceEnabled()) {
logger.trace("Pre-instantiating singletons in " + this);
}
// Iterate over a copy to allow for init methods which in turn register new bean definitions.
List<String> beanNames = new ArrayList<>(this.beanDefinitionNames);
// Trigger initialization of all non-lazy singleton beans...
this.preInstantiationThread.set(PreInstantiation.MAIN);
if (this.mainThreadPrefix == null) {
this.mainThreadPrefix = getThreadNamePrefix();
}
try {
List<CompletableFuture<?>> futures = new ArrayList<>();
for (String beanName : beanNames) {
RootBeanDefinition mbd = getMergedLocalBeanDefinition(beanName);
if (!mbd.isAbstract() && mbd.isSingleton()) {
CompletableFuture<?> future = preInstantiateSingleton(beanName, mbd);
if (future != null) {
futures.add(future);
}
}
}
if (!futures.isEmpty()) {
try {
CompletableFuture.allOf(futures.toArray(new CompletableFuture<?>[0])).join();
}
catch (CompletionException ex) {
ReflectionUtils.rethrowRuntimeException(ex.getCause());
}
}
}
finally {
this.mainThreadPrefix = null;
this.preInstantiationThread.remove();
}
// Trigger post-initialization callback for all applicable beans...
for (String beanName : beanNames) {
Object singletonInstance = getSingleton(beanName, false);
if (singletonInstance instanceof SmartInitializingSingleton smartSingleton) {
StartupStep smartInitialize = getApplicationStartup().start("spring.beans.smart-initialize")
.tag("beanName", beanName);
smartSingleton.afterSingletonsInstantiated();
smartInitialize.end();
}
}
}
6.1 逐段解读
- 复制一份名单再遍历 。
new ArrayList<>(this.beanDefinitionNames)------注释说明原因:init 方法里可能反过来注册新 bean 定义,遍历副本允许这种"边遍历边注册"的行为,不会ConcurrentModificationException。 - 绑定线程身份 。
preInstantiationThread.set(PreInstantiation.MAIN)把当前线程标记为"主线程预实例化者";如果还没记录主线程名前缀,用getThreadNamePrefix()(第 1240-1244 行)取线程名最后一个-之前的部分------比如main线程名就是main,Test worker - 1就取Test worker。这个前缀后面用来识别"冒名顶替的外部线程"。 - 逐个判断是否需要预实例化 。对每个名字拿合并定义
getMergedLocalBeanDefinition(beanName),只处理"非抽象 + 单例"的 bean。注意getMergedLocalBeanDefinition返回的是合并后的RootBeanDefinition------哪怕注册时是个ChildBeanDefinition或AnnotatedGenericBeanDefinition,到这里已经被合并成RootBeanDefinition了。 - 调用 preInstantiateSingleton 。这个方法在 7.0.9 里返回一个
CompletableFuture<?>------这是和旧版最大的不同。旧版(6.x)这个方法是 void,主循环同步调getBean(beanName)。7.0 允许它返回一个 future,代表"这个 bean 在后台线程造着"。 - 收集 future 并 join 。所有后台任务提交完后,
CompletableFuture.allOf(...).join()阻塞等它们全部结束。任何一个失败,拆CompletionException把真实异常抛出来。 - SmartInitializingSingleton 回调 。所有单例造完后,再遍历一次名单,对每个 singleton 实例判断是不是
SmartInitializingSingleton,是就调afterSingletonsInstantiated()。这个回调的时机很关键------它发生在所有常规单例都就绪之后 ,所以它内部getBean不会触发循环依赖。
6.2 preInstantiateSingleton:前台/后台分支
第 1153-1199 行:
java
private @Nullable CompletableFuture<?> preInstantiateSingleton(String beanName, RootBeanDefinition mbd) {
if (mbd.isBackgroundInit()) {
Executor executor = getBootstrapExecutor();
if (executor != null) {
// Force initialization of depends-on beans in mainline thread.
String[] dependsOn = mbd.getDependsOn();
if (dependsOn != null) {
for (String dep : dependsOn) {
getBean(dep);
}
}
// Force initialization of factory reference in mainline thread.
String factoryBeanName = mbd.getFactoryBeanName();
if (factoryBeanName != null) {
getBean(factoryBeanName);
}
// Instantiate current bean in background thread.
CompletableFuture<?> future = CompletableFuture.runAsync(
() -> instantiateSingletonInBackgroundThread(beanName), executor);
addSingletonFactory(beanName, () -> {
try {
future.join();
}
catch (CompletionException ex) {
ReflectionUtils.rethrowRuntimeException(ex.getCause());
}
return future; // not to be exposed, just to lead to ClassCastException in case of mismatch
});
return (!mbd.isLazyInit() ? future : null);
}
else if (logger.isInfoEnabled()) {
logger.info("Bean '" + beanName + "' marked for background initialization " +
"without bootstrap executor configured - falling back to mainline initialization");
}
}
if (!mbd.isLazyInit()) {
try {
instantiateSingleton(beanName);
}
catch (BeanCurrentlyInCreationException ex) {
logger.info("Bean '" + beanName + "' marked for pre-instantiation (not lazy-init) " +
"but currently initialized by other thread - skipping it in mainline thread");
}
}
return null;
}
这一段是 7.0 并发模型的心脏,逐段讲:
mbd.isBackgroundInit():这个 bean 是否被标记为"后台初始化"(7.0 在RootBeanDefinition上加了backgroundInit标志,可以通过BeanDefinitionBuilder或 XML 属性开启)。- 配了
bootstrapExecutor才真正并行。如果没配 executor,打一条 info 日志"退化为主线程初始化",落到下面的同步分支。这是一种优雅降级------标了后台初始化但没配执行器,行为和旧版一致。 - 后台实例化前的"主先"保证 。要把 bean 丢到后台线程去造,它的
dependsOn依赖和factoryBeanName工厂引用必须先在主线程造好 。原因:后台线程造这个 bean 时会反过来getBean(dep),如果 dep 还没造,就会跨线程触发创建;而单例创建的早期暴露(三级缓存)设计上只允许在原创建线程内推断(前面提过的tryLock快速失败)。所以容器选择在主线程把这些"前置依赖"先造好,后台线程只造这个 bean 本身。 addSingletonFactory的妙用 。注意那个 lambda:它往三级缓存里塞了一个ObjectFactory,这个 factory 的getObject()会future.join()等后台线程造完,然后返回 future 本身(注释说"不是用来暴露的,只是为了类型不匹配时抛 ClassCastException")。这意味着:在后台线程还没造完时,别的线程如果来getBean(beanName),会被这个 ObjectFactory 阻塞住直到后台造完------既不允许提前拿到半成品,也不会死锁。BeanCurrentlyInCreationException的宽容处理 。主线程走到一个 bean 时,如果它已经被别的后台线程开始造了(instantiateSingleton内部会抛这个异常),主线程不报错,只打一条 info "skipping it in mainline thread",直接跳过。这就是"宽松锁"哲学:多个线程同时准备造同一个 bean,谁先抢到算谁的,后来者让开。
6.3 instantiateSingleton 与 SmartFactoryBean
第 1217-1227 行:
java
private void instantiateSingleton(String beanName) {
if (isFactoryBean(beanName)) {
Object bean = getBean(FACTORY_BEAN_PREFIX + beanName);
if (bean instanceof SmartFactoryBean<?> smartFactoryBean && smartFactoryBean.isEagerInit()) {
getBean(beanName);
}
}
else {
getBean(beanName);
}
}
对普通 bean 直接 getBean。对 FactoryBean,先 getBean("&beanName") 把 FactoryBean 本身造出来;然后判断它是不是 SmartFactoryBean 且 isEagerInit()------如果是,再 getBean(beanName) 把它生产的对象也提前造出来。SmartFactoryBean(路径 factory/SmartFactoryBean.java)在普通 FactoryBean 之外加了两个 default 方法(第 105、124 行):isPrototype() 和 isEagerInit()。普通 FactoryBean 的产物是懒加载的(第一次 getBean("xxx") 才调 getObject()),但 SmartFactoryBean.isEagerInit() 返回 true 时,容器在预实例化阶段就把产物也造好------比如一个数据库连接池的 FactoryBean,希望启动时就建好池。
6.4 这个设计为什么重要
旧版 Spring 启动是单线程的:所有单例在主线程按顺序造,bean 越多启动越慢。7.0 的方案不是简单地"所有 bean 丢线程池并行造"------那会破坏循环依赖的早期暴露语义。它的策略是:
- 默认行为不变 :不配
bootstrapExecutor,一切照旧单线程; - 显式 opt-in :只有标了
backgroundInit的 bean 才进后台; - 前置依赖主线程先造:保证后台 bean 启动时它的依赖已经在主线程就绪;
- 三级缓存挂 future:让并发访问者安全阻塞等待。
这种"保守并行"是 Spring 一贯的工程风格------不牺牲正确性去换速度。STRICT_LOCKING_PROPERTY_NAME 那个系统属性,则给了一个"回退到 6.1 严格锁风格"的逃生阀。
七、源码剖析(五):AbstractBeanFactory.doGetBean 全流程
doGetBean(路径 spring-beans/src/main/java/org/springframework/beans/factory/support/AbstractBeanFactory.java 第 235-400 行)是 getBean 的真正实现。所有 getBean(...) 重载都汇聚到这里(第 194-222 行:四个 public getBean 方法各自一行 return doGetBean(...))。下面贴出完整方法体并逐段讲。
7.1 名字解析与单例快路径
java
@SuppressWarnings("unchecked")
protected <T> T doGetBean(
String name, @Nullable Class<T> requiredType, @Nullable Object @Nullable [] args, boolean typeCheckOnly)
throws BeansException {
String beanName = transformedBeanName(name);
Object beanInstance;
// Eagerly check singleton cache for manually registered singletons.
Object sharedInstance = getSingleton(beanName);
if (sharedInstance != null && args == null) {
if (logger.isTraceEnabled()) {
if (isSingletonCurrentlyInCreation(beanName)) {
logger.trace("Returning eagerly cached instance of singleton bean '" + beanName +
"' that is not fully initialized yet - a consequence of a circular reference");
}
else {
logger.trace("Returning cached instance of singleton bean '" + beanName + "'");
}
}
beanInstance = getObjectForBeanInstance(sharedInstance, requiredType, name, beanName, null);
}
第一步 transformedBeanName(name)(第 1271-1272 行):
java
protected String transformedBeanName(String name) {
return canonicalName(BeanFactoryUtils.transformedBeanName(name));
}
它做两件事:先用 BeanFactoryUtils.transformedBeanName 剥掉开头的 & 前缀(7.0.9 里用 FACTORY_BEAN_PREFIX_CHAR 这个 char 常量做前缀判断,避免字符串拼接),再用 canonicalName 把别名链一路追到最终规范名。也就是说,传进来的 name 可能是 "&myJndiObject"、"aliasOfUserService" 或 "userService",经过这一步都变成干净的 beanName = "userService"。
紧接着 getSingleton(beanName) 查单例表。这个方法(继承自 DefaultSingletonBeanRegistry)走的是三级缓存查找:一级 singletonObjects、二级 earlySingletonObjects、三级 singletonFactories。如果命中(说明这个 singleton 已经造好了,或者正在被别人造、有早期引用),且调用方没有传构造参数 args,就直接进入 getObjectForBeanInstance 后处理------这是热路径,绝大多数 getBean 都停在这里,不进下面的创建逻辑。
注意 trace 日志里的分支:isSingletonCurrentlyInCreation(beanName) 为 true 意味着拿到的是"正在创建中的早期引用"------这是循环依赖的信号。
7.2 原型循环检测与父工厂回退
java
else {
// Fail if we're already creating this bean instance:
// We're assumably within a circular reference.
if (isPrototypeCurrentlyInCreation(beanName)) {
throw new BeanCurrentlyInCreationException(beanName);
}
// Check if bean definition exists in this factory.
BeanFactory parentBeanFactory = getParentBeanFactory();
if (parentBeanFactory != null && !containsBeanDefinition(beanName)) {
// Not found -> check parent.
String nameToLookup = originalBeanName(name);
if (parentBeanFactory instanceof AbstractBeanFactory abf) {
return abf.doGetBean(nameToLookup, requiredType, args, typeCheckOnly);
}
else if (args != null) {
// Delegation to parent with explicit args.
return (T) parentBeanFactory.getBean(nameToLookup, args);
}
else if (requiredType != null) {
// No args -> delegate to standard getBean method.
return parentBeanFactory.getBean(nameToLookup, requiredType);
}
else {
return (T) parentBeanFactory.getBean(nameToLookup);
}
}
- 原型循环依赖直接报错 。singleton 的循环依赖可以靠三级缓存绕过去,但 prototype 不行------prototype 不缓存,容器没法提前暴露一个"正在造的 prototype"引用。所以一旦发现"当前线程正在造这个 prototype",立刻抛
BeanCurrentlyInCreationException。 - 父工厂回退 。如果本地工厂没有这个 bean 的定义(
!containsBeanDefinition(beanName)),且有父工厂,就把请求转发给父工厂。originalBeanName(name)把原始的&前缀和别名信息恢复出来(因为父工厂里的别名表是独立的)。如果父工厂也是AbstractBeanFactory家族,直接递归调它的doGetBean;否则按args/requiredType的有无,选对应的getBean重载转发。这就是HierarchicalBeanFactory层次结构在getBean路径上的兑现------只在本地定义表找不到时才向上问。
7.3 合并定义与 depends-on
java
if (!typeCheckOnly) {
markBeanAsCreated(beanName);
}
StartupStep beanCreation = this.applicationStartup.start("spring.beans.instantiate")
.tag("beanName", name);
try {
if (requiredType != null) {
beanCreation.tag("beanType", requiredType::toString);
}
RootBeanDefinition mbd = getMergedLocalBeanDefinition(beanName);
checkMergedBeanDefinition(mbd, beanName, args);
// Guarantee initialization of beans that the current bean depends on.
String[] dependsOn = mbd.getDependsOn();
if (dependsOn != null) {
for (String dep : dependsOn) {
if (isDependent(beanName, dep)) {
throw new BeanCreationException(mbd.getResourceDescription(), beanName,
"Circular depends-on relationship between '" + beanName + "' and '" + dep + "'");
}
registerDependentBean(dep, beanName);
try {
getBean(dep);
}
catch (NoSuchBeanDefinitionException ex) {
throw new BeanCreationException(mbd.getResourceDescription(), beanName,
"'" + beanName + "' depends on missing bean '" + dep + "'", ex);
}
// ...... BeanCreationException 包装略
}
}
markBeanAsCreated(beanName):标记"已经开始创建这个 bean"。这一步把alreadyCreated集合里加一条,hasBeanCreationStarted()从此返回 true------前面registerBeanDefinition里那个并发分支就靠它判断。getMergedLocalBeanDefinition(beanName):拿合并后的RootBeanDefinition。注册时的GenericBeanDefinition/ChildBeanDefinition到这里全部拍平成RootBeanDefinition。checkMergedBeanDefinition(第 1512-1516 行):如果合并定义标了abstract,抛BeanIsAbstractException------抽象定义只是用来当父模板的,不能直接 getBean。depends-on处理 。如果配了depends-on,先检查循环依赖(isDependent:如果 dep 已经反过来依赖 beanName,那就是循环 depends-on,直接报错);再registerDependentBean(dep, beanName)登记"beanName 依赖 dep"这个关系(这张关系表用于销毁顺序:dep 要在 beanName 之后销毁);最后getBean(dep)强制把依赖先造出来。注意 depends-on 是硬依赖 ------它和@Autowired的注入不同,depends-on 只保证"先造",不负责把 dep 引用塞进 beanName 的字段。
7.4 三大作用域分支
java
// Create bean instance.
if (mbd.isSingleton()) {
sharedInstance = getSingleton(beanName, () -> {
try {
return createBean(beanName, mbd, args);
}
catch (BeansException ex) {
// Explicitly remove instance from singleton cache: It might have been put there
// eagerly by the creation process, to allow for circular reference resolution.
destroySingleton(beanName);
throw ex;
}
});
beanInstance = getObjectForBeanInstance(sharedInstance, requiredType, name, beanName, mbd);
}
else if (mbd.isPrototype()) {
// It's a prototype -> create a new instance.
Object prototypeInstance = null;
try {
beforePrototypeCreation(beanName);
prototypeInstance = createBean(beanName, mbd, args);
}
finally {
afterPrototypeCreation(beanName);
}
beanInstance = getObjectForBeanInstance(prototypeInstance, requiredType, name, beanName, mbd);
}
else {
String scopeName = mbd.getScope();
if (!StringUtils.hasLength(scopeName)) {
throw new IllegalStateException("No scope name defined for bean '" + beanName + "'");
}
Scope scope = this.scopes.get(scopeName);
if (scope == null) {
throw new IllegalStateException("No Scope registered for scope name '" + scopeName + "'");
}
try {
Object scopedInstance = scope.get(beanName, () -> {
beforePrototypeCreation(beanName);
try {
return createBean(beanName, mbd, args);
}
finally {
afterPrototypeCreation(beanName);
}
});
beanInstance = getObjectForBeanInstance(scopedInstance, requiredType, name, beanName, mbd);
}
catch (IllegalStateException ex) {
throw new ScopeNotActiveException(beanName, scopeName, ex);
}
}
三个分支结构高度一致:拿一个对象(singleton 从单例表取/造、prototype 直接造、自定义 scope 从 Scope 实现里取/造),然后都过一道 getObjectForBeanInstance。
- singleton 分支 :
getSingleton(beanName, ObjectFactory)是带创建逻辑的重载------它先查单例表(查不到才执行工厂 lambda),把createBean(beanName, mbd, args)包在 lambda 里。创建失败时destroySingleton(beanName)清理可能被早期暴露出去的半成品。 - prototype 分支 :
beforePrototypeCreation/afterPrototypeCreation在当前线程的prototypesCurrentlyInCreationThreadLocal 里登记/摘除,配合前面那个"原型循环检测"。 - 自定义 scope 分支 :
this.scopes是AbstractBeanFactory第 158 行声明的Map<String, Scope>,web 环境的request/session就注册在这里。scope.get(beanName, objectFactory)由 Scope 实现决定"是复用还是新建",比如 session scope 就是从HttpSession里取属性。
7.5 收尾:类型转换与元数据清理
java
}
catch (BeansException ex) {
beanCreation.tag("exception", ex.getClass().toString());
beanCreation.tag("message", String.valueOf(ex.getMessage()));
cleanupAfterBeanCreationFailure(beanName);
throw ex;
}
finally {
beanCreation.end();
if (!isCacheBeanMetadata()) {
clearMergedBeanDefinition(beanName);
}
}
}
return adaptBeanInstance(name, beanInstance, requiredType);
}
- 失败时
cleanupAfterBeanCreationFailure把这个 bean 从"正在创建"等集合里清掉,避免污染后续请求。 finally里:如果工厂不配缓存 bean 元数据(isCacheBeanMetadata()为 false,这种工厂用完即弃),就clearMergedBeanDefinition(beanName)把合并定义标记为 stale------下次再 getBean 时重新合并。- 最后
adaptBeanInstance(第 402-419 行):如果调用方传了requiredType而实际对象不是该类型,尝试用TypeConverter做一次转换(比如把String转成Integer),转不了抛BeanNotOfRequiredTypeException。
至此,doGetBean 的全貌就清楚了:名字解析 → 单例快路径 → 原型循环检测 → 父工厂回退 → 合并定义 → depends-on → 按 scope 创建 → FactoryBean 后处理 → 类型适配 。其中 createBean 本身的细节(实例化、属性填充、初始化)属于生命周期范畴,本章不展开。
八、源码剖析(六):FactoryBean 机制与 getObjectForBeanInstance
8.1 FactoryBean 接口本身
FactoryBean<T>(路径 spring-beans/src/main/java/org/springframework/beans/factory/FactoryBean.java)只有三个方法:
java
@Nullable T getObject() throws Exception;
@Nullable Class<?> getObjectType();
default boolean isSingleton() {
return true;
}
getObject():生产 bean 实例。getObjectType():提前告诉容器这个工厂会生产什么类型。注释(第 97-116 行)强调:这个方法可能在 FactoryBean 完全初始化之前就被调用,不能依赖初始化期间才建立的状态;如果返回 null,自动装配会直接忽略这个 FactoryBean------所以正确实现它很重要。isSingleton():默认 true,意味着getObject()每次返回同一个引用,工厂会把产物缓存起来。
SmartFactoryBean<T> extends FactoryBean<T>(同目录,第 52 行)加了两个 default 方法:isPrototype()(第 105 行)和 isEagerInit()(第 124 行)。前面预实例化那节已经见过 isEagerInit 的作用。
8.2 getObjectForBeanInstance:FactoryBean 后处理
doGetBean 无论哪个分支拿到对象后,都要过一道 getObjectForBeanInstance(第 1841-1882 行)。完整源码:
java
protected Object getObjectForBeanInstance(Object beanInstance, @Nullable Class<?> requiredType,
String name, String beanName, @Nullable RootBeanDefinition mbd) {
// Don't let calling code try to dereference the factory if the bean isn't a factory.
if (BeanFactoryUtils.isFactoryDereference(name)) {
if (beanInstance instanceof NullBean) {
return beanInstance;
}
if (!(beanInstance instanceof FactoryBean)) {
throw new BeanIsNotAFactoryException(beanName, beanInstance.getClass());
}
if (mbd != null) {
mbd.isFactoryBean = true;
}
return beanInstance;
}
// Now we have the bean instance, which may be a normal bean or a FactoryBean.
// If it's a FactoryBean, we use it to create a bean instance, unless the
// caller actually wants a reference to the factory.
if (!(beanInstance instanceof FactoryBean<?> factoryBean)) {
return beanInstance;
}
Object object = null;
if (mbd != null) {
mbd.isFactoryBean = true;
}
else {
object = getCachedObjectForFactoryBean(beanName);
}
if (object == null) {
// Return bean instance from factory.
// Caches object obtained from FactoryBean if it is a singleton.
if (mbd == null && containsBeanDefinition(beanName)) {
mbd = getMergedLocalBeanDefinition(beanName);
}
boolean synthetic = (mbd != null && mbd.isSynthetic());
object = getObjectFromFactoryBean(factoryBean, requiredType, beanName, !synthetic);
}
return object;
}
逐段讲:
&前缀分支 。BeanFactoryUtils.isFactoryDereference(name)判断原始 name(注意是name不是beanName------beanName已经被transformedBeanName剥过&了)是不是带&。如果带&,调用方明确要 FactoryBean 本身:- 如果实际对象是
NullBean,原样返回; - 如果实际对象不是
FactoryBean,抛BeanIsNotAFactoryException------你要&xxx说明你以为 xxx 是工厂,结果它不是,这是用法错误; - 否则标记
mbd.isFactoryBean = true,直接返回 FactoryBean 本身 ,不调getObject()。
- 如果实际对象是
- 普通 bean 直通 。如果拿到的对象连
FactoryBean都不是,直接返回------普通 bean 的后处理到此结束。 - 不带
&的 FactoryBean 。走到这里说明beanInstance确实是个FactoryBean,但调用方要的是它生产的对象。如果有 mbd,标记mbd.isFactoryBean=true;否则先查 FactoryBean 产物缓存(getCachedObjectForFactoryBean,这是FactoryBeanRegistrySupport维护的一个缓存 Map)。 - 缓存未命中则调 getObjectFromFactoryBean 。如果 mbd 为 null 但本地有定义,临时合并一份 mbd。
synthetic标志决定是否做后置处理:合成的(synthetic,比如内部 bean)不做 BeanPostProcessor 回调。真正调factoryBean.getObject()的是getObjectFromFactoryBean(在FactoryBeanRegistrySupport里),它负责把 singleton 产物缓存起来。
这个方法是 & 协议的物理落点:带 & 返回工厂,不带 & 返回工厂产品 。doGetBean 里两处调用它------快路径(第 255 行)和创建后(第 343/356/378 行)------用的是同一个方法,所以无论 singleton 还是 prototype 还是自定义 scope,FactoryBean 后处理逻辑完全一致。
8.3 类型提前探测:为什么 getType 可能触发 FactoryBean 初始化
FactoryBean.getObjectType() 的设计目的,就是让容器在不真正调 getObject() 的情况下知道产物类型。这在按类型查找(getBeanNamesForType、@Autowired 候选筛选)时至关重要------比如你写 @Autowired DataSource ds;,容器要能列出所有类型为 DataSource 的 bean,包括那些由 FactoryBean<DataSource> 生产出来的。
但问题来了:如果某个 FactoryBean 的 getObjectType() 写得不规范,必须等到 getObject() 跑过才能算出类型怎么办?这时容器有两个选择:
- 保守:返回 null,承认"类型不可知"。
- 激进 :把 FactoryBean 初始化出来,调一次
getObject()看实际类型。
getType(String, boolean allowFactoryBeanInit) 那个参数就是控制这个开关的。类型匹配的批量场景(比如 getBeansOfType)会传 allowEagerInit,决定要不要为了看类型而触发副作用。这也是为什么 ListableBeanFactory.getBeanNamesForType 的注释反复警告:"passing in true for this flag will initialize FactoryBeans"。
8.4 FactoryBean 在 findAutowireCandidates 中的位置
后面讲依赖解析时会看到,按类型找候选 bean 时,容器对每个候选名字调 isTypeMatch(beanName, type)。这个方法内部会判断"这个 bean 是不是 FactoryBean,如果是,比较的是它 getObjectType() 的类型,而不是 FactoryBean 本身的类型"。这就是为什么你 @Autowired DataSource 能注入到一个 <bean id="dataSource" class="...DataSourceFactoryBean"/>------容器比较的是 FactoryBean 生产的 BasicDataSource 类型,不是 FactoryBean 本身。
一个常见坑:如果 DataSourceFactoryBean.getObjectType() 返回 null,那 @Autowired DataSource 就找不到它。这正是 FactoryBean 注释里那句"Autowiring will simply ignore FactoryBeans that return null here"的含义。
九、源码剖析(七):BeanDefinition 元模型
BeanDefinition 是 IoC 容器的"配方接口"。这一节把它的继承树从根到叶讲清楚。
9.1 BeanDefinition 接口
路径 spring-beans/src/main/java/org/springframework/beans/factory/config/BeanDefinition.java(378 行)。它本身继承了两个标记接口:AttributeAccessor(属性访问器,在 beans/PropertyAccessor.java 隔壁,实际在 core/AttributeAccessor.java)和 BeanMetadataElement(元数据元素,在 beans/BeanMetadataElement.java)。
它定义的关键方法(按职责分组):
- 类定位 :
getBeanClassName()(第 121 行)、setBeanClassName、getParentName()(第 97 行)。 - 作用域 :
getScope()/setScope(String)(第 128-134 行)。 - 懒加载 :
isLazyInit()(第 147 行)。 - 依赖 :
getDependsOn()/setDependsOn。 - 构造参数与属性 :
getConstructorArgumentValues()、getPropertyValues(),以及两个 5.1 的 default 方法hasConstructorArgumentValues()(第 253 行)、hasPropertyValues()(第 269 行)。 - 生命周期方法 :
getInitMethodName()(第 283 行)、getDestroyMethodName()(第 295 行)。 - 角色 :
getRole()(第 316 行),返回ROLE_APPLICATION(用户定义)/ROLE_SUPPORT(外部支持)/ROLE_INFRASTRUCTURE(容器自身)三档。 - 工厂相关 :
getFactoryBeanName()/getFactoryMethodName()。 - 候选标志 :
isAutowireCandidate()、isPrimary()。
AttributeAccessor 接口提供了一组"任意名字-值"的附件能力:setAttribute(String, Object)/getAttribute(String)/hasAttribute/attributeNames。它的实现是 AttributeAccessorSupport(core/AttributeAccessorSupport.java),内部维护一个 Map<String, Object>。为什么 BeanDefinition 需要这个?因为框架的很多扩展(比如注解元数据、AOP 配置标记)不想污染 BeanDefinition 的正式字段,就以"属性"的形式挂上去。BeanMetadataAttributeAccessor(beans/factory/config/BeanMetadataAttributeAccessor.java)在 AttributeAccessorSupport 基础上加了 source(来源资源)支持,AbstractBeanDefinition 就继承它。
9.2 AbstractBeanDefinition:配方的通用骨架
路径 support/AbstractBeanDefinition.java,第 61 行:
java
public abstract class AbstractBeanDefinition extends BeanMetadataAttributeAccessor
implements BeanDefinition, Serializable {
它把 BeanDefinition 接口的通用字段全部实现了。关键字段(第 169-221 行):
java
private volatile @Nullable Object beanClass; // 169 行:可能是 Class,也可能是 String 类名
private @Nullable String scope;
private boolean lazyInit;
private @Nullable String[] dependsOn;
private @Nullable Supplier<?> instanceSupplier; // 195 行:7.0 编程式注册用
private int role = BeanDefinition.ROLE_APPLICATION; // 221 行
注意 beanClass 字段的类型是 Object 而不是 Class<?>------它既可以是已经解析好的 Class,也可以是一个尚未加载的 String 类名 。getBeanClassName()(第 421-422 行)做了防御性读取:
java
Object beanClassObject = this.beanClass; // defensive access to volatile beanClass field
return (beanClassObject instanceof Class<?> clazz ? clazz.getName() : (String) beanClassObject);
这种"二态字段"设计是为了支持延迟类加载 :XML 解析阶段只拿到类名字符串,不立刻 Class.forName;等到真正需要实例化时才解析成 Class。这就是为什么 allowEagerClassLoading 那个开关能生效------by-type 查询时如果类还没加载,可以选择不强行加载。
validate() 方法前面 registerBeanDefinition 那节已经讲过:校验方法覆写与工厂方法不兼容。
9.3 GenericBeanDefinition:通用配方
路径 support/GenericBeanDefinition.java。它是 2.5 引入的"通用版"定义,取代了早期 XML 里 <bean> 元素必须区分 root/child 的繁琐。它就是 AbstractBeanDefinition 加一个 parentName 字段:
java
public void setParentName(@Nullable String parentName) { ... }
public @Nullable String getParentName() { ... }
GenericBeanDefinition 的特点是可继承父定义 但自身也可以独立存在(不强制有父)。XML 命名空间、注解驱动注册出来的定义,绝大多数都是 GenericBeanDefinition 或它的子类。
9.4 ChildBeanDefinition:父子继承的显式子类
路径 support/ChildBeanDefinition.java。版本事实:7.0.9 中它并没有被 @Deprecated 标记 ------它依然是一个活的、可编译的类。它的构造函数强制要求 parentName:
java
public ChildBeanDefinition(String parentName) { ... }
public ChildBeanDefinition(String parentName, MutablePropertyValues pvs) { ... }
它的语义是"必须依附于一个父定义",自己只覆盖部分属性。在 XML 时代,<bean id="child" parent="parentBean"> 解析出来就是 ChildBeanDefinition。现在注解驱动场景几乎不用它,但它仍然存在于类路径上,合并定义逻辑必须兼容它。
9.5 RootBeanDefinition:合并终点
路径 support/RootBeanDefinition.java,第 64 行 public class RootBeanDefinition extends AbstractBeanDefinition。它是"已经合并好的、可以直接用于创建"的定义。它不支持 parentName(getParentName 默认返回 null),因为它已经是父-子合并的产物。
它比 AbstractBeanDefinition 多出来的,是一堆运行时解析缓存字段(第 77-122 行):
java
volatile @Nullable ResolvableType targetType;
volatile @Nullable Class<?> resolvedTargetType;
volatile @Nullable Boolean isFactoryBean;
volatile @Nullable ResolvableType factoryMethodReturnType;
volatile @Nullable Method factoryMethodToIntrospect;
volatile @Nullable String resolvedDestroyMethodName;
volatile @Nullable Boolean beforeInstantiationResolved;
private @Nullable Set<Member> externallyManagedConfigMembers;
private @Nullable Set<String> externallyManagedInitMethods;
private @Nullable Set<String> externallyManagedDestroyMethods;
这些字段全是 volatile,因为合并后的 RootBeanDefinition 可能被多个线程并发访问。它们的作用是把"第一次解析出来的结果"缓存住:比如第一次判断出这个 bean 是不是 FactoryBean,就把结论存在 isFactoryBean 里,下次直接用,不用重复 introspect。externallyManaged* 那几个集合存的是被 @Autowired/@Resource 标注的成员和方法------由 MergedBeanDefinitionPostProcessor 在合并后填进去。
9.6 注解场景:AnnotatedGenericBeanDefinition 与 ScannedGenericBeanDefinition
AnnotatedGenericBeanDefinition:路径spring-beans/src/main/java/org/springframework/beans/factory/annotation/AnnotatedGenericBeanDefinition.java。它在GenericBeanDefinition之上加了AnnotatedTypeMetadata(注解元数据)和MethodMetadata(方法元数据)。编程式registry.registerBean(MyConfig.class)或AnnotatedBeanDefinitionReader读@Configuration类时用它。ScannedGenericBeanDefinition:路径spring-context/src/main/java/org/springframework/context/annotation/ScannedGenericBeanDefinition.java。组件扫描(@ComponentScan)扫出来的 bean 用它------它持有扫描时读到的注解元数据,但此时类可能还没被类加载(这是它和AnnotatedGenericBeanDefinition的关键区别:后者类已经加载,前者用 ASM 读元数据、类延迟加载)。
这两个类的共同父类是 AnnotatedBeanDefinition(spring-beans/.../factory/annotation/AnnotatedBeanDefinition.java),它扩展了 BeanDefinition,加了 getMetadata()、getFactoryMethodMetadata() 两个方法。
9.7 MergedBeanDefinition:为什么要"合并"
容器里同时存在这么多种 BeanDefinition:注册进来的是 GenericBeanDefinition/ChildBeanDefinition/AnnotatedGenericBeanDefinition,但创建时需要的是一个"最终配方"。这个拍平过程就是合并 ,产物是 RootBeanDefinition,缓存在 AbstractBeanFactory.mergedBeanDefinitions(第 164 行):
java
private final Map<String, RootBeanDefinition> mergedBeanDefinitions = new ConcurrentHashMap<>(256);
getMergedBeanDefinition(第 1396-1472 行)的关键逻辑前面已经读过,这里总结它的合并规则:
- 如果注册定义没有 parentName:如果它本身是
RootBeanDefinition,cloneBeanDefinition()复制一份;否则new RootBeanDefinition(bd)把它包成 Root。 - 如果有 parentName:递归
getMergedBeanDefinition(parentBeanName)拿到父的合并定义,new RootBeanDefinition(pbd)深拷贝父,再mbd.overrideFrom(bd)用子定义覆盖同名属性。overrideFrom(在AbstractBeanDefinition里)的规则是"子定义显式设过的属性覆盖父的,没设的沿用父的"。 - 合并后如果 scope 为空,默认设为 singleton(
SCOPE_SINGLETON)。 - 如果是嵌套在一个非 singleton 外部 bean 里的内部 bean,强制把内部 bean 的 scope 改成外部 bean 的 scope------不然会出现"外层 request scope、内层 singleton"这种矛盾。
- 合并结果按条件缓存(
cacheMergedBeanDefinition,第 1501-1503 行,就是往mergedBeanDefinitions里 put)。
缓存失效靠 stale 标志:clearMergedBeanDefinition(第 1523-1528 行)不删条目,只把 bd.stale = true;下次访问时发现 mbd.stale 就重新合并。这就是 registerBeanDefinition 末尾 resetBeanDefinition 要调 clearMergedBeanDefinition 的原因------旧配方变了,合并缓存作废。
至此,BeanDefinition 体系的全貌是:接口(BeanDefinition)→ 抽象骨架(AbstractBeanDefinition)→ 可注册的具体配方(Generic/Root/Child/Annotated/Scanned)→ 运行时合并产物(RootBeanDefinition + 缓存)。
十、源码剖析(八):依赖解析 doResolveDependency 与候选筛选
@Autowired 字段到底怎么找到 bean 的?入口是 DefaultListableBeanFactory.resolveDependency,它调 doResolveDependency(第 1661-1776 行)。这是 IoC 容器里最精巧的方法之一,我们逐段拆。
10.1 六个步骤总览
java
public @Nullable Object doResolveDependency(DependencyDescriptor descriptor, @Nullable String beanName,
@Nullable Set<String> autowiredBeanNames, @Nullable TypeConverter typeConverter) throws BeansException {
InjectionPoint previousInjectionPoint = ConstructorResolver.setCurrentInjectionPoint(descriptor);
try {
// Step 1: pre-resolved shortcut for single bean match, for example, from @Autowired
Object shortcut = descriptor.resolveShortcut(this);
if (shortcut != null) {
return shortcut;
}
Class<?> type = descriptor.getDependencyType();
// Step 2: pre-defined value or expression, for example, from @Value
Object value = getAutowireCandidateResolver().getSuggestedValue(descriptor);
// ...... @Value 解析 + 类型转换,略
// Step 3: declared dependency name or qualifier-suggested name
if (descriptor.usesStandardBeanLookup()) { ...... }
// Step 4a: stream / array / collection / map
Object multipleBeans = resolveMultipleBeans(descriptor, beanName, autowiredBeanNames, typeConverter);
if (multipleBeans != null) { return multipleBeans; }
// Step 4b: direct bean matches
Map<String, Object> matchingBeans = findAutowireCandidates(beanName, type, descriptor);
// Step 5: determine single candidate
if (matchingBeans.size() > 1) {
autowiredBeanName = determineAutowireCandidate(matchingBeans, descriptor);
// ......
}
// Step 6: validate single result
// ......
}
finally {
ConstructorResolver.setCurrentInjectionPoint(previousInjectionPoint);
}
}
六个步骤:
- 短路 :
descriptor.resolveShortcut(this)。少数特殊注入点(如某些场景下的@Bean方法参数)可以直接给出结果,跳过后面所有流程。 - @Value :
getAutowireCandidateResolver().getSuggestedValue(descriptor)。如果注入点标了@Value("${...}"),这里拿到值字符串,再resolveEmbeddedValue解析占位符、evaluateBeanDefinitionString求值 SpEL、最后用TypeConverter转成字段类型。这一步和"按类型找 bean"完全无关------它是值注入。 - 按名字直查 :如果注入点有声明的依赖名(比如字段名叫
userService)或 qualifier 建议名,先试containsBean(dependencyName),命中且类型匹配、是 autowire candidate、不是 fallback、没有 primary 冲突、不是自引用,就直接resolveBean(dependencyName, ...)返回。这是"by-name"快路径------字段名恰好等于某个 bean 名时,不必走全表扫描。 - 多 bean 集合 :如果依赖类型是
Stream/数组/List/Map,走resolveMultipleBeans把所有匹配的 bean 收集成集合。 - 找候选 + 定唯一 :
findAutowireCandidates拿到所有类型匹配的候选 Map;候选多于一个时,determineAutowireCandidate在里面挑一个。 - 校验 :
resolveInstance检查 NullBean、做isAssignableValue类型校验。
finally 里把 InjectionPoint 恢复成之前的值------setCurrentInjectionPoint 用 ThreadLocal 记录"当前正在解析哪个注入点",这个上下文在 @Value 求值 SpEL、ObjectFactory 延迟解析时都会用到。
10.2 findAutowireCandidates:候选从哪来
第 1959-2005 行:
java
protected Map<String, Object> findAutowireCandidates(
@Nullable String beanName, Class<?> requiredType, DependencyDescriptor descriptor) {
String[] candidateNames = BeanFactoryUtils.beanNamesForTypeIncludingAncestors(
this, requiredType, true, descriptor.isEager());
Map<String, Object> result = CollectionUtils.newLinkedHashMap(candidateNames.length);
for (Map.Entry<Class<?>, Object> classObjectEntry : this.resolvableDependencies.entrySet()) {
Class<?> autowiringType = classObjectEntry.getKey();
if (autowiringType.isAssignableFrom(requiredType)) {
Object autowiringValue = classObjectEntry.getValue();
autowiringValue = AutowireUtils.resolveAutowiringValue(autowiringValue, requiredType);
if (requiredType.isInstance(autowiringValue)) {
result.put(ObjectUtils.identityToString(autowiringValue), autowiringValue);
break;
}
}
}
for (String candidate : candidateNames) {
if (!isSelfReference(beanName, candidate) && isAutowireCandidate(candidate, descriptor)) {
addCandidateEntry(result, candidate, descriptor, requiredType);
}
}
// ...... 空结果时的 fallback 两轮尝试,略
return result;
}
逐段讲:
beanNamesForTypeIncludingAncestors:列出本地 + 父工厂里所有类型匹配的 bean 名。这一步可能触发isTypeMatch,进而可能触发 FactoryBean 类型探测。- 先查内建可注入对象表
resolvableDependencies。遍历这张表,凡是登记的类型能赋值给依赖类型(autowiringType.isAssignableFrom(requiredType))且实际值是该类型的实例,就作为候选加进去。key 用identityToString(autowiringValue)------因为内建对象没有 bean 名。 - 再遍历普通候选 。跳过自引用(
isSelfReference:注入点所在的 bean 自己),且isAutowireCandidate(candidate, descriptor)为 true(即这个 bean 定义没标autowire-candidate="false",且通过了 qualifier 检查)才加入。 - 空结果时的两轮 fallback :第一轮用
descriptor.forFallbackMatch()放宽类型匹配(处理泛型擦除等情况);第二轮连自引用也放进来------这是为了在极端情况下(比如一个 bean 依赖自己的早期引用)还能凑出候选。
addCandidateEntry(第 2011-2028 行)有个性能细节:对于已经是单例的 bean,直接 resolveCandidate 拿实例;否则只放 getType(candidateName)(Class 对象),不提前实例化。注释说"prevent early bean initialization ahead of primary candidate selection"------在还没决定选哪个之前,不要急着造 bean。
10.3 determineAutowireCandidate:多候选时的仲裁顺序
第 2038-2082 行:
java
protected @Nullable String determineAutowireCandidate(Map<String, Object> candidates, DependencyDescriptor descriptor) {
Class<?> requiredType = descriptor.getDependencyType();
// Step 1: check primary candidate
String primaryCandidate = determinePrimaryCandidate(candidates, requiredType);
if (primaryCandidate != null) {
return primaryCandidate;
}
// Step 2a: match bean name against declared dependency name
String dependencyName = descriptor.getDependencyName();
if (dependencyName != null) {
for (String beanName : candidates.keySet()) {
if (matchesBeanName(beanName, dependencyName)) {
return beanName;
}
}
}
// Step 2b: match bean name against qualifier-suggested name
String suggestedName = getAutowireCandidateResolver().getSuggestedName(descriptor);
if (suggestedName != null) {
for (String beanName : candidates.keySet()) {
if (matchesBeanName(beanName, suggestedName)) {
return beanName;
}
}
}
// Step 3: check highest priority candidate
String priorityCandidate = determineHighestPriorityCandidate(candidates, requiredType);
if (priorityCandidate != null) {
return priorityCandidate;
}
// Step 4: pick unique default-candidate
String defaultCandidate = determineDefaultCandidate(candidates);
if (defaultCandidate != null) {
return defaultCandidate;
}
// Step 5: pick directly registered dependency
for (Map.Entry<String, Object> entry : candidates.entrySet()) {
Object beanInstance = entry.getValue();
if (beanInstance != null && this.resolvableDependencies.containsValue(beanInstance)) {
return entry.getKey();
}
}
return null;
}
这是"多个 bean 都匹配时选谁"的完整优先级链,顺序非常重要:
@Primary:第一个判断。determinePrimaryCandidate(第 2092-2128 行)先找所有标了primary的候选;如果找到两个 primary,且它们都是本地定义(candidateLocal == primaryLocal),直接抛NoUniqueBeanDefinitionException------两个 primary 是配置错误。如果一个本地一个父工厂,选本地的(本地覆盖祖先)。找不到 primary 时,第二遍找"非 fallback"的唯一候选(isFallback,@Fallback注解)。- 按注入点声明名匹配 :字段名/参数名等于 bean 名。比如候选是
userService和userServiceV2,注入点字段叫userService,就选前者。 - 按 qualifier 建议名匹配 :
getSuggestedName(descriptor)------@Qualifier("xxx")里的值。 @Priority:determineHighestPriorityCandidate,基于jakarta.annotation.Priority,数值越小优先级越高。- 默认候选 :
determineDefaultCandidate------只有一个"非默认"候选时选它。 - 内建可注入对象 :最后才考虑
resolvableDependencies里的对象。
这个顺序和网上很多教程说的"@Primary 优先于字段名"一致,但要注意:字段名匹配排在 @Priority 之前 。也就是说,如果你有两个候选都没标 primary,但其中一个名字恰好等于字段名,那名字匹配赢,@Priority 根本不参与。
10.4 resolvableDependencies:内建可注入对象
AbstractApplicationContext.prepareBeanFactory(spring-context/src/main/java/org/springframework/context/support/AbstractApplicationContext.java 第 747-750 行)注册了四个:
java
beanFactory.registerResolvableDependency(BeanFactory.class, beanFactory);
beanFactory.registerResolvableDependency(ResourceLoader.class, this);
beanFactory.registerResolvableDependency(ApplicationEventPublisher.class, this);
beanFactory.registerResolvableDependency(ApplicationContext.class, this);
而 Environment 不在这四个里面 ------它不是通过 registerResolvableDependency 注册的,而是作为一个普通 singleton bean 注册,同时配合 ignoreDependencyInterface(EnvironmentAware.class)(第 737 行)让 EnvironmentAware 回调走 BeanPostProcessor 路径。这个精确差别值得记住:BeanFactory/ResourceLoader/ApplicationEventPublisher/ApplicationContext 是"特殊可解析依赖"(不经过候选筛选),Environment 是"普通 bean"。
registerResolvableDependency 的实现(DefaultListableBeanFactory 第 887-897 行)有个校验:autowiredValue 要么是 ObjectFactory,要么必须是 dependencyType 的实例,否则抛 IllegalArgumentException。
10.5 AutowireCandidateResolver:策略扩展点
接口 support/AutowireCandidateResolver.java 前面已经读过,它定义了一组 default 方法:isAutowireCandidate、isRequired、hasQualifier、getSuggestedName、getSuggestedValue、getLazyResolutionProxyIfNecessary。
默认实现 SimpleAutowireCandidateResolver.INSTANCE 只做最朴素的判断。到了 web/注解环境,GenericTypeAwareAutowireCandidateResolver(加泛型匹配)→ QualifierAnnotationAutowireCandidateResolver(加 @Qualifier/@Value)→ ContextAnnotationAutowireCandidateResolver(在 spring-context 的 context/annotation/ 包下,加 @Lazy 代理)。最后这个实现会在 @Lazy 注入点上生成一个延迟解析代理,调用时才真正去容器里取 bean------这就是为什么 @Lazy 能打破循环依赖。
本章聚焦工厂本身,这层 resolver 的具体扩展留给注解配置章节。
十一、源码剖析(九):DependencyDescriptor、按类型 getBean 与类型缓存
前面讲 doResolveDependency 时,有几个配角没展开。这一节把它们补深,让依赖解析这条链路完整闭合。
15.1 DependencyDescriptor 与 InjectionPoint
InjectionPoint(路径 spring-beans/src/main/java/org/springframework/beans/factory/InjectionPoint.java)是注入点的基类,封装"这个待注入位置是一个字段还是一个方法参数"。它持有一个 AnnotatedElement(被注解的元素)和一个 MethodParameter(如果是方法/构造参数)。
DependencyDescriptor extends InjectionPoint(路径 config/DependencyDescriptor.java)在它之上加了注入语义:
getDependencyType():依赖的实际类型(字段类型,或方法参数类型)。getResolvableType():带泛型信息的类型------这是为什么Map<String, UserService>这种泛型能被正确解析。isRequired():是否必需(@Autowired(required=true))。resolveCandidate(String beanName, Class<?> beanType, BeanFactory beanFactory):真正去容器里把这个候选 bean 取出来。resolveShortcut(BeanFactory):短路钩子。forFallbackMatch():返回一个放宽匹配规则的副本(泛型擦除场景用)。usesStandardBeanLookup():7.0 的新方法,判断"这个注入点是否走标准按名查找"------前面doResolveDependencyStep 3 用它决定要不要试字段名匹配。
为什么需要这么一个描述符对象,而不是直接传 Field/MethodParameter?因为注入点可能是字段、构造参数、方法参数三种形态,它们的反射 API 不一样。DependencyDescriptor 把它们统一成"我要一个什么类型、叫什么名、必需与否"的抽象,后面 doResolveDependency 的逻辑就不必关心自己面对的是字段还是参数。
15.2 getBean(Class) 的完整路径
getBean(Class<T>) 不走 doGetBean,它走的是 DefaultListableBeanFactory 自己的 resolveBean 路线。第 380-389 行:
java
@SuppressWarnings("unchecked")
@Override
public <T> T getBean(Class<T> requiredType, @Nullable Object @Nullable ... args) throws BeansException {
Assert.notNull(requiredType, "Required type must not be null");
Object resolved = resolveBean(ResolvableType.forRawClass(requiredType), args, false);
if (resolved == null) {
throw new NoSuchBeanDefinitionException(requiredType);
}
return (T) resolved;
}
resolveBean 内部调 resolveNamedBean(第 1569 行起)。它的逻辑是:
getBeanNamesForType(requiredType)列出所有匹配的 bean 名。- 候选为空 → 返回 null(抛
NoSuchBeanDefinitionException)。 - 候选只有一个 → 直接
resolveNamedBean(candidateNames[0], requiredType, args)走doGetBean。 - 候选多个 → 逐个判断:是不是 primary、名字是否匹配参数......最终选出一个;选不出来就抛
NoUniqueBeanDefinitionException。
注意这个路径和 doResolveDependency 的候选仲裁顺序不完全相同 ------getBean(Class) 是给人用的"手动按类型取",它的仲裁更简单(primary + 唯一 + 名字匹配),而 @Autowired 自动注入走的是 doResolveDependency 那条更复杂的链(还要处理集合、@Value、@Lazy 代理)。理解这个差异,能解释一个现象:为什么 @Autowired Foo foo 能正常注入,但 getBean(Foo.class) 却报 NoUniqueBeanDefinitionException------两者用的仲裁代码不完全一样。
15.3 getBeanNamesForType 与按类型缓存
getBeanNamesForType(第 602 行起)是 findAutowireCandidates 和 getBeansOfType 的公共基础。它的性能关键是那两个缓存 Map:allBeanNamesByType 和 singletonBeanNamesByType。
逻辑大致是:
- 如果配置已冻结(
configurationFrozen=true),先查allBeanNamesByType.get(type),命中直接返回------不必遍历全表做isTypeMatch。 - 未命中则遍历
beanDefinitionNames,对每个 bean 名调isTypeMatch(beanName, type, allowFactoryBeanInit),收集匹配的名字,结果写进缓存。 getBeansOfType(第 727-740 行)更简单:先getBeanNamesForType拿名字数组,再对每个名字getBean取实例,塞进 Map。
这解释了 freezeConfiguration() 的价值:冻结之后,按类型查找从"每次 O(n) 遍历 + 类型匹配"变成"O(1) 查缓存"。容器启动完成、单例都造好后,调一次 freezeConfiguration() 是有性能收益的------ApplicationContext.refresh() 在 finishBeanFactoryInitialization 之后会自动冻结。
15.4 ConfigurableBeanFactory 的其余配置面
补几个前面没展开但日常会用到的配置项:
registerScope(String scopeName, Scope scope):注册自定义作用域。Scope接口(config/Scope.java)只有四个方法:get(name, objectFactory)、remove(name)、registerDestructionCallback(name, callback)、getConversationId()。web 环境的RequestScope/SessionScope就实现了它------get方法从HttpServletRequest/HttpSession属性里取 bean,取不到就用objectFactory.getObject()造一个塞回去。setTypeConverter(TypeConverter):替换默认类型转换器。doGetBean末尾adaptBeanInstance、doResolveDependency里@Value转换都用它。addBeanPostProcessor(BeanPostProcessor):加一个后置处理器。注意它有一个隐含约束------必须在任何 bean 实例化之前加 ,因为已经在创建中的 bean 不会回头应用新的 processor。AbstractBeanFactory里有个beanPostProcessorsList 和一个beanPostProcessorCache(按类型分组的缓存),加 processor 时会重建缓存。resolveEmbeddedValue(String):解析${...}占位符。背后是EmbeddedValueResolver(config/EmbeddedValueResolver.java),最终委托给PropertySourcesPlaceholderConfigurer。setTempClassLoader:设置临时类加载器。组件扫描时用 ASM 读注解元数据,不真正加载类;等到真正需要实例化时才切回主 ClassLoader。这就是为什么扫描阶段即使类路径有问题也不会立刻报错。
15.5 ObjectProvider 的实现本质
ObjectProvider(路径 factory/ObjectProvider.java)继承了 ObjectFactory<T> 和 Iterable<T>。它的方法:getIfAvailable()、getIfUnique()、ifAvailable(Consumer)、iterator()、stream()、orderedStream()。
在 DefaultListableBeanFactory 里,getBeanProvider 返回的是一个 BeanProvider(内部类,在 AbstractBeanFactory 或 DefaultListableBeanFactory 里),它持有"beanFactory 引用 + requiredType + allowEagerInit",每次调 getObject() 时才真正走 getBean(requiredType)。这就是"延迟"的本质------provider 本身不持有 bean,只持有"怎么去找"的上下文。
@Lazy 注入能打破循环依赖,靠的也是类似机制:ContextAnnotationAutowireCandidateResolver.getLazyResolutionProxyIfNecessary 生成一个代理对象注入字段,代理内部持有 DependencyDescriptor 和 beanFactory,第一次调方法时才真正 resolveDependency------此时双方都已初始化完成,不再循环。
15.6 一个被忽略的细节:MarkBeanAsCreated 与并发
doGetBean 里那行 markBeanAsCreated(beanName),除了标记"开始创建",还把 bean 名加进 alreadyCreated 集合。这个集合在两个地方被用:
hasBeanCreationStarted()判断"启动期是否结束"------前面registerBeanDefinition的写时复制分支就靠它。isSingletonCurrentlyInCreation系列并发判断的辅助信号。
7.0 的并行预实例化之所以能安全地"多个线程同时准备造 bean",靠的就是这个集合配合 singletonsCurrentlyInCreation(一个 Set<String>,在 DefaultSingletonBeanRegistry 第 101 行)。当后台线程 A 开始造 beanX,它把 beanX 加进 singletonsCurrentlyInCreation;主线程随后走到 beanX,instantiateSingleton 发现它正在被造,抛 BeanCurrentlyInCreationException,主线程 catch 后跳过------这就是 preInstantiateSingleton 第 1193-1196 行那段宽容处理的由来。
十二、运行机制:一次 getBean("userService") 的四层落点
前面九节都是"拆零件",这一节把它们装回去,走一遍真实的 getBean("userService")。我们用"接口 → 实现 → 数据结构 → 缓存"四层视角,看每一步落在哪。
11.1 调用栈全貌
假设一个标准 Spring Boot 应用,内部是 AnnotationConfigApplicationContext,它持有的 beanFactory 就是一个 DefaultListableBeanFactory。现在业务代码调 context.getBean("userService"):
scss
业务代码
└─ ApplicationContext.getBean("userService") [spring-context]
└─ AbstractApplicationContext 委托给 beanFactory
└─ AbstractBeanFactory.getBean(String) [spring-beans/AbstractBeanFactory.java:195]
└─ doGetBean("userService", null, null, false) [:236]
第一层:接口 。业务代码拿到的引用类型是 ApplicationContext(它 extends ListableBeanFactory 等),但它自己不存 bean,全部委托给内部的 beanFactory。
11.2 名字解析:transformedBeanName
scss
doGetBean
└─ transformedBeanName("userService")
├─ BeanFactoryUtils.transformedBeanName(...) 剥 '&' 前缀(此处无)
└─ canonicalName("userService") 查别名表,一路追到规范名
第二层:实现 。canonicalName 在 SimpleAliasRegistry(core/SimpleAliasRegistry.java)里,别名表是一个 Map<String,String> aliasMap。假设 "userService" 不是别名,原样返回。
第三层:数据结构 。aliasMap。
11.3 单例快路径
scss
doGetBean
└─ getSingleton("userService") [DefaultSingletonBeanRegistry:208]
├─ singletonObjects.get("userService") 一级缓存
├─ earlySingletonObjects.get(...) 二级(一级没命中才查)
└─ singletonFactories.get(...) 三级(二级没命中 + 正在创建才查)
第三层:数据结构。三个 ConcurrentHashMap。这是热路径------第一次 getBean 时这里返回 null(bean 还没造),之后每次都命中一级缓存。
11.4 创建路径(假设是第一次)
快路径没命中,进入 else 分支:
scss
isPrototypeCurrentlyInCreation("userService") → false(不是 prototype)
getParentBeanFactory() != null && !containsBeanDefinition("userService")
→ containsBeanDefinition 返回 true(本地有定义),不回退父工厂
markBeanAsCreated("userService")
getMergedLocalBeanDefinition("userService")
└─ mergedBeanDefinitions.get("userService") 第四层:合并定义缓存
(未命中)→ getMergedBeanDefinition(beanName, bd)
├─ synchronized(mergedBeanDefinitions)
├─ 无 parentName → new RootBeanDefinition(bd)
├─ 默认 scope=singleton
└─ cacheMergedBeanDefinition → mergedBeanDefinitions.put(...)
checkMergedBeanDefinition → 非抽象,放行
dependsOn → null,跳过
mbd.isSingleton() → true
getSingleton("userService", () -> createBean(...))
└─ [DefaultSingletonBeanRegistry:255] 三级缓存 + 加锁 + 执行工厂 lambda
└─ createBean("userService", mbd, null) [AbstractAutowireCapableBeanFactory]
└─ doCreateBean → 实例化 → populateBean → initializeBean
(细节属生命周期章,此处不展开)
第四层:缓存 。mergedBeanDefinitions 这次合并的结果被缓存下来,下次 getBean 同一个 bean 直接命中。
11.5 FactoryBean 后处理
java
getObjectForBeanInstance(sharedInstance, null, "userService", "userService", mbd)
├─ isFactoryDereference("userService") → false(无 &)
├─ sharedInstance instanceof FactoryBean → 假设不是,直接返回
└─ (若是 FactoryBean,则查 FactoryBean 产物缓存 → getObjectFromFactoryBean)
11.6 类型适配与返回
csharp
adaptBeanInstance("userService", bean, null)
└─ requiredType 为 null,不做转换,直接返回
11.7 如果这次 getBean 是为了 @Autowired 注入
假设字段是 @Autowired UserService userService;,走的不是 getBean(String),而是 resolveDependency:
java
DefaultListableBeanFactory.resolveDependency(descriptor, "orderService", ...)
└─ doResolveDependency
├─ descriptor.resolveShortcut → null
├─ getAutowireCandidateResolver().getSuggestedValue → null(不是 @Value)
├─ usesStandardBeanLookup → 字段名 "userService",containsBean 命中
│ → isTypeMatch / isAutowireCandidate / !isFallback / !hasPrimaryConflict / !isSelfReference
│ → resolveBean("userService", ...) → 回到上面 11.2-11.6 的 getBean 流程
└─ (字段名不匹配时才走 findAutowireCandidates + determineAutowireCandidate)
11.8 四层视角总结
把这次调用涉及的"表"按层归类:
| 层 | 数据结构 | 所在类 | 这次访问做了什么 |
|---|---|---|---|
| 别名表 | aliasMap |
SimpleAliasRegistry |
canonicalName 追规范名 |
| 定义表 | beanDefinitionMap / beanDefinitionNames |
DefaultListableBeanFactory |
containsBeanDefinition 确认本地有 |
| 合并定义缓存 | mergedBeanDefinitions |
AbstractBeanFactory |
第一次合并 RootBeanDefinition 并缓存 |
| 单例三级缓存 | singletonObjects/earlySingletonObjects/singletonFactories |
DefaultSingletonBeanRegistry |
第一次未命中触发创建,之后命中一级 |
| FactoryBean 产物缓存 | factoryBeanObjectCache |
FactoryBeanRegistrySupport |
普通 bean 不涉及 |
这就是"接口 → 实现 → 数据结构 → 缓存"四层的完整落点。新手最容易迷糊的地方是:为什么 getBean 要查这么多表? 答案是每一层都在解决不同的问题------别名表解决"名字别名",定义表解决"配方存不存在",合并缓存解决"父子定义拍平",单例缓存解决"复用还是新建"。
11.9 单例注册表与定义注册表如何配合
最后把这两张表的配合关系讲透:
- 启动期 :
BeanDefinitionReader(XML 读取器)或ComponentScan把所有配方写进beanDefinitionMap/beanDefinitionNames。此时singletonObjects是空的。 - 预实例化期 :
preInstantiateSingletons遍历beanDefinitionNames,对每个非抽象 singleton 调getBean,触发createBean,成品被addSingleton写进singletonObjects。 - 运行期 :业务
getBean先查singletonObjects,命中直接返回;未命中才回beanDefinitionMap找配方现造。
两张表的生命周期是:定义表先满,单例表后满;定义表是"图纸仓库",单例表是"成品仓库" 。registerSingleton 是一个例外入口------它跳过定义表,直接往单例表塞现成对象(比如 Spring 把 Environment 这个对象直接 registerSingleton 进去,不建配方)。
这也解释了为什么 containsBeanDefinition 和 containsBean 的行为不同:前者只看定义表,后者两张表都看。
十三、扩展用法与最佳实践
12.1 脱离 ApplicationContext,直接用 DefaultListableBeanFactory
DefaultListableBeanFactory 是一个可以独立使用的 IoC 容器,不依赖 spring-context。下面是一个最小可运行例子(注意:纯演示,不引入任何 XML/注解扫描):
java
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.beans.factory.support.RootBeanDefinition;
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
// 1. 编程式注册一个 bean 定义
beanFactory.registerBeanDefinition("userService",
new RootBeanDefinition(UserService.class));
// 2. 再注册一个,并通过构造参数引用 userService
RootBeanDefinition orderBd = new RootBeanDefinition(OrderService.class);
orderBd.getConstructorArgumentValues()
.addIndexedArgumentValue(0, new RuntimeBeanReference("userService"));
beanFactory.registerBeanDefinition("orderService", orderBd);
// 3. 触发预实例化(可选;不调的话就是懒加载)
beanFactory.preInstantiateSingletons();
// 4. 取对象
UserService userService = beanFactory.getBean("userService", UserService.class);
7.0 还提供了更简洁的 registerBean(在 GenericApplicationContext/DefaultListableBeanFactory 的 BeanDefinitionRegistry 默认方法里,基于 InstanceSupplier):
java
beanFactory.registerBean("userService", UserService.class, () -> new UserService());
InstanceSupplier(路径 support/InstanceSupplier.java)是 5.0 引入的函数式接口,允许用 lambda 直接指定创建逻辑,跳过反射实例化。
手动注册单例用 registerSingleton(继承自 SingletonBeanRegistry):
java
// 不建配方,直接把现成对象塞进单例表
beanFactory.registerSingleton("environment", envObject);
它和 registerBeanDefinition 的区别前面讲过:前者没有配方、不出现在 beanDefinitionNames、不参与预实例化,只出现在单例表和 getBeanNamesForType 结果里。
12.2 编程式使用的注意点
- 必须自己加 BeanPostProcessor 。裸
DefaultListableBeanFactory不会有任何@Autowired处理能力------那是AutowiredAnnotationBeanPostProcessor干的事,而它是 spring-context 里的类。如果你希望@Autowired生效,得自己beanFactory.addBeanPostProcessor(new AutowiredAnnotationBeanPostProcessor())。 - 必须自己调 preInstantiateSingletons 。裸工厂不会自动启动。这和
ApplicationContext.refresh()自动调它不同。 - 并发与热更新 。
registerBeanDefinition在hasBeanCreationStarted()之后走锁内写时复制,是线程安全的,但它会触发resetBeanDefinition→destroySingleton------意味着启动后再注册同名定义,会把已经造好的单例销毁。在生产环境做"热更新 bean"是危险操作:持有旧引用的地方还在用旧对象,新 getBean 拿到的是新对象,两份状态可能不一致。 allowBeanDefinitionOverriding默认 true 。裸工厂里同名后注册的定义会静默覆盖先注册的。如果你的场景要求严格(比如插件系统),建议beanFactory.setAllowBeanDefinitionOverriding(false),让覆盖直接抛异常。
12.3 BeanFactory 与 ApplicationContext 的选择
这两个不是二选一的竞争关系,而是包含关系 :ApplicationContext 内部持有一个 DefaultListableBeanFactory,在它之上加了一层。差异点:
| 能力 | BeanFactory(裸 DefaultListableBeanFactory) | ApplicationContext |
|---|---|---|
| bean 实例化/依赖注入 | 有 | 有(委托给内部 factory) |
| 注解配置类注册/组件扫描 | 需手动加 reader | 开箱即用 |
| BeanPostProcessor 自动注册 | 否 | 是 |
| 国际化(MessageSource) | 无 | 有 |
| 事件发布(ApplicationEventPublisher) | 无 | 有 |
| 环境抽象(Environment/Profile) | 无 | 有 |
| 启动时预实例化单例 | 需手动调 | refresh() 自动调 |
| 资源加载(ResourceLoader) | 无 | 有 |
选择建议:
- 库/中间件作者 :如果你的组件需要被宿主应用集成,依赖
BeanFactory接口就够了------这样你的库既能在 ApplicationContext 里跑,也能在裸 BeanFactory 里跑,耦合最小。 - 业务应用:直接用 ApplicationContext。裸 BeanFactory 的那些"手动活"(加 post-processor、调预实例化)在 ApplicationContext 里全自动。
- 测试 :可以用
StaticListableBeanFactory(support/StaticListableBeanFactory.java)------它不创建 bean,只持有你手动塞进去的实例,写单测时mock 依赖特别方便。
12.4 getBeanProvider 的正确用法
getBeanProvider(Class) 返回的 ObjectProvider 是延迟查找句柄,比 getBean(Class) 更宽容:
java
ObjectProvider<UserService> provider = beanFactory.getBeanProvider(UserService.class);
UserService s = provider.getIfAvailable(); // 没有就返回 null,不抛异常
UserService s2 = provider.getIfUnique(); // 唯一才返回,多个返回 null
provider.ifAvailable(s -> s.doWork()); // 有就消费
注入场景里,@Autowired ObjectProvider<UserService> 比 @Autowired UserService 更适合"可选依赖"或"需要延迟初始化"的场景。7.0 新增的 getBeanProvider(ParameterizedTypeReference) 让你能精确取带泛型的 bean:
java
ObjectProvider<List<Foo>> p =
beanFactory.getBeanProvider(new ParameterizedTypeReference<List<Foo>>() {});
12.5 线程安全要点总结
把本章涉及的并发模型收拢成几条:
- 定义表写时复制 :启动后新增/删除
beanDefinitionNames在synchronized(beanDefinitionMap)内做整表替换,volatile保证引用可见。 - 单例表 ReentrantLock :
singletonLock保护三级缓存的写路径,读路径先无锁查一级缓存。 - 合并定义表 synchronized :
getMergedBeanDefinition在synchronized(mergedBeanDefinitions)内 double-check。 - 7.0 并行预实例化 :靠
bootstrapExecutor+PreInstantiationThreadLocal + 三级缓存挂 future 实现"宽松并行",用tryLock快速失败避免跨线程提前推断。 beanDefinitionMap本身是 ConcurrentHashMap:单个 put/get 是线程安全的,但"读名字列表"这种复合操作仍需外部同步------这就是为什么要整表复制。
理解这些,你才能在自定义 BeanFactoryPostProcessor 或启动后动态注册 bean 时,知道哪些操作安全、哪些会踩坑。
附录:AbstractBeanFactory 的其余关键字段与 FactoryBeanRegistrySupport
为了让"工厂内部到底存了什么"这张地图更完整,补读 AbstractBeanFactory 的两个字段(第 143、158 行):
java
private final Map<Class<?>, Class<? extends PropertyEditor>> customEditors = new HashMap<>(4);
private final Map<String, Scope> scopes = new LinkedHashMap<>(8);
customEditors:自定义PropertyEditor注册表。ConfigurableBeanFactory.registerCustomEditor往里加。属性填充时遇到字符串到特殊类型的转换(比如把 "2024-01-01" 转成Date),就从这里查 editor。scopes:作用域注册表。前面 doGetBean 的自定义 scope 分支用的就是它。默认只注册了 singleton 和 prototype 两个内建 scope;web 环境的 request/session/globalSession 由AbstractApplicationContext子类(如XmlWebApplicationContext)启动时registerScope进去。
另外 AbstractBeanFactory 还维护着 beanPostProcessors(一个 List<BeanPostProcessor>)和一个按类型分组的 beanPostProcessorCache(BeanPostProcessorCache record,把 before/after/destructionAware/mergedDefinition 四类 processor 各缓存成数组)。这个缓存是 6.0 引入的优化------applyBeanPostProcessorsBeforeInitialization 等热路径不再每次遍历全 List 做 instanceof 判断,直接取缓存好的子数组。
FactoryBeanRegistrySupport 的产物缓存
getObjectForBeanInstance 里那个 getCachedObjectForFactoryBean,实现在 FactoryBeanRegistrySupport(路径 support/FactoryBeanRegistrySupport.java)。它维护一个 Map<String, Object> factoryBeanObjectCache:key 是 bean 名,value 是 FactoryBean 生产出来的单例产物。singleton 类型的 FactoryBean 产物第一次调 getObject() 后就缓存进来,之后 getBean("xxx") 直接命中缓存,不会重复调 getObject()。prototype 类型的 FactoryBean 不缓存,每次都新建。
这套设计和单例表是平行的:单例表存"FactoryBean 本身",factoryBeanObjectCache 存"FactoryBean 生产的对象" 。两级缓存各司其职,getBean("&xxx") 查前者,getBean("xxx") 查后者。
BeanDefinitionReaderUtils:注册动作的公共入口
最后补一个常被忽略的环节。XML/注解 reader 解析出一个定义后,并不直接调 beanFactory.registerBeanDefinition,而是走 BeanDefinitionReaderUtils.registerBeanDefinition(support/BeanDefinitionReaderUtils.java)。它额外做了两件事:处理 BeanDefinitionHolder 里带的别名(registerAlias),以及统一处理"定义名 + 别名"的注册。这也是为什么 registerBeanDefinition 里要专门处理"撞别名"分支------reader 可能在同一个名字上既注册定义又注册别名,顺序错乱就冲突。
把这几个配角补齐,IoC 工厂的内部结构就完全闭合了:接口族定义行为,DefaultListableBeanFactory 持有四表,AbstractBeanFactory 提供模板方法 doGetBean,FactoryBeanRegistrySupport 兜住 FactoryBean 产物缓存,BeanDefinitionReaderUtils 统一注册入口。本章的内容到这里自足闭环。
十四、常见问题与坑
13.1 getBean("xxx") 与 getBean("&xxx") 的区别
这是 FactoryBean 协议最容易踩的坑。假设容器里注册了:
java
class MyFactoryBean implements FactoryBean<MyBean> { ... }
beanFactory.registerBeanDefinition("myBean", new RootBeanDefinition(MyFactoryBean.class));
那么:
getBean("myBean")→ 调MyFactoryBean.getObject(),返回一个MyBean实例。getBean("&myBean")→ 返回MyFactoryBean本身。
源码落点在 getObjectForBeanInstance(AbstractBeanFactory.java:1845):BeanFactoryUtils.isFactoryDereference(name) 判断原始 name 是否带 &。注意 transformedBeanName 已经把 & 剥掉了,所以判断必须用原始 name ,而不是剥过的 beanName。一个常见错误是以为 & 是别名------不是,它是一个前缀协议,在 doGetBean 入口处就被识别。
如果 getBean("&xxx") 但 xxx 实际不是 FactoryBean,会抛 BeanIsNotAFactoryException(第 1850 行)。
13.2 getBeansOfType 与 List 注入的顺序
@Autowired List<Handler> handlers; 注入的顺序由什么决定?答案在 resolveMultipleBeanCollection(第 1863-1887 行):
java
Map<String, Object> matchingBeans = findAutowireCandidates(beanName, elementType,
new MultiElementDescriptor(descriptor));
// ......
Object result = converter.convertIfNecessary(matchingBeans.values(), descriptor.getDependencyType());
if (result instanceof List<?> list && list.size() > 1) {
Comparator<Object> comparator = adaptDependencyComparator(matchingBeans);
if (comparator != null) {
list.sort(comparator);
}
}
顺序规则是:
- 先按
beanDefinitionNames的注册顺序 (findAutowireCandidates用的beanNamesForTypeIncludingAncestors返回的是定义顺序)。 - 如果配了
dependencyComparator(ApplicationContext 里是AnnotationAwareOrderComparator),再按@Order/Ordered/@Priority排序。
所以:不写 @Order 时,顺序就是 bean 注册顺序(XML 是文档顺序,组件扫描是类路径扫描顺序------后者不稳定 ,依赖文件系统遍历顺序)。想要确定顺序,必须显式标 @Order。
13.3 重复 bean 名覆盖
两个同名 bean 定义注册进来,谁赢?看 registerBeanDefinition(第 1268-1277 行):
allowBeanDefinitionOverriding默认 true:后注册的赢,旧的被静默替换(只打日志)。- 设为 false:直接抛
BeanDefinitionOverrideException。
没有"按定义内容新旧判断"的逻辑------7.0.9 里没有 lastModified 时间戳比较 。谁后注册谁赢,这是唯一规则。这也是 Spring Boot 2.1 开始把 allow-bean-definition-overriding 默认改成 false 的原因:防止开发者意外覆盖自动配置的 bean。
覆盖后会发生 resetBeanDefinition:合并定义缓存标记 stale、旧单例被 destroySingleton。也就是说,如果同名 singleton 已经造好了,再覆盖定义,旧实例会被销毁,下次 getBean 按新定义重建。
13.4 NoUniqueBeanDefinitionException 与 @Primary/@Priority 的解析顺序
多个候选 bean 匹配类型时,determineAutowireCandidate 的仲裁顺序(前面贴过完整源码)是:
@Primary(多个 primary 直接报错);- 字段名/参数名匹配;
- qualifier 建议名;
@Priority(数值小优先);- 默认候选(唯一非 fallback);
- 内建可注入对象。
一个容易搞混的点:字段名匹配排在 @Priority 前面 。假设两个 bean userServiceV1、userServiceV2 都标了 @Priority(1)/@Priority(2),但注入字段叫 userServiceV1,那会选 userServiceV1------因为第 2 步名字匹配就命中返回了,根本走不到第 4 步。
另一个坑:@Primary 和 @Priority 不在一个层级。@Primary 是"这个类型默认选我",@Priority 是"多个候选时排个序"。如果有两个候选都标了 @Primary,容器不做优先级仲裁,直接抛 NoUniqueBeanDefinitionException(determinePrimaryCandidate 第 2102-2106 行:两个本地 primary 直接抛异常)。
13.5 registerBeanDefinition 的"lastModified 语义"
任务书里提到这个点,这里明确:7.0.9 的 registerBeanDefinition 没有任何基于时间戳的 lastModified 比较 。同名覆盖的判断只看 isBeanDefinitionOverridable() 布尔开关。网上一些旧资料提到的"只有新定义比旧定义新才覆盖"的语义,在 spring-beans 本身并不存在------那是某些上层框架(如 Spring Boot 的条件评估)自己加的逻辑。以仓库代码为准:覆盖就是"后写覆盖先写"。
13.6 isTypeMatch 为什么可能触发 FactoryBean 初始化
getBeanNamesForType(SomeType.class) 看似只是"查类型",但它可能被迫初始化 FactoryBean。原因:要知道 FactoryBean 生产的对象类型,理想情况读 getObjectType();如果该方法实现得不理想,只能真的调一次 getObject()。这就是 allowEagerInit 参数存在的意义。性能敏感场景下,批量 by-type 查询要意识到这个副作用------它可能触发数据库连接池初始化之类的重量级操作。
13.7 为什么 containsBean 为 true 但 getBean 抛 NoSuchBeanDefinitionException
containsBean 的注释(BeanFactory.java:285-299)专门警告:它返回 true 不代表 getBean 一定能拿到实例。因为它只判断"有没有这个名字的定义或单例",不判断这个定义是不是 abstract、是不是 lazy。一个 abstract 的父模板定义,containsBean 返回 true,但 getBean 会抛 BeanIsAbstractException(checkMergedBeanDefinition,AbstractBeanFactory.java:1513)。
十五、本章小结
本章从 BeanFactory 接口族出发,逐层深入到 DefaultListableBeanFactory 的数据结构、registerBeanDefinition 的注册事务、preInstantiateSingletons 的 7.0 并行模型、doGetBean 的完整取对象流程、FactoryBean 的 & 协议、BeanDefinition 的元模型与合并机制、以及 doResolveDependency 的候选仲裁链。核心结论:
- 接口按用途拆分 :根接口管"取/查",
ListableBeanFactory管"枚举",Configurable*管"配置",AutowireCapableBeanFactory管"外部对象生命周期",最终聚合在ConfigurableListableBeanFactory。 - 四张表 :定义表(
beanDefinitionMap+beanDefinitionNames)、单例三级缓存(singletonObjects/earlySingletonObjects/singletonFactories)、按类型索引缓存、内建可注入对象表(resolvableDependencies)。 - 7.0 新特性 :并行预实例化(
bootstrapExecutor+isBackgroundInit+PreInstantiationThreadLocal)、getBeanProvider(ParameterizedTypeReference)、FACTORY_BEAN_PREFIX_CHARchar 常量。 - 候选仲裁顺序:@Primary → 字段名 → qualifier 名 → @Priority → 默认候选 → 内建对象。
- 合并定义 :注册时的多种 BeanDefinition 统一拍平成 RootBeanDefinition 缓存在
mergedBeanDefinitions,stale标志控制失效。
本章关键类清单
| 类名 | 模块与相对路径 | 职责一句话 |
|---|---|---|
BeanFactory |
spring-beans/src/main/java/org/springframework/beans/factory/BeanFactory.java | IoC 容器根接口:getBean 家族与查方法 |
HierarchicalBeanFactory |
spring-beans/.../factory/HierarchicalBeanFactory.java | 父子层次:getParentBeanFactory、containsLocalBean |
ListableBeanFactory |
spring-beans/.../factory/ListableBeanFactory.java | 可枚举:批量按名/按类型列举 |
AutowireCapableBeanFactory |
spring-beans/.../factory/config/AutowireCapableBeanFactory.java | 外部对象执行完整 bean 生命周期 |
ConfigurableBeanFactory |
spring-beans/.../factory/config/ConfigurableBeanFactory.java | 工厂配置面:scope/类加载器/类型转换/BeanPostProcessor |
ConfigurableListableBeanFactory |
spring-beans/.../factory/config/ConfigurableListableBeanFactory.java | 聚合接口:freeze、preInstantiateSingletons、resolveDependency |
DefaultListableBeanFactory |
spring-beans/.../factory/support/DefaultListableBeanFactory.java | 全功能工厂:定义表+候选筛选+预实例化 |
AbstractBeanFactory |
spring-beans/.../factory/support/AbstractBeanFactory.java | doGetBean 主流程、合并定义缓存、FactoryBean 后处理 |
AbstractAutowireCapableBeanFactory |
spring-beans/.../factory/support/AbstractAutowireCapableBeanFactory.java | createBean/doCreateBean 骨架(生命周期细节见后续章节) |
DefaultSingletonBeanRegistry |
spring-beans/.../factory/support/DefaultSingletonBeanRegistry.java | 单例三级缓存、singletonLock、销毁注册 |
FactoryBeanRegistrySupport |
spring-beans/.../factory/support/FactoryBeanRegistrySupport.java | FactoryBean 产物缓存 |
BeanDefinition |
spring-beans/.../factory/config/BeanDefinition.java | bean 配方接口 |
AbstractBeanDefinition |
spring-beans/.../factory/support/AbstractBeanDefinition.java | 配方通用骨架与 validate |
GenericBeanDefinition |
spring-beans/.../factory/support/GenericBeanDefinition.java | 通用配方,可带 parentName |
RootBeanDefinition |
spring-beans/.../factory/support/RootBeanDefinition.java | 合并终点,运行时解析缓存字段 |
ChildBeanDefinition |
spring-beans/.../factory/support/ChildBeanDefinition.java | 必须依附父定义的子配方(7.0.9 未废弃) |
AnnotatedGenericBeanDefinition |
spring-beans/.../factory/annotation/AnnotatedGenericBeanDefinition.java | 带注解元数据的通用配方 |
ScannedGenericBeanDefinition |
spring-context/src/main/java/org/springframework/context/annotation/ScannedGenericBeanDefinition.java | 组件扫描产物,ASM 读元数据 |
FactoryBean |
spring-beans/.../factory/FactoryBean.java | 工厂 bean:getObject/getObjectType/isSingleton |
SmartFactoryBean |
spring-beans/.../factory/SmartFactoryBean.java | FactoryBean 扩展:isEagerInit/isPrototype |
DependencyDescriptor |
spring-beans/.../factory/config/DependencyDescriptor.java | 注入点描述符:类型、名字、是否必需 |
InjectionPoint |
spring-beans/.../factory/InjectionPoint.java | 注入点基类(字段/方法参数) |
AutowireCandidateResolver |
spring-beans/.../factory/support/AutowireCandidateResolver.java | 候选判断策略接口 |
SimpleAutowireCandidateResolver |
spring-beans/.../factory/support/SimpleAutowireCandidateResolver.java | 默认候选判断:只看 isAutowireCandidate 标志 |
ContextAnnotationAutowireCandidateResolver |
spring-context/src/main/java/org/springframework/context/annotation/ContextAnnotationAutowireCandidateResolver.java | @Lazy 代理等上下文扩展 |
ObjectProvider |
spring-beans/.../factory/ObjectProvider.java | 延迟查找句柄 |
BeanDefinitionRegistry |
spring-beans/.../factory/support/BeanDefinitionRegistry.java | 定义注册表面接口 |
SingletonBeanRegistry |
spring-beans/.../factory/config/SingletonBeanRegistry.java | 单例注册表面接口 |