在测试的BeanFactoryTest类中首先调用了ClassPathResource的构造函数来构造Resource资源文件的实例对象,这样后续的资源处理就可以用Resource提供的各种服务来操作了,构造好Resource后就可以进行XmlBeanFactory的初始化了。
下面通过时序图可以看到整个逻辑处理顺序

配置文件的封装
ClassPathResource完成了什么功能呢?
在Java中,将不同来源的资源抽象成URL,通过注册不同的handler(RULStreamHandler)来处理不同来源的资源的读取逻辑,一般handler的类型使用不同前缀(协议,Protocol)来识别,如“file:”、“http:”、“jar:”等,然而URL没有默认定义相对ClassPath或ServletContext等资源的handler,虽然可以注册自己的URLStreamHandler来解析特定的URL前缀(协议),比如“classpath:”,然而这需要了解URL的实现机制,而且URL也没有提供一些基本的方法,如检查资源是否存在、检查当前资源是否可读等方法,因而Spring对其内部使用到的资源实现了自己的抽象结构:Resource接口来封装底层资源。

InputStreamSource只有一个方法定义:getInputStream(),该方法返回一个新的InputStream对象。

Resource接口抽象了所有Spring内部使用到的底层资源:File、URL、Classpath等。exists()
存在性isReadable()
可读性isOpen()
是否处于打开状态getURL()
转换为URL类型getURI()
转换为URI类型getFile()
转换为file类型contentLength()
获取资源长度lastModified()
获取上次修改日期createRelative()
基于当前资源创建一个相对资源getFilename()
获取不带路径信息的文件名称getDescription()
在错误处理中打印信息
不同来源的资源文件都有相应的Resource实现:文件(FileSystemResource)、Classpath资源(ClassPathResource)、URL资源(UrlResource)、InputStream资源(InputStreamResource)、Byte数组(ByteArrayResource)等。

在日常开发中资源文件的加载也是经常用到,可以直接使用Spring提供的类,比如在希望加载文件时可以使用以下代码:
Resource resource = new ClassPathResource("BeanFactoryTest.xml");
InputStream inputStream = resource.getInputStream();
得到inputStream后就可以按照以前的方式进行开发了,并且可以使用Resource及其子类为我们提供好的诸多特性。
Resource接口可以对所有资源文件进行统一处理。其实现是非常简单的,以getInputStream为例,ClassPathResource中的实现方式便是通过class或者classLoader提供的底层方法进行调用,而对于FileSystemResource的实现其实更简单,直接使用FileInputStream对文件进行实例化。
ClassPathResource.java
if(this.clazz!=null){
is = this.clazz.getResourceAsStream(this.path);
}else{
is = this.classLoader.getResourceAsStream(this.path);
}
FileSystemResource.java
public InputStream getInputStream() throws IOException{
return new FileInputStream(this.file);
}
通过Resource相关类完成对配置文件进行封装后,配置文件的读取工作就全权交给XmlBeanDefinitionReader来处理了。
XmlBeanFactory的初始化过程中有若干个初始化的构造方法,下面分析一下使用Resource实例作为参数的构造函数
XmlBeanFactory.java
public XmlBeanFactory(Resource resource) throws BeansException {
this(resource, null);
}
构造函数内部再次调用内部构造函数
上面函数中的代码this.reader.loadBeanDefinitions(resource);才是资源加载的真正实现,也是我们分析的重点之一。我们可以看到时序图中提到的XmlBeanDefinitionReader加载数据就是在这里完成的,但是XmlBeanDefinitionReader加载数据之前还有一个调用父类构造函数初始化的过程:super(parentBeanFactory);跟踪代码到父类AbstractAutowireCapableBeanFactory的构造函数中
AbstractAutowireCapableBeanFactory.java
public AbstractAutowireCapableBeanFactory() {
super();
ignoreDependencyInterface(BeanNameAware.class);
ignoreDependencyInterface(BeanFactoryAware.class);
ignoreDependencyInterface(BeanClassLoaderAware.class);
}
这里有必要提及一下ignoreDependencyInterface方法的主要功能是忽略给定接口的自动装配功能。
这样做的目的是:
举例来说,当A中有属性B,那么当Spring在获取A的Bean时如果其属性B还没有初始化,那么Spring会自动初始化B,这也是Spring中的一个重要特性,但是,在某些情况下,B不会被初始化,其中的一种情况就是B实现了BeanNameAware接口,Spring中是这样介绍的:自动装配是忽略给定的依赖接口,典型应用是通过其他方式解析Application上下文注册依赖,类似于BeanFactory通过BeanFactoryAware进行注入或者ApplicationContext通过ApplicationContextAware进行注入。
加载Bean
在XmlBeanFactory构造函数中调用了XmlBeanDefinitionReader类型的reader属性提供的方法this.reader.loadBeanDefinitions(resource),而这句代码则是整个资源加载的切入点,我们来看看这个方法的时序图

从上面的时序图梳理整个过程如下:
1、封装资源文件,当进入XmlBeanDefinitionReader后首先对参数Resource使用EncodeResource类进行封装
2、获取输入流,从Resource中获取对应的InputStream并构造InputSource
3、通过构造InputStream实例和Resource实例继续调用函数doLoadBeanDefinitions
loadBeanDefinitions函数具体实现过程:
public int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException {
return loadBeanDefinitions(new EncodedResource(resource));
}
那么EncodedResource的作用是什么呢?通过名称我们可以大致推断这个类的主要作用是用于对资源文件的编码进行处理的,其中主要逻辑体现在getReader()方法中,当设置了编码属性的时候,Spring会使用相应的编码作为输入流的编码。
public Reader getReader() throws IOException {
if (this.encoding != null) {
return new InputStreamReader(this.resource.getInputStream(), this.encoding);
}
else {
return new InputStreamReader(this.resource.getInputStream());
}
}
上面代码构造了一个有编码(encoding)的InputStreamReader。当构造好encodedResource对象后,再次转入了可复用的方法loadBeanDefinitions(new EncodedResource(resource))。
这个方法内部才是真正的数据准别阶段,也就是时序图所述的逻辑:
public int loadBeanDefinitions(EncodedResource encodedResource) throws BeanDefinitionStoreException {
Assert.notNull(encodedResource, "EncodedResource must not be null");
if (logger.isInfoEnabled()) {
logger.info("Loading XML bean definitions from " + encodedResource);
}
//通过属性来记录已经加载的资源
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 {
//从encodedResource中获取已经封装的Resource对象并再次从Resource中获取其中的InputStream
InputStream inputStream = encodedResource.getResource().getInputStream();
try {
//InputStream这个类并不来自于Spring,它的全路径是org.xml.sax.InputSource
InputSource inputSource = new InputSource(inputStream);
if (encodedResource.getEncoding() != null) {
inputSource.setEncoding(encodedResource.getEncoding());
}
//真正进入了逻辑核心部分
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();
}
}
}
再次整理一下数据准备阶段的逻辑,首先对传入的resource参数做封装,目的是考虑到Resource可能存在编码要求的情况,其次,通过SAX读取XML文件的方式来准备InputSource对象,最后将准备的数据通过参数传入真正的核心处理部分doLoadBeanDefinitions(inputSource,encodedResource.getResource())。
protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource)
throws BeanDefinitionStoreException {
try {
Document doc = doLoadDocument(inputSource, resource);
return registerBeanDefinitions(doc, resource);
}
catch (BeanDefinitionStoreException ex) {
throw ex;
}
catch (SAXParseException ex) {
throw new XmlBeanDefinitionStoreException(resource.getDescription(),
"Line " + ex.getLineNumber() + " in XML document from " + resource + " is invalid", ex);
}
catch (SAXException ex) {
throw new XmlBeanDefinitionStoreException(resource.getDescription(),
"XML document from " + resource + " is invalid", ex);
}
catch (ParserConfigurationException ex) {
throw new BeanDefinitionStoreException(resource.getDescription(),
"Parser configuration exception parsing XML from " + resource, ex);
}
catch (IOException ex) {
throw new BeanDefinitionStoreException(resource.getDescription(),
"IOException parsing XML document from " + resource, ex);
}
catch (Throwable ex) {
throw new BeanDefinitionStoreException(resource.getDescription(),
"Unexpected exception parsing XML document from " + resource, ex);
}
}
上面的代码中假如不考虑异常类的代码,其实只做了三件事,这三件事每一件必不可少。
1、获取对XML文件的验证模式
2、加载XML文件,并得到对应的Document
3、根据返回的Document注册bean信息
这三个步骤支撑着整个Spring容器部分的实现基础,尤其是第三部对配置文件的解析,逻辑非常复杂。