首页 > 代码库 > Spring 源码探究 - BeanDefinitionReader
Spring 源码探究 - BeanDefinitionReader
基本概念
BeanDefinitionReader ,该类的作用是读取 Spring 的配置文件的内容,并将其转换成 Ioc 容器内部的数据结构,而容器的数据结构就是 BeanDefinition。
该类的功能概括的讲可分为两步:
负责 BeanDefinition 的资源定位
负责 BeanDefinition 的载入
下面来看它的接口定义:
public interface BeanDefinitionReader {
BeanDefinitionRegistry getRegistry();
ResourceLoader getResourceLoader();
ClassLoader getBeanClassLoader();
BeanNameGenerator getBeanNameGenerator();
// 关键 -> 负责 BeanDefinition 的 载入,读取 Resrouce 的内容,通过 BeanDefinitionDocumentReader 将其解析成 BeanDefinition
int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException;
int loadBeanDefinitions(Resource... resources) throws BeanDefinitionStoreException;
// 关键 -> 负责 BeanDefinition 的资源定位,根据路径利用 ResourceLoder 取得 Resrouce 实例
int loadBeanDefinitions(String location) throws BeanDefinitionStoreException;
int loadBeanDefinitions(String... locations) throws BeanDefinitionStoreException;
}
再来看它的继承关系:
源码分析
首先来看 Spring Ioc 容器从启动开始到调用 BeanDefinitionReader 过程。
注意:由于这里默认采用 XML 文件作为 Spring 的配置文件,所以调用 XmlBeanDefinitionReader 来处理。
下面来看 XmlBeanDefinitionReader 的工作流程:
public int loadBeanDefinitions(String location) throws BeanDefinitionStoreException {
return loadBeanDefinitions(location, null);
}
public int loadBeanDefinitions(String location, Set<Resource> actualResources) throws BeanDefinitionStoreException {
// 步骤一:取得资源加载器
ResourceLoader resourceLoader = getResourceLoader();
// 步骤二:校验资源加载器(判断是否为空)
if (resourceLoader == null) {
// 抛出异常...
}
// 步骤二:校验资源加载器(判断是否实现 ResourcePatternResolver 接口)
if (resourceLoader instanceof ResourcePatternResolver) {
// Resource pattern matching available.
try {
// 步骤三:关键 -> BeanDefinition 的资源定位
Resource[] resources = ((ResourcePatternResolver) resourceLoader).getResources(location);
// 步骤四:关键 -> BeanDefinition 的载入
int loadCount = loadBeanDefinitions(resources);
if (actualResources != null) {
for (Resource resource : resources) {
actualResources.add(resource);
}
}
// 日志输出...
return loadCount;
}catch (IOException ex) {
// 抛出异常...
}
}else {
Resource resource = resourceLoader.getResource(location);
int loadCount = loadBeanDefinitions(resource);
if (actualResources != null) {
actualResources.add(resource);
}
// 日志输出...
return loadCount;
}
}
观察代码,BeanDefinitionReader 的工作流程可分为四个步骤:
取得 ResourceLoader
校验 ResourceLoader
BeanDefinition 的资源定位
BeanDefinition 的载入
通过代码再次验证了 BeanDefinitionReader 的职能,接下来详细探究下后两个步骤。
1.BeanDefinition 的资源定位
资源定位,是获取 Resource 实例对象的过程。BeanDefinitionReader 本身不具备该功能,而是通过 ResourceLoader 获取 Resource 实例。
上面提到 BeanDefinitionReader 会去判断 ResourceLoader 是否实现 ResourcePatternResolver 接口,实质就是判断该 ResourceLoader 是否支持同时访问多个资源。
- 若支持同时访问多个资源,则返回 Resrouce 数组。
Resource[] resources = ((ResourcePatternResolver) resourceLoader).getResources(location);
首先来来看 ResourcePatternResolver 接口的继承关系:
在这里 XmlBeanDefinitionReader 会默认调用父类 AbstractApplicationContext 的 getResources 方法:
public Resource[] getResources(String locationPattern) throws IOException {
// 此时 resourcePatternResolver 为 ServletContextResourcePatternResolver
return this.resourcePatternResolver.getResources(locationPattern);
}
然后 ServletContextResourcePatternResolver 会默认调用父类 PathMatchingResourcePatternResolver 的 getResources 方法。
PathMatchingResourcePatternResolver 在介绍 ResourceLoader 已探究过这里不细述。
整个调用流程如下图所示:
- 若不支持同时访问多个资源,则返回单个 Resource 对象:
Resource resource = resourceLoader.getResource(location);
整个资源定位的过程相对简单,与 Resource 、ResourceLoader 这两个类息息相关。Spring 通过将不同类型的资源抽象为 Resrouce 接口,再通过 ResourceLoader 获取其实例,这样 Spring 只和 Resource 接口耦合,不用再去关心底层采用何种类型资源访问。
2.BeanDefinition 的载入
载入,指的是读取 Resource 内容并加载为 Doucument 的过程。
下面来看具体的流程:
- 取得 Resource ,并添加编码信息,将其转成 EncodedResource
public int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException {
// 将 resource 包裹在 EncodedResource 中,其实例化过程会为 Resource 添加编码信息,这里默认编码信息为 null
return loadBeanDefinitions(new EncodedResource(resource));
}
- 通过获取 Resource 的输入流,并将其添加进 InputSource ,准备开始读取内容
public int loadBeanDefinitions(EncodedResource encodedResource) throws BeanDefinitionStoreException {
Assert.notNull(encodedResource, "EncodedResource must not be null");
// 日志输出...
// 取得已经加载的资源
Set<EncodedResource> currentResources = this.resourcesCurrentlyBeingLoaded.get();
// 如果不存在已加载的资源,新建一个 HashSet 存放之后加载的资源
if (currentResources == null) {
currentResources = new HashSet<EncodedResource>(4);
this.resourcesCurrentlyBeingLoaded.set(currentResources);
}
// 将当前的资源添加进 HashSet
if (!currentResources.add(encodedResource)) {
// 如果当前资源已经存在,则抛出异常...
}
try {
// 所有类型的 Resource 都可以被转换成流操作
InputStream inputStream = encodedResource.getResource().getInputStream();
try {
// 将流添加进 InputSource 以便为 SAX 解析做准备
InputSource inputSource = new InputSource(inputStream);
// 设置编码
if (encodedResource.getEncoding() != null) {
inputSource.setEncoding(encodedResource.getEncoding());
}
// 关键 -> 开始真正的 BeanDefinitions 载入过程
return doLoadBeanDefinitions(inputSource, encodedResource.getResource());
}finally {
inputStream.close();
}
}catch (IOException ex) {
// 抛出异常...
}finally {
// 最后要异常当前已加载的资源
currentResources.remove(encodedResource);
// 资源集若为空,一起移除
if (currentResources.isEmpty()) {
this.resourcesCurrentlyBeingLoaded.remove();
}
}
}
- 由于这里 Spring 采用了 XML 配置文件,所以利用 SAX 解析取得 Document 对象
protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource) throws BeanDefinitionStoreException {
try {
// 利用 SAX 解析得到 XML 文件的 Document 对象
Document doc = doLoadDocument(inputSource, resource);
// 关键 -> 解析并注册 BeanDefinitions
return registerBeanDefinitions(doc, resource);
}catch (BeanDefinitionStoreException ex) {
// 抛出异常...
}catch (SAXParseException ex) {
// 抛出异常...
}catch (SAXException ex) {
// 抛出异常...
}catch (ParserConfigurationException ex) {
// 抛出异常...
}catch (IOException ex) {
// 抛出异常...
}catch (Throwable ex) {
// 抛出异常...
}
}
最后简单来概述下整个载入过程:
取得 Resource ,并添加编码信息,将其转成 EncodedResource。
取得 Resource 的输入流,添加进 InputSource,并 SAX 解析做准备。
取得 InputSource ,利用 SAX 解析取得 Document 对象,完成载入。
3.BeanDefinition 的解析和注册
上一步中 XmlBeanDefinitionReader 已经取得了 XML 的 Document 对象,完成了载入过程。接下要进行的就是 BeanDefinition 的解析和注册过程。该功能具体实现功能交由 BeanDefinitionDocumentReader 来完成。
public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {
// 利用 documentReader 对配置文件的内容进行解析
BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();
// 取得已经注册 BeanDefinition
int countBefore = getRegistry().getBeanDefinitionCount();
// 关键 -> 注册 BeanDefinition (包含解析过程)
documentReader.registerBeanDefinitions(doc, createReaderContext(resource));
return getRegistry().getBeanDefinitionCount() - countBefore;
}
总结
分析完 BeanDefinitionReader 具体工作流程,最后通过一个图简单阐述:
Spring 源码探究 - BeanDefinitionReader