本篇将对定义在 XMl 文件中的 bean,从静态的的定义到变成可以使用的对象的过程,即 bean 的加载和获取的过程进行一个整体的了解,不去深究,点到为止,只求对 Spring IOC 的实现过程有一个整体的感知,具体实现细节留到后面用针对性的篇章进行讲解。
首先我们来引入一个 Spring 入门使用示例,假设我们现在定义了一个类 org.zhenchao.framework.MyBean ,我们希望利用 Spring 来管理类对象,这里我们利用 Spring 经典的 XMl 配置文件形式进行配置:
<?xml version="1.0" encoding="UTF-8"?>
<beansxmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<!-- bean的基本配置 -->
<beanname="myBean"class="org.zhenchao.framework.MyBean"/>
</beans>
</div>
我们将上面的配置文件命名为 spring-core.xml,则对象的最原始的获取和使用示例如下:
// 1. 定义资源
Resource resource = new ClassPathResource("spring-core.xml");
// 2. 利用XmlBeanFactory解析并注册bean定义
XmlBeanFactory beanFactory = new XmlBeanFactory(resource);
// 3. 从IOC容器加载获取bean
MyBean myBean = (MyBean) beanFactory.getBean("myBean");
// 4. 使用bean
myBean.sayHello();
</div>
上面 demo 虽然简单,但麻雀虽小,五脏俱全,完整的让 Spring 执行了一遍配置文件加载,并获取 bean 的过程。虽然从 Spring 3.1 开始 XmlBeanFactory 已经被置为 Deprecated ,但是 Spring 并没有定义出更加高级的基于 XML 加载 bean 的 BeanFactory,而是推荐采用更加原生的方式,即组合使用 DefaultListableBeanFactory 和 XmlBeanDefinitionReader 来完成上诉过程:
Resource resource = new ClassPathResource("spring-core.xml");
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(beanFactory);
reader.loadBeanDefinitions(resource);
MyBean myBean = (MyBean) beanFactory.getBean("myBean");
myBean.sayHello();
</div>
后面的分析你将会看到 XmlBeanFactory 实际上是对 DefaultListableBeanFactory 和 XmlBeanDefinitionReader 组合使用方式的封装,所以这里我们仍然将继续分析基于 XmlBeanFactory 加载 bean 的过程。
一. Bean的解析和注册

Bean的加载过程,主要是对配置文件的解析,并注册 bean 的过程,上图是加载过程的时序图,当我们 new XmlBeanFactory(resource) 的时候,已经完成将配置文件包装成了 Spring 定义的资源,并触发解析和注册。 new XmlBeanFactory(resource) 调用的是下面的构造方法:
publicXmlBeanFactory(Resource resource)throwsBeansException{
this(resource, null);
}
</div>
这个构造方法本质上还是继续调用了:
publicXmlBeanFactory(Resource resource, BeanFactory parentBeanFactory)throwsBeansException{
super(parentBeanFactory);
// 加载xml资源
this.reader.loadBeanDefinitions(resource);
}
</div>
在这个构造方法里面先是调用了父类构造函数,即 org.springframework.beans.factory.support.DefaultListableBeanFactory 类,这是一个非常核心的类,它包含了基本 IOC 容器所具有的重要功能,是一个 IOC 容器的基本实现。然后是调用了 this.reader.loadBeanDefinitions(resource) ,从这里开始加载配置文件。
Spring 在设计采用了许多程序设计的基本原则,比如迪米特法则、开闭原则,以及接口隔离原则等等,这样的设计为后续的扩展提供了灵活性,也增强了模块的复用性,这也是我看 Spring 源码的动力之一,希望通过阅读学习的过程来提升自己接口设计的能力。Spring 使用了专门的资源加载器对资源进行加载,这里的 reader 就是 org.springframework.beans.factory.xml.XmlBeanDefinitionReader 对象,专门用来加载基于 XML 文件配置的 bean。这里的加载过程为:
- 利用 EncodedResource 二次包装资源文件
- 获取资源输入流,并构造 InputSource 对象
- 获取 XML 文件的实体解析器和验证模式
- 加载 XML 文件,获取对应的 Document 对象
- 由 Document 对象解析并注册 bean
1.利用 EncodedResource 二次包装资源文件
采用 org.springframework.core.io.support.EncodedResource 对resource 进行二次封装.
2.获取资源输入流,并构造 InputSource 对象
对资源进行编码封装之后,开始真正进入 this.loadBeanDefinitions(new EncodedResource(resource)) 的过程,该方法源码如下:
publicintloadBeanDefinitions(EncodedResource encodedResource)throwsBeanDefinitionStoreException{
Assert.notNull(encodedResource, "EncodedResource must not be null");
if (logger.isInfoEnabled()) {
logger.info("Loading XML bean definitions from " + encodedResource.getResource());
}
// 标记正在加载的资源,防止循环引用
Set<EncodedResource> currentResources = this.resourcesCurrentlyBeingLoaded.get();
if (currentResources == null) {
currentResources = new HashSet<EncodedResource>(4);
this.resourcesCurrentlyBeingLoaded.set(currentResources);
}
if (!currentResources.add(encodedResource)) {
throw new BeanDefinitionStoreException("Detected cyclic loading of " + encodedResource + " - check your import definitions!");
}
try {
// 获取资源的输入流
InputStream inputStream = encodedResource.getResource().getInputStream();
try {
// 构造InputSource对象
InputSource inputSource = new InputSource(inputStream);
if (encodedResource.getEncoding() != null) {
inputSource.setEncoding(encodedResource.getEncoding());
}
// 真正开始从XML文件中加载Bean定义
return this.doLoadBeanDefinitions(inputSource, encodedResource.getResource());
} finally {
inputStream.close();
}
} catch (IOException ex) {
throw new BeanDefinitionStoreException("IOException parsing XML document from " + encodedResource.getResource(), ex);
} finally {
currentResources.remove(encodedResource);
if (currentResources.isEmpty()) {
this.resourcesCurrentlyBeingLoaded.remove();
}
}
}
</div>
需要知晓的是 org.xml.sax.InputSource 不是 Spring 中定义的类,这个类来自 jdk,是 java 对 XML 实体提供的原生支持。这个方法主要还是做了一些准备工作,按照 Spring 方法的命名相关,真正干活的方法一般都是以 “do” 开头的,这里的 this.doLoadBeanDefinitions(inputSource, encodedResource.getResource()) 就是真正开始加载 XMl 的入口,该方法源码如下:
protectedintdoLoadBeanDefinitions(InputSource inputSource, Resource resource)throwsBeanDefinitionStoreException{
try {
// 1. 加载xml文件,获取到对应的Document(包含获取xml文件的实体解析器和验证模式)
Document doc = this.doLoadDocument(inputSource, resource);
// 2. 解析Document对象,并注册bean
return this.registerBeanDefinitions(doc, resource);
} catch (BeanDefinitionStoreException ex) {
// 这里是连环catch,省略
}
}
</div>
方面里面的逻辑还是很清晰的,第一步获取 org.w3c.dom.Document 对象,第二步由该对象解析得到 BeanDefinition 对象,并注册到 IOC 容器中。
3.获取 XML 文件的实体解析器和验证模式
this.doLoadDocument(inputSource, resource) 包含了获取实体解析器、验证模式,以及 Document 对象的逻辑,源码如下:
protectedDocumentdoLoadDocument(InputSource inputSource, Resource resource)throwsException{
return this.documentLoader.loadDocument(
inputSource,
this.getEntityResolver(), // 获取实体解析器
this.errorHandler,

