Spring 容器读取 XML 和创建 Bean 是两个相连、但不能混为一谈的过程:前者把配置转换成 BeanDefinition,后者依据定义查找或创建对象。这篇笔记沿着阅读《Spring 源码深度解析》时关注的两条线展开,方法名以 Spring Framework 5.x 为背景。
从一个小例子找到入口
旧例子使用 XmlBeanFactory。它从 Spring 3.1 起就已弃用,可用 DefaultListableBeanFactory 搭配 XmlBeanDefinitionReader 表达相同的基本关系。XmlBeanFactory API
把下面的 beans.xml 放在应用 classpath 中:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="message" class="java.lang.String">
<constructor-arg value="hello"/>
</bean>
</beans>
在有 Spring Beans 和 Core 依赖的工程中,入口可以写成:
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.beans.factory.xml.XmlBeanDefinitionReader;
import org.springframework.core.io.ClassPathResource;
public class XmlBeanDemo {
public static void main(String[] args) {
DefaultListableBeanFactory factory = new DefaultListableBeanFactory();
XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(factory);
reader.loadBeanDefinitions(new ClassPathResource("beans.xml"));
System.out.println(factory.getBeanDefinitionCount());
System.out.println(factory.getBean("message", String.class));
factory.destroySingletons();
}
}
这是说明调用关系的最小示例。普通 BeanFactory 不会自动拥有 ApplicationContext 的所有扩展处理;例如,不能仅因为 classpath 有某个 BeanPostProcessor,就认为它已自动注册到这个 factory。
XML 如何变成 BeanDefinition
阅读 XmlBeanDefinitionReader 时,可以按下面的层次定位,而不必先记住每一个重载:
| 层次 | 关注对象 | 要回答的问题 |
|---|---|---|
| 资源 | Resource、EncodedResource | 配置来自哪里,是否指定字符集 |
| 输入 | InputStream、InputSource | 如何把资源交给解析器 |
| 文档 | DocumentLoader、DOM Document | XML 如何解析,校验在哪里进行 |
| 定义 | BeanDefinitionDocumentReader | 标签如何转换为容器元数据 |
| 注册 | BeanDefinitionRegistry | 定义最终放到哪里 |
EncodedResource 是资源与编码信息的包装,不能理解为“无论原文件是什么编码都自动修复”。XmlBeanDefinitionReader 在加载期间还维护正在处理的资源集合,用于识别循环加载;这与两个 Bean 互相依赖是不同问题。XML 校验也没有因为外层方法变短就消失,仍需继续跟到 doLoadDocument 等实现。5.3.39 XmlBeanDefinitionReader 源码
文档层再区分默认命名空间和自定义命名空间。默认 beans 标签中的 import、alias、bean 以及嵌套 beans 各有处理分支;自定义命名空间则交给对应的 NamespaceHandler。profile 和嵌套 beans 的默认配置还会影响当前解析上下文。遇到不熟悉的标签,先看 namespace,再找处理器,比把所有标签都归给 parseBeanDefinitionElement 更容易跟对路径。
getBean 不是直接 new
getBean 的主体会进入 AbstractBeanFactory.doGetBean。读这段代码时,先区分“命中已有对象”和“需要创建对象”:
- 处理别名和名称前缀,得到容器使用的 beanName。
- 尝试单例缓存;命中后仍要处理 FactoryBean 的语义。
- 本地没有相应定义时,按条件委托父工厂。
- 合并定义,检查依赖关系,再按 singleton、prototype 或其他 scope 获取对象。
- 最后根据请求的类型处理返回值。
普通 FactoryBean 名称通常取得其产品,带 & 前缀则用于获取工厂本身。BeanDefinition 里的父定义合并与父 BeanFactory 委托也不是同一种“父子关系”。读源码时,把这两对概念分开,后续异常更容易解释。AbstractBeanFactory 源码
从 createBean 进入真正的创建流程
createBean 先处理类和方法覆盖等准备工作,还会给实例化前处理器返回替代对象的机会;因此不是每次都必然执行完整 doCreateBean。
进入 doCreateBean 后,可以用下面这条主线阅读:
createBeanInstance
→ 合并定义后处理
→ 在符合条件时准备早期引用
→ populateBean
→ initializeBean
→ 注册需要的销毁回调
createBeanInstance 也不只有无参构造器:Supplier、工厂方法、构造器自动装配和默认实例化都有自己的分支。BeanWrapper 包装实例与属性访问能力;它不是业务 Bean 的另一个副本。方法注入可能使用子类化策略,但不能据此说“所有 Bean 都是 CGLIB 代理”。
单例、允许循环引用、对象正在创建等条件满足时,容器可加入早期引用工厂。这不是提前创建第二个业务实例,更不意味着所有循环依赖都能解决。构造器循环依赖、prototype 循环依赖等情况不能套用同一结论;面对循环依赖,先改进依赖设计,而不是默认打开容错开关。创建流程源码
属性填充之后,初始化仍有多个阶段
populateBean 负责属性填充及相关扩展点;initializeBean 则涉及 Aware 回调、初始化前处理器、初始化方法、初始化后处理器等。业务对象可能在后处理阶段被包装,因此最终暴露对象不一定与最初实例具有相同的引用。
@PostConstruct 与 InitializingBean、init-method 的关系,另见初始化回调与后处理器顺序。将这些步骤统称为“构造函数执行完”会漏掉依赖注入和扩展处理。
这两条源码链最有用的连接点是 BeanDefinition:XML 阶段回答“容器知道了哪些定义”,getBean 阶段回答“这些定义如何成为可用对象”。排查时先确定错误发生在哪一侧,再定位具体扩展点。