XmlBeanFactory
XmlBeanFactory 是 Spring Framework 中用于读取 XML 配置文件并初始化 Bean 的一种方式。在源码学习的时候可以跟踪 XmlBeanFactory 的运行流程,因为它继承了 DefaultListableBeanFactory 这个高级工厂,使用方法如下:
public class XmlBeanFactoryTest {
@Test
public void testXmlBeanFactory() {
// 指定配置文件,创建 Spring 工厂
BeanFactory factory = new XmlBeanFactory(new ClassPathResource("/applicationContext.xml"));
System.out.println(factory.getBean("user"));
}
}
它的初始化过程可以简单概括如下:
- 加载配置文件:获取配置文件的输入流,读取配置文件时使用 ClassPathResource 类,读取类路径下相关资源文件的内容(applicationContext.xml)。
- DOM 解析:读取 XML 的配置内容,使用 XmlBeanDefinitionReader 类来实现 XML 的 DOM 解析(底层基于 SAX 实现)。
- Bean的创建与注册:通过解析出来的 DOM 对象来进一步进行 Bean 的解析,将 XML 中的一个个的
<bean>
标签封装为 Java 的对象 BeanDefinition,之后以 bean 的 id 为键,BeanDefinition 为值,注册到工厂(DefaultListableBeanFactory)的 beanDefinitionMap 中。
通过查看源码可以看到,这个类其实非常的简单,只是在本类中维护了一个 XmlBeanDefinitionReader,这个类负责具体的 XML 解析,其他所有的核心工厂方法都是来自父类的 DefaultListableBeanFactory,而且这个红色框住的内容也显示,这个 XmlBeanFactory 类已经废弃了,可以直接搭配 DefaultListableBeanFactory 和 XmlBeanDefinitionReader 进行使用即可,使用方法如下:
public class XmlBeanFactoryTest {
@Test
public void testDefaultListableBeanFactory() {
// 创建工厂
DefaultListableBeanFactory factory = new DefaultListableBeanFactory();
// 创建 XML 阅读器,并指定关联的工厂
XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(factory);
// 开始解析加载 Bean
reader.loadBeanDefinitions(new ClassPathResource("applicationContext.xml"));
System.out.println(factory.getBean("user"));
}
}
XmlBeanDefinitionReader 解析
在 Spring 框架中,XML 文件的解析由 org.springframework.beans.factory.xml.XmlBeanDefinitionReader
类负责。这个类是 Spring 中负责读取 XML 配置文件并将其转换为相应的 BeanDefinition 对象的关键组件之一。
通过观察上面的代码可以发现,XmlBeanDefinitionReader 构造的时候就需要传入一个工厂作为注册器,之后解析 XMl 创建的所有的 BeanDefinition 都要注册到里面。而开始解析的过程是从 reader.loadBeanDefinitions(new ClassPathResource("applicationContext.xml"));
这句开始的。
接下来我们来大概看一下源码:
Spring 的源码非常的麻烦,很多方法的重载(do 开头的方法才是真正干活的),类也是一层套一层,看起来非常的乱…
包装资源 EncodedResource
XmlBeanDefinitionReader#loadBeanDefinitions
@Override
public int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException {
return loadBeanDefinitions(new EncodedResource(resource));
}
public class EncodedResource implements InputStreamSource {
private final Resource resource;
@Nullable
private final String encoding;
@Nullable
private final Charset charset;
public EncodedResource(Resource resource) {
this(resource, (String)null, (Charset)null);
}
...
这就是一个简单的重载,把配置文件的资源使用 EncodedResource 包装一下,但是可以发现,其实和没包装也差不多,包装后 encoding 和 charset 也都还是空的。
准备解析 loadBeanDefinitions
XmlBeanDefinitionReader#loadBeanDefinitions
public int loadBeanDefinitions(EncodedResource encodedResource) throws BeanDefinitionStoreException {
// ↓ 直接省略一堆校验逻辑...
Assert.notNull(encodedResource, "EncodedResource must not be null");
Set<EncodedResource> currentResources = this.resourcesCurrentlyBeingLoaded.get();
if (currentResources == null) {
currentResources = new HashSet<>(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 就是 SAX 提供的,也就印证了 Spring XML 解析是由 SAX 处理的!
InputSource inputSource = new InputSource(inputStream);
if (encodedResource.getEncoding() != null) {
inputSource.setEncoding(encodedResource.getEncoding());
}
// 这里又转了一层,开始加载 BeanDefinition
return 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();
}
}
}
这里先是做了一大堆的校验,然后从上一步包装的 EncodedResource 获取真正的输入流,之后构建 SAX 提供的 InputSource 对象,接下来开始真正的解析工作…
开始解析 doLoadBeanDefinitions
XmlBeanDefinitionReader#doLoadBeanDefinitions
protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource) throws BeanDefinitionStoreException {
try {
// 使用 SAX 进行 DOM 解析
Document doc = doLoadDocument(inputSource, resource);
// 处理 DOM 解析的结果,创建并注册 BeanDefinition
int count = registerBeanDefinitions(doc, resource);
return count;
} catch (BeanDefinitionStoreException ex) {
throw ex;