上一章讲到refreshBeanFactory做了两件事情,一件是创建容器,一件是加载BeanDefinition,即loadBeanDefinitions()。加载BeanDefinition包含两个过程。本章讲第一点。

  1. 加载bean配置文件
  2. 解析bean配置文件

bean配置文件整体加载过程

加载bean配置文件的过程可以分为三个:

  1. 初始化XmlBeanDefinitionReader
  2. 解析资源文件路径
  3. 加载文件内容

loadBeanDefinitions()为抽象方法,由AbstractXmlApplicationContext实现。具体代码如下:

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
protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException, IOException {
// 创建一个Bean读取器,用于读取bean的配置资源,即我们在之前提到的BeanDefinitionReader
XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);

beanDefinitionReader.setEnvironment(this.getEnvironment());
// 设置spring资源加载器,其本身继承了DefaultResourceLoader,也是一个ResourceLoader
beanDefinitionReader.setResourceLoader(this);
// 设置xml解析器
beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this));

// 初始化bean读取器的上下文,主要在这里设置了是否校验xml的开关
initBeanDefinitionReader(beanDefinitionReader);
// 真正实现加载BeanDefinition
loadBeanDefinitions(beanDefinitionReader);
}

protected void loadBeanDefinitions(XmlBeanDefinitionReader reader) throws BeansException, IOException {
Resource[] configResources = getConfigResources();
if (configResources != null) {
reader.loadBeanDefinitions(configResources);
}
String[] configLocations = getConfigLocations();
if (configLocations != null) {
reader.loadBeanDefinitions(configLocations);
}
}

真正实现加载BeanDefinition是由loadBeanDefinitions()来处理,这里getConfigResources()默认返回null,所以会走到第二个reader.loadBeanDefinitions()。第二个loadBeanDefinitions()在最后也会将configLocations转换成Resource对象,然后会调用到一个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
// 第二个loadBeanDefinitions(),多次调用重载方法后的源码
public int loadBeanDefinitions(String location, @Nullable Set<Resource> actualResources) throws BeanDefinitionStoreException {
// 获取初始化时,设置的resourceLoader
ResourceLoader resourceLoader = getResourceLoader();
if (resourceLoader == null) {
throw new BeanDefinitionStoreException(
"Cannot import bean definitions from location [" + location + "]: no ResourceLoader available");
}

if (resourceLoader instanceof ResourcePatternResolver) {
try {
// 将指定位置的bean配置信息解析为spring所封装的Resource对象
Resource[] resources = ((ResourcePatternResolver) resourceLoader).getResources(location);
// 使用委派调用XmlBeanDefinitionReader.loadBeanDefinitions()。也就是上面第一个loadBeanDefinitions()
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 {
// 加载指定单个的Resource对象
Resource resource = resourceLoader.getResource(location);
// 使用委派调用XmlBeanDefinitionReader.loadBeanDefinitions()
int loadCount = loadBeanDefinitions(resource);
if (actualResources != null) {
actualResources.add(resource);
}
if (logger.isDebugEnabled()) {
logger.debug("Loaded " + loadCount + " bean definitions from location [" + location + "]");
}
return loadCount;
}
}

小结一下。经过一系列的重载、委托,最后都会走到XmlBeanDefinitionReader.loadBeanDefinitions(Resource)。这也就是真正的加载方法,会在第三小节说明。而走到这个方法,所需要的Resource对象,则是通过resourceLoader.getResources()而来的。

解析资源文件路径

这里需要重提的是,在初始化XmlBeanDefinitionReader的时候,设置resourceLoader,传入的this,也就是ClassPathXmlApplicationContext对象。这里调用resourceLoader.getResources()则是父类(DefaultResourceLoader)所实现的方法。

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
public Resource getResource(String location) {
Assert.notNull(location, "Location must not be null");

for (ProtocolResolver protocolResolver : this.protocolResolvers) {
Resource resource = protocolResolver.resolve(location, this);
if (resource != null) {
return resource;
}
}

if (location.startsWith("/")) {
// 如果是文件路径,走默认处理
return getResourceByPath(location);
} else if (location.startsWith(CLASSPATH_URL_PREFIX)) {
return new ClassPathResource(location.substring(CLASSPATH_URL_PREFIX.length()), getClassLoader());
} else {
try {
// 如果是URL路径,特殊处理
URL url = new URL(location);
return (ResourceUtils.isFileURL(url) ? new FileUrlResource(url) : new UrlResource(url));
} catch (MalformedURLException ex) {
// 既不是文件路径、也不是url形式路径
return getResourceByPath(location);
}
}
}

protected Resource getResourceByPath(String path) {
return new ClassPathContextResource(path, getClassLoader());
}

这里主要区分location是不是为url方法传入。如果是直接new一个UrlResource对象,否则会走到getResourceByPath(),而该方法为protected,允许子类重写,例如FileSystemXmlApplicationContext就重写了该方法,处理文件路径的location。如下

1
2
3
4
5
6
7
@Override
protected Resource getResourceByPath(String path) {
if (path.startsWith("/")) {
path = path.substring(1);
}
return new FileSystemResource(path);
}

加载文件内容

回到第一节留下的坑,XmlBeanDefinitionReader.loadBeanDefinitions(Resource)。

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
public int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException {
return 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.getResource());
}
// 非空处理
...
try {
InputStream inputStream = encodedResource.getResource().getInputStream();
try {
// 开始解析,将资源文件转成sax所需的InputSource对象
InputSource inputSource = new InputSource(inputStream);
if (encodedResource.getEncoding() != null) {
inputSource.setEncoding(encodedResource.getEncoding());
}
// 真实读取xml内容过程
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();
}
}
}

protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource)
throws BeanDefinitionStoreException {
try {
// 将其转成Document对象,到这里就慢慢的像我们自己写的代码了
Document doc = doLoadDocument(inputSource, resource);
// 继续将Document往下传,完成解析
return registerBeanDefinitions(doc, resource);
} catch ....{
....
}
}

protected Document doLoadDocument(InputSource inputSource, Resource resource) throws Exception {
return this.documentLoader.loadDocument(inputSource, getEntityResolver(), this.errorHandler,
getValidationModeForResource(resource), isNamespaceAware());
}

到这里为止,又分为两条路,一条就是加载Document对象,一条就是继续真正解析。前者是DefaultDocumentLoader调用jdk的JAXP将xml转换为所需要的Document对象,写过xml的应该很了解,这一块代码就不深入分析了。且看后者:registerBeanDefinitions()

1
2
3
4
5
6
7
8
9
public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {
// 使用委派,获得解析xml的对象
BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();
int countBefore = getRegistry().getBeanDefinitionCount();
// 解析过程的入口,具体实现由DefaultBeanDefinitionDocumentReader实现。
documentReader.registerBeanDefinitions(doc, createReaderContext(resource));
// 统计解析bean数量
return getRegistry().getBeanDefinitionCount() - countBefore;
}

总结一下,从最开始的loadBeanDefinitions()方法说起

  1. 初始化XmlBeanDefinitionReader。
  2. 由XmlBeanDefinitionReader调用loadBeanDefinitions(),该方法分两步:解析文件路径、加载文件内容。
  3. 解析文件路径,分别处理传入的location方式,封装成所需的Resource对象。
  4. 加载文件内容,又分为两步。通过jdk的JAXP将xml转换成文档对象、将该文档对象进行解析。

第4点中的第二个步骤:registerBeanDefinitions()下章单独描述。