Spring 源码阅读 06:加载 BeanDefinition 的过程(准备阶段)
基于 Spring Framework v5.2.6.RELEASE
前情提要
- 在之前的 Spring 源码阅读:ApplicationContext 初始化 Spring 容器 一文中曾经介绍,
AbstractApplicationContext
中定义的refresh
方法十分重要,它里面几乎包含了 Spring 容器初始化的所有步骤。 - 在容器初始化的过程中,
BeanFactory
的初始化是非常重要的步骤,Spring 源码阅读:BeanFactory 初始化 探索了BeanFactory
初始化的流程。为了能在一篇文章完整介绍整个流程,其中有两个需要详细介绍的地方留了坑。一个是DefaultListableBeanFactory
构造方法中的流程探索,这个坑在 Spring 源码阅读:忽略感知接口对应成员变量的自动装配 已经填了,还有一个AbstractBeanDefinitionReader#loadBeanDefinitions
方法的坑没有填,这便是这篇要开始介绍的 BeanDefinitionBeanDefinition 加载的过程(填这个坑可能需要写好几篇)。
加载 BeanDefinition 的准备工作
这里先简单介绍一下 BeanDefinition。它是 Spring 框架中一个重要的接口,用来描述 Spring 容器中一个 Bean 实例的各种信息。在 Spring 实例化 Bean 之前,先要加载这些 Bean 的信息,这些信息就会保存在 BeanDefinition 中。
关于 BeanDefinition 的更详细的内容,值的单独写一篇。这里再挖个坑,填坑之后把链接贴在下方。
loadBeanDefinitions
重载方法
言归正传,先看loadBeanDefinitions
方法的代码:
@Override
public int loadBeanDefinitions(String... locations) throws BeanDefinitionStoreException {
Assert.notNull(locations, "Location array must not be null");
int count = 0;
for (String location : locations) {
count += loadBeanDefinitions(location);
}
return count;
}
这里对参数中传入的所有配置文件路径进行了遍历,对每一个路径调用重载方法按顺序分别解析。这里调用的重载方法定义如下:
@Override
public int loadBeanDefinitions(String location) throws BeanDefinitionStoreException {
return loadBeanDefinitions(location, null);
}
这里又调用了一个重载方法,并且我们留意到,增加了第二个参数,传入值为空。方法代码如下:
public int loadBeanDefinitions(String location, @Nullable Set<Resource> actualResources) throws BeanDefinitionStoreException {
ResourceLoader resourceLoader = getResourceLoader();
if (resourceLoader == null) {
throw new BeanDefinitionStoreException(
"Cannot load bean definitions from location [" + location + "]: no ResourceLoader available");
}
if (resourceLoader instanceof ResourcePatternResolver) {
// Resource pattern matching available.
try {
Resource[] resources = ((ResourcePatternResolver) resourceLoader).getResources(location);
int count = loadBeanDefinitions(resources);
if (actualResources != null) {
Collections.addAll(actualResources, resources);
}
if (logger.isTraceEnabled()) {
logger.trace("Loaded " + count + " bean definitions from location pattern [" + location + "]");
}
return count;
}
catch (IOException ex) {
throw new BeanDefinitionStoreException(
"Could not resolve bean definition resource pattern [" + location + "]", ex);
}
}
else {
// Can only load single resources by absolute URL.
Resource resource = resourceLoader.getResource(location);
int count = loadBeanDefinitions(resource);
if (actualResources != null) {
actualResources.add(resource);
}
if (logger.isTraceEnabled()) {
logger.trace("Loaded " + count + " bean definitions from location [" + location + "]");
}
return count;
}
}
方法提虽然长,但是我们稍微仔细看一下就会发现,其实这里核心就做了一件事,就是将方法参数中传递进来的配置文件路径,加载成了一个 Resource 对象,然后对这个 Resource 对象调用了另外一个以 Resource 类型为参数的loadBeanDefinitions
重载方法。
Resource 是 Spring 中一个非常重要的接口,它是 Spring 对底层资源访问的一个抽象,通过实现 Resource 接口,我们可以开发各种访问底层资源的能力。之前的一篇笔记 Spring 源码阅读:Resource 资源抽象 中曾经详细介绍过 Resource 接口,这里不再做过多介绍。
总之,这里把我们给配置文件路径转换成了一个资源对象,然后从这个资源对象中加载 BeanDefinition。
再看下一个重载方法:
public int loadBeanDefinitions(Resource... resources) throws BeanDefinitionStoreException {
Assert.notNull(resources, "Resource array must not be null");
int count = 0;
for (Resource resource : resources) {
count += loadBeanDefinitions(resource);
}
return count;
}
还是遍历了所有的 Resource 对象,对其调用另一个以单个 Resource 对象作为参数的重载方法。当在 IDE 中查找这个方法定义的时候就会发现,这个重载方法定义在BeanDefinitionReader
中,而上面我们看过的几个同名的重载方法都定义在AbstractBeanDefinitionReader
中。此时,我们需要找到这个方法的具体实现。
在之前的源码阅读(Spring 源码阅读:BeanFactory 初始化 )中可以知道,BeanDefinitionReader
对象创建时,它的类型是XmlBeanDefinitionReader
,因此我们去这里找方法的实现。
方法体如下:
@Override
public int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException {
return loadBeanDefinitions(new EncodedResource(resource));
}
这里将 Resource 资源封装成了 EncodedResource 后再一次调用了一个重载方法。为了不影响接着向下找 BeanDefinition 加载的逻辑,我们稍后再回过头来看 EncodedResource。继续找到下一个重载方法:
public int loadBeanDefinitions(EncodedResource encodedResource) throws BeanDefinitionStoreException {
Assert.notNull(encodedResource, "EncodedResource must not be null");
if (logger.isTraceEnabled()) {
logger.trace("Loading XML bean definitions from " + encodedResource);
}
Set<EncodedResource> currentResources = this.resourcesCurrentlyBeingLoaded.get();
if (!currentResources.add(encodedResource)) {
throw new BeanDefinitionStoreException(
"Detected cyclic loading of " + encodedResource + " - check your import definitions!");
}
try (InputStream inputStream = encodedResource.getResource().getInputStream()) {
InputSource inputSource = new InputSource(inputStream);
if (encodedResource.getEncoding() != null) {
inputSource.setEncoding(encodedResource.getEncoding());
}
return doLoadBeanDefinitions(inputSource, encodedResource.getResource());
}
catch (IOException ex) {
throw new BeanDefinitionStoreException(
"IOException parsing XML document from " + encodedResource.getResource(), ex);
}
finally {
currentResources.remove(encodedResource);
if (currentResources.isEmpty()) {
this.resourcesCurrentlyBeingLoaded.remove();
}
}
}
这个方法体重,可以看到一句关键的方法调用:
doLoadBeanDefinitions(inputSource, encodedResource.getResource());
根据 Spring 源码中的命名习惯,看到 doXXX()
的方法名,就知道真正的 BeanDefinition 加载逻辑终于找到了。在这个方法的参数列表中,一个是用 Resource 的输入流封装成的 InputSource,用于 XML 的读取,另一个是 Resource 对象本身。
protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource)
throws BeanDefinitionStoreException {
try {
Document doc = doLoadDocument(inputSource, resource);
int count = registerBeanDefinitions(doc, resource);
if (logger.isDebugEnabled()) {
logger.debug("Loaded " + count + " bean definitions from " + resource);
}
return count;
}
/* 略掉一些异常处理的代码 */
}
在doLoadBeanDefinitions
方法中,主要通过两个步骤来加载 BeanDefinition,第一步是对 XML 文件进行解析,将资源加载到一个 Document 对象中,第二步是调用registerBeanDefinitions
方法注册 BeanDefinition。
EncodedResource
到这儿,我们先看一下前面跳过的 EncodedResource 类型。虽然这个类的名字叫做 EncodedResource,但是它并不是 Resource 接口的实现类,不过它实现了 InputStreamSource 接口,也就是 Resource 接口继承的那个接口。
这里重点来分析一下,为什么在执行具体的加载之前,要把 Resource 封装成 EncodedResource,也就是它的作用是什么。先从前文的代码中调用的 EncodedResource 构造方法开始入手:
public EncodedResource(Resource resource) {
this(resource, null, null);
}
private EncodedResource(Resource resource, @Nullable String encoding, @Nullable Charset charset) {
super();
Assert.notNull(resource, "Resource must not be null");
this.resource = resource;
this.encoding = encoding;
this.charset = charset;
}
这里可以看到,除了封装了 Resource 之外,EncodedResource 还有两个成员变量,分别是编码和字符集,这两个会在通过字符流读取资源输入流的时候用到,不过我们这里给到的两个成员变量值都是null
。
后续
这篇先告一段落,从第一次调用 BeanDefinitionReader
的loadBeanDefinitions
方法一直找到doLoadBeanDefinitions
方法,了解了在真正执行 BeanDefinition 之前,都进行了哪些准备工作。下篇继续探索后面的逻辑。
转载自:https://juejin.cn/post/7132875366477070343