Spring 加载BeanDefinitionReader

1. 基本概念

BeanDefinitionReader ,该接口的作用就是加载 Bean。

在 Spring 中,Bean 一般来说都在配置文件中定义。而在配置的路径由在 web.xml 中定义。所以加载 Bean 的步骤大致就是:

加载资源,通过配置文件的路径(Location)加载配置文件(Resource)

解析资源,通过解析配置文件的内容得到 Bean。

下面来看它的接口定义:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public interface BeanDefinitionReader {


BeanDefinitionRegistry getRegistry();

@Nullable
ResourceLoader getResourceLoader();

@Nullable
ClassLoader getBeanClassLoader();

BeanNameGenerator getBeanNameGenerator();

// 通过 Resource 加载 Bean
int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException;
// 通过 Resource 加载 Bean
int loadBeanDefinitions(Resource... resources) throws BeanDefinitionStoreException;
// 通过 location 加载 Bean
int loadBeanDefinitions(String location) throws BeanDefinitionStoreException;

int loadBeanDefinitions(String... locations) throws BeanDefinitionStoreException;

}

具体的继承关系如下:

2. 流程分析

首先来看 Spring Ioc 容器从启动开始到调用 BeanDefinitionReader 加载 Bean 的过程如下:

注意:由于这里采用 XML 文件作为 Spring 的配置文件,所以默认调用 XmlBeanDefinitionReader 来处理

2.1.通过 BeanFactory 加载 Bean

在 Spring 容器(ApplicationContext)内部存在一个内部容器(BeanFactory)负责 Bean 的创建与管理。

在创建完 BeanFactory ,下一步就是要去加载 Bean。它由loadBeanDefinitions方法负责。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
//实现父类抽象的载入bean定义方法
@Override
protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException, IOException {
//创建XmlBeanDefinitionReader,即创建Bean读取器,并通过回调设置到容器中去,容器使用读取器读取Bean定义资源
// Create a new XmlBeanDefinitionReader for the given BeanFactory.
XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);

// 为Bean读取器设置Spring资源加载器,AbstractXmlApplicationContext的祖先父类AbstractApplicationContext继承DefaultResourceLoader,因此,容器本身也是一个资源加载器
// Configure the bean definition reader with this context's
// resource loading environment.
beanDefinitionReader.setEnvironment(this.getEnvironment());
beanDefinitionReader.setResourceLoader(this);

//为Bean读取设置SAX xml解析器
beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this));

//当Bean读取器读取Bean定义的xml资源文件时,启用xml的校验机制
// Allow a subclass to provide custom initialization of the reader,
// then proceed with actually loading the bean definitions.
initBeanDefinitionReader(beanDefinitionReader);
//Bean读取器真正实现加载的方法
loadBeanDefinitions(beanDefinitionReader);
}

观察代码,该方法的主要目的是创建了 BeanDefinitionReader ,并由它去加载 Bean。具体过程如下:

  • 创建 BeanDefinitionReader
  • 设置 BeanDefinitionReader 的相关属性
  • 初始化 BeanDefinitionReader
  • 通过 BeanDefinitionReader 加载 Bean

2.2.通过 BeanDefinitionReader 加载 Bean

在创建完 BeanDefinitionReader 后,则就需要通过它来 加载 Bean,过程如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//xml Bean读取器加载Bean定义资源
protected void loadBeanDefinitions(XmlBeanDefinitionReader reader) throws BeansException, IOException {
//获取Bean定义资源的定位
Resource[] configResources = getConfigResources();
if (configResources != null) {
//Xml bean 读取器调用其父类AbstractBeanDefinitionReader读取定位的Bean定义资源
reader.loadBeanDefinitions(configResources);
}
//如果子类中获取的Bean定义资源位为空,则获取FileSystemXmlApplicationContext
//构造方法中setConfigLocations方法设置的资源
String[] configLocations = getConfigLocations();
if (configLocations != null) {

//xml Bean读取器调用其父类AbstractBeanDefinitionReader读取定位的Bean定义资源
reader.loadBeanDefinitions(configLocations);
}
}

2.3.通过 Location 加载 Bean

加载的 Bean 的责任被交给了 BeanDefinitionReader ,下面来看看该类的 loadBeanDefinitions 方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
public int loadBeanDefinitions(String location, @Nullable Set<Resource> actualResources) throws BeanDefinitionStoreException {
//获取在IOC容器初始化过程中设置的资源加载器
ResourceLoader resourceLoader = getResourceLoader();
if (resourceLoader == null) {
throw new BeanDefinitionStoreException(
"Cannot import bean definitions from location [" + location + "]: no ResourceLoader available");
}

if (resourceLoader instanceof ResourcePatternResolver) {
// Resource pattern matching available.
try {
//将指定位置的Bean定义资源文件解析为Spring IOC容器封装的资源
//加载多个指定位置的Bean定义资源文件
Resource[] resources = ((ResourcePatternResolver) resourceLoader).getResources(location);
//委派调用其子类XmlBeanDefinitionReader的方法,实现加载功能
int loadCount = loadBeanDefinitions(resources);
if (actualResources != null) {
for (Resource resource : resources) {
actualResources.add(resource);
}
}
if (logger.isDebugEnabled()) {
logger.debug("Loaded " + loadCount + " bean definitions from location pattern [" + location + "]");
}
return loadCount;
}
catch (IOException ex) {
throw new BeanDefinitionStoreException(
"Could not resolve bean definition resource pattern [" + location + "]", ex);
}
}
else {
//将指定位置的bean定义资源文件解析为Spring IOC容器封装的资源,
//加载单个指定位置的Bean定义资源文件
// Can only load single resources by absolute URL.
Resource resource = resourceLoader.getResource(location);
//委派调用其子类XmlBeanDefinitionReader的方法,实现加载功能
int loadCount = loadBeanDefinitions(resource);
if (actualResources != null) {
actualResources.add(resource);
}
if (logger.isDebugEnabled()) {
logger.debug("Loaded " + loadCount + " bean definitions from location [" + location + "]");
}
return loadCount;
}
}

@Override
public int loadBeanDefinitions(String... locations) throws BeanDefinitionStoreException {
Assert.notNull(locations, "Location array must not be null");
int counter = 0;
for (String location : locations) {
//此处loadBeanDefinitions并没有实现,具体实现在各个子类中
//比如XmlBeanDefinitionReader中
counter += loadBeanDefinitions(location);
}
return counter;
}

观察代码,该方法的工作流程可分为四个步骤:

  • 取得资源加载器
  • 加载资源,通过 location 利用 ResourceLoader 加载
  • 通过 Resource 加载 Bean

2.4.通过 Resource 加载 Bean

在得到资源(Resource)后,该方法对它进行了封装,使其变成一个 EncodedResource 对象。

1
2
3
4
5
6
7
8
9
10
11
//XmlBeanDefinitionReader加载资源的入口方法
@Override
public int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException {

//将读入的XML资源进行特殊编码处理
return loadBeanDefinitions(new EncodedResource(resource));
}

public EncodedResource(Resource resource) {
this(resource, null, null);
}

2.5.通过 EncodedResource 加载 Bean

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
//这里是载入XML形式Bean定义资源文件方法
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<>(4);
this.resourcesCurrentlyBeingLoaded.set(currentResources);
}
if (!currentResources.add(encodedResource)) {
throw new BeanDefinitionStoreException(
"Detected cyclic loading of " + encodedResource + " - check your import definitions!");
}
try {
//将资源文件转为InputStream的IO流
InputStream inputStream = encodedResource.getResource().getInputStream();
try {
//从InputStream中得到XML的解析源
InputSource inputSource = new InputSource(inputStream);
if (encodedResource.getEncoding() != null) {
inputSource.setEncoding(encodedResource.getEncoding());
}
//这里是具体的读取过程
return doLoadBeanDefinitions(inputSource, encodedResource.getResource());
}
finally {
//关闭充Resource中得到的IO流
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();
}
}
}
  • 取得已加载的资源集合
  • 将当前资源添加到集合
  • 将资源转换成流
  • 通过流创建 InputSource ,并设置编码
  • 通过 InputSource 加载 Bean

2.6.通过 InputSource 加载 Bean

之所以把 Resource 转换成 InputSource ,就是为了 SAX 解析作准备。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
//从特定XML文件中实际载入Bean定义资源的方法
protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource)
throws BeanDefinitionStoreException {
try {
//将xml文件转为DOM对象,解析过程由documentLoader实现
Document doc = doLoadDocument(inputSource, resource);
//这里是启动对Bean定义解析的详细过程,该解析过程会用到Spring的Bean配置规则
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);
}
}

// 解析 XML 文件,并返回 Document 对象。
protected Document doLoadDocument(InputSource inputSource, Resource resource) throws Exception {
return this.documentLoader.loadDocument(inputSource, getEntityResolver(), this.errorHandler,
getValidationModeForResource(resource), isNamespaceAware());
}

3.Bean 注册

上一步中 XmlBeanDefinitionReader 已经取得了 XML 的 Document 对象,完成了资源的解析过程。

下一步就是 Bean 的注册过程。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//注册bean
//按照Spring的bean语义要求将bean定义资源解析并转换为容器内部数据接口
public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {
//得到BeanDefinitionDocumentReader来对xml格式的BeanDefinition解析
//先实例化一个BeanDefinitionDocumentReader,这个对象是通过BeanUtils.instantiateClass方法实例化出来的
//实际上BeanUtils.instantiateClass中是封装了Java的反射的一些方法,通过基本的Java反射来构造实例。
BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();
//获取容器中注册的Bean数量
int countBefore = getRegistry().getBeanDefinitionCount();
//解析过程入口,这里使用了委派模式,BeanDefinitionDocumentReader只是个接口,
//具体的解析实现过程有实现类DefaultBeanDefinitionDocumentReader完成
documentReader.registerBeanDefinitions(doc, createReaderContext(resource));
//统计解析的Bean数量
return getRegistry().getBeanDefinitionCount() - countBefore;
}

4. 总结

分析完 BeanDefinitionReader 具体工作流程,最后通过一个图简单阐述: