接下来分析BeanFactory bf = new XmlBeanFactory(new ClassPathResource("MyBean.xml"));
这行代码的实现细节。
通过XmlBeanFactory初始化时序图来看下上面代码具体是如何执行的。
首先调用ClassPathResource的构造函数将XML配置文件封装成一个Resource对象,然后根据Resource对象加载并注册BeanDefinition,最终得到了一个XmlBeanFactory对象。
封装配置文件
从上面我们了解到,读取配置文件是通过ClassPathResource来完成的。那么ClassPathResource具体是如何读取配置文件的呢?
Spring通过Resource接口提供了读取配置文件的方法,而ClassPathResource实现了Resource接口。从ClassPathResource层次结构图中可以看到:
Resource接口抽象了所有Spring内部使用的底层侧缘:File、URL、ClassPath等等。对于不同的资源文件都有对应的实现:FileSystemResource、ClassPathResource、UrlResource、InputStreamResource、ByteArrayResource等等。相关结构层次图如图所示。
new ClassPathResource("MyBean.xml")
到底做了哪些操作呢?
ClassPathResource.java代码片段
public ClassPathResource(String path) {
this(path, (ClassLoader) null);
}
public ClassPathResource(String path, @Nullable ClassLoader classLoader) {
Assert.notNull(path, "Path must not be null");
String pathToUse = StringUtils.cleanPath(path);
if (pathToUse.startsWith("/")) {
pathToUse = pathToUse.substring(1);
}
this.path = pathToUse;
this.classLoader = (classLoader != null ? classLoader : ClassUtils.getDefaultClassLoader());
}
从代码中可以看到,程序创建了一个ClassPathResource对象,并将ClassPathResource对象的path属性指向了处理后的路径名。
了解了如何将配置文件封装为Resource类型的实例后,我们继续了解XmlBeanFactory的初始化。
XmlBeanFactory的初始化
看下XmlBeanFactory的构造方法
XmlBeanFactory.java代码片段
private final XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(this);
public XmlBeanFactory(Resource resource) throws BeansException {
this(resource, null);
}
public XmlBeanFactory(Resource resource, BeanFactory parentBeanFactory) throws BeansException {
super(parentBeanFactory);
this.reader.loadBeanDefinitions(resource);
}
跟踪到父类AbstractAutowireCapableBeanFactory查看super(parentBeanFactory)
AbstractAutowireCapableBeanFactory代码片段
/**
* Create a new AbstractAutowireCapableBeanFactory.
*/
public AbstractAutowireCapableBeanFactory() {
super();
ignoreDependencyInterface(BeanNameAware.class);
ignoreDependencyInterface(BeanFactoryAware.class);
ignoreDependencyInterface(BeanClassLoaderAware.class);
}
ignoreDependencyInterface方法的主要功能是忽略给定接口的自动装配功能。
这时相信大家都能看出来,this.reader.loadBeanDefinitions(resource);
才是资源加载的真正实现。跟踪到XmlBeanDefinitionReader中看看具体实现。
XmlBeanDefinitionReader代码片段
/**
* Load bean definitions from the specified XML file.
*/
@Override
public int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException {
return loadBeanDefinitions(new EncodedResource(resource));
}
/**
* Load bean definitions from the specified XML file.
*/
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.getResource());
}
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 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实例的处理过程如下:
- 封装资源文件。
new EncodedResource(resource)
:使用EncodedResource对Resource实例进行封装。 - 获取InputStream 。
InputStream inputStream = encodedResource.getResource().getInputStream();
。 - 构造InputSource。
InputSource inputSource = new InputSource(inputStream);
。 - 调用
doLoadBeanDefinitions(inputSource, encodedResource.getResource());
继续处理资源。
到现在还没处理资源文件ㄒoㄒ~~
继续往下看看doLoadBeanDefinitions(inputSource, encodedResource.getResource());
到底做了哪些操作。
/**
* Actually load bean definitions from the specified XML file.
...
*/
protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource)
throws BeanDefinitionStoreException {
try {
Document doc = doLoadDocument(inputSource, resource);
return registerBeanDefinitions(doc, resource);
}
catch (BeanDefinitionStoreException ex) {
...
}
看到这个方法的第一行注释,我就知道终于要真正加载XML文件了^_^。
阅读方法后,我们知道,这个方法做了这么几件事:
- 加载Xml文件,并封装成Document 实例。
Document doc = doLoadDocument(inputSource, resource);
- 根据Document 实例注册BeanDefinitions。
return registerBeanDefinitions(doc, resource)
;
这几个步骤支撑着整个Spring容器的实现基础,尤其是根据Document 实例注册BeanDefinitions,非常复杂。以后会详细讲解。
Spring IoC容器的初始化过程可以总结为以下几个步骤:
- 获取Resouce实例。加载XML配置文件,封装成Resouce实例。这时Resouce实例中已经有了配置文件的路径等信息。
- 获取Document实例。通过Resource,读取XML配置文件,封装成Document实例。这时Document实例中已经有了配置文件中的标签。
- 获取并注册BeanDefinition实例。解析Document实例中的标签,最终获得BeanDefinition实例。这时BeanDefinition实例中已经有了bean的id、name、alias、class等信息。注册过程把BeanDefinition向IOC容器进行注册,相当于将bean的name作为key,BeanDefinition作为value,放入一个map中。
下一篇文章会先讲解如何获取Document实例。