抱歉,您的浏览器无法访问本站
本页面需要浏览器支持(启用)JavaScript
了解详情 >

SpringIOC-XML解析

流程分析

  1. 首先从入口ClassPathXmlApplicationContext初始化调用setConfigLocations方法将spring配置文件赋值给全局的configLocations
  2. 调用refresh方法进入SpringIOC的核心入口
  3. 调用prepareRefresh方法进行容器初始化做准备
  4. 调用obtainFreshBeanFactory方法创建BeanFactory是BeanDefinition注册的核心入口
  5. 调用refreshBeanFactory方法进行BeanFactory的刷新操作
  6. 接着来到AbstractRefreshableApplicationContext类进行refreshBeanFactory
  7. 首先检查BeanFactory是否存在如果存在就进行容器的注销以及关闭
  8. 创建BeanFactory,并进行BeanFactory的一些设置和初始化工作
  9. 调用loadBeanDefinitions加载spring配置文件主要负责把xml中的标签封装成BeanDefinition对象
  10. 来到AbstractXmlApplicationContext类调用loadBeanDefinitions
  11. 创建XmlBeanDefinitionReader对象主要负责xml的解析交给下面的类进行解析,是一个委托模式
  12. 进行XmlBeanDefinitionReader的一些设置以及初始化工作
  13. 继续调用loadBeanDefinitions方法,进行xml文件以及路径的解析
  14. 来到XmlBeanDefinitionReader负责具体xml的处理,调用loadBeanDefinitions并进行Resource的编码
  15. 继续调用loadBeanDefinitions将Resource转换为InputStream流对象调用doLoadBeanDefinitions
  16. 调用doLoadBeanDefinitions真正开始xml解析,调用doLoadDocument方法将xml转换为Dom对象
  17. 调用registerBeanDefinitions进行注册BeanDefinition,创建BeanDefinitionDocumentReader托这个类进行document的解析
  18. 来到DefaultBeanDefinitionDocumentReader进行具体Dom的解析
  19. 调用doRegisterBeanDefinitions进行DOM的解析,创建BeanDefinition的解析器,交给具体类执行,又是个委托模式
  20. 里面有两个埋点preProcessXml,以及postProcessXml,是空方法,用来进行对dom进行解析前后的修改扩展。
  21. 主要的方法是parseBeanDefinitions是标签具体解析过程,主要分为默认标签以及自定义标签的解析。

Spring的入口

找到源码的入口很重要,不然的话就一直在门外徘徊,不得而入,如果找到入口就会很容易

ClassPathXmlApplicationContext

大学就已经学习的创建spring容器的方式,一般的做法如下

1
2
3
4
5
6
public static void main(String[] args) {
//创建Spring的上下文
ApplicationContext context = new ClassPathXmlApplicationContext("Spring.xml");
//获取Bean对象
context.getBean("user");
}

这个就是我们今天入手spring的入口ClassPathXmlApplicationContext

找到核心入口

点击去ClassPathXmlApplicationContext我们进入他的构造方法

1
2
3
public ClassPathXmlApplicationContext(String configLocation) throws BeansException {
this(new String[] {configLocation}, true, null);
}

然后继续点击this

1
2
3
4
5
6
7
8
9
10
11
12
13
14

public ClassPathXmlApplicationContext(
String[] configLocations, boolean refresh, @Nullable ApplicationContext parent)
throws BeansException {

super(parent);
//创建解析器,解析configLocations 即spring的配文件
setConfigLocations(configLocations);

if (refresh) {
//入口IOC的核心入口
refresh();
}
}
setConfigLocations

创建解析器并将spring配置文件赋值给全局的configLocations数组

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
	/**
*
* 拿到需要解析的xml配置 例如 spring.xml
* 并将spring配置文件赋值给全局的configLocations数组
*/
public void setConfigLocations(@Nullable String... locations) {
if (locations != null) {
//校验locations不能为空
Assert.noNullElements(locations, "Config locations must not be null");
//将多个文件转换为数组
this.configLocations = new String[locations.length];
for (int i = 0; i < locations.length; i++) {
//模糊匹配,比较负复杂,并将spring配置文件赋值给全局的configLocations数组
this.configLocations[i] = resolvePath(locations[i]).trim();
}
} else {
this.configLocations = null;
}
}

refresh 方法

refresh 方法是spring的核心流程,完成了spring的初始化的所有工作

重要程度 ★★★★★

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
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111

/*
* 该方法是spring容器初始化的核心方法。是spring容器初始化的核心流程,是一个典型的父类模板设计模式的运用
* 根据不同的上下文对象,会掉到不同的上下文对象子类方法中
*
* 核心上下文子类有:
* ClassPathXmlApplicationContext
* FileSystemXmlApplicationContext
* AnnotationConfigApplicationContext
* EmbeddedWebApplicationContext(springboot)
* 必须读的 :重要程度 5
* */
@Override
public void refresh() throws BeansException, IllegalStateException {
synchronized (this.startupShutdownMonitor) {
//为容器初始化做准备,重要程度:0
// Prepare this context for refreshing.
prepareRefresh();

/**
重要程度:5
* 1、创建BeanFactory对象
* 2、xml解析
* 传统标签解析:bean、import等
* 自定义标签解析 如:<context:component-scan base-package="com.xiangxue.jack"/>
* 自定义标签解析流程:
* a、根据当前解析标签的头信息找到对应的namespaceUri
* b、加载spring所以jar中的spring.handlers文件。并建立映射关系
* c、根据namespaceUri从映射关系中找到对应的实现了NamespaceHandler接口的类
* d、调用类的init方法,init方法是注册了各种自定义标签的解析类
* e、根据namespaceUri找到对应的解析类,然后调用paser方法完成标签解析
*
* 3、把解析出来的xml标签封装成BeanDefinition对象
* */
// Tell the subclass to refresh the internal bean factory.
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

//给beanFactory设置一些属性值,可以不看
// Prepare the bean factory for use in this context.
prepareBeanFactory(beanFactory);

try {
// Allows post-processing of the bean factory in context subclasses.
postProcessBeanFactory(beanFactory);

/**
* BeanDefinitionRegistryPostProcessor
* BeanFactoryPostProcessor
* 很重要的一个接口 完成BeanDefinition注册后调用接口可以对注册的BeanDefinition进行修改
* 完成对这两个接口的调用
*/
// Invoke factory processors registered as beans in the context.
invokeBeanFactoryPostProcessors(beanFactory);

//把实现了BeanPostProcessor接口的类实例化,并且加入到BeanFactory中
// Register bean processors that intercept bean creation.
registerBeanPostProcessors(beanFactory);

//国际化,重要程度2
// Initialize message source for this context.
initMessageSource();

//初始化事件管理类
// Initialize event multicaster for this context.
initApplicationEventMulticaster();

//这个方法着重理解模板设计模式,因为在springboot中,这个方法是用来做内嵌tomcat启动的
// Initialize other special beans in specific context subclasses.
onRefresh();

//往事件管理类中注册事件类
// Check for listener beans and register them.
registerListeners();

/*
* 这个方法是spring中最重要的方法,没有之一
* 所以这个方法一定要理解要具体看
* 1、bean实例化过程
* 2、ioc
* 3、注解支持
* 4、BeanPostProcessor的执行
* 5、Aop的入口
*
* */
// Instantiate all remaining (non-lazy-init) singletons.
finishBeanFactoryInitialization(beanFactory);

// Last step: publish corresponding event.
finishRefresh();
} catch (BeansException ex) {
if (logger.isWarnEnabled()) {
logger.warn("Exception encountered during context initialization - " +
"cancelling refresh attempt: " + ex);
}

// Destroy already created singletons to avoid dangling resources.
destroyBeans();

// Reset 'active' flag.
cancelRefresh(ex);

// Propagate exception to caller.
throw ex;
} finally {
// Reset common introspection caches in Spring's core, since we
// might not ever need metadata for singleton beans anymore...
resetCommonCaches();
}
}
}

创建BeanFactory

obtainFreshBeanFactory 方法概述

​ 该方法会解析所有 Spring 配置文件(通常我们会放在 resources 目录下),将所有 Spring 配置文件中的 bean 定义封装成 BeanDefinition,加载到 BeanFactory 中。常见的,如果解析到<context:component-scan base-package=”” /> 注解时,会扫描 base-package 指定的目录,将该目录下使用指定注解(@Controller、@Service、@Component、@Repository)的 bean 定义也同样封装成 BeanDefinition,加载到 BeanFactory 中。
上面提到的 “加载到 BeanFactory 中” 的内容主要指的是添加到以下3个缓存:

  • beanDefinitionNames缓存:所有被加载到 BeanFactory 中的 bean 的 beanName 集合。
  • beanDefinitionMap缓存:所有被加载到 BeanFactory 中的 bean 的 beanName 和 BeanDefinition 映射。
  • aliasMap缓存:所有被加载到 BeanFactory 中的 bean 的 beanName 和别名映射。

obtainFreshBeanFactory 方法

该方法是spring解析xml创建BeanFactory的核心方法

重要程度 ★★★★★

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/**
* 获取bean工厂
*
* @return the fresh BeanFactory instance
* @see #refreshBeanFactory()
* @see #getBeanFactory()
*/
protected ConfigurableListableBeanFactory obtainFreshBeanFactory() {
//核心方法,必须读,重要程度:5
// 1.刷新 BeanFactory,由AbstractRefreshableApplicationContext实现
refreshBeanFactory();
// 2.拿到刷新后的 BeanFactory
return getBeanFactory();
}
refreshBeanFactory 方法

刷新BeanFactory

该方法完成了创建BeanFactory以及Spring配置文件的解析

重要程度 ★★★★★

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
/**
* 刷新BeanFactory
*/
@Override
protected final void refreshBeanFactory() throws BeansException {
//如果BeanFactory不为空,则清除BeanFactory和里面的实例
// 1.判断是否已经存在 BeanFactory,如果存在则先销毁、关闭该 BeanFactory
if (hasBeanFactory()) {
destroyBeans();
closeBeanFactory();
}
try {
//2. 创建DefaultListableBeanFactory

//BeanFactory 实例工厂 上下文和BeanFactory的区别
DefaultListableBeanFactory beanFactory = createBeanFactory();
beanFactory.setSerializationId(getId());

//3. 设置是否可以循环依赖 allowCircularReferences
//是否允许使用相同名称重新注册不同的bean实现.
customizeBeanFactory(beanFactory);

//4. 解析Spring配置文件,并把xml中的标签封装成BeanDefinition对象
loadBeanDefinitions(beanFactory);
synchronized (this.beanFactoryMonitor) {
this.beanFactory = beanFactory;
}
} catch (IOException ex) {
throw new ApplicationContextException("I/O error parsing bean definition source for " + getDisplayName(), ex);
}
}
createBeanFactory

完成了创建BeanFactory

1
2
3
protected DefaultListableBeanFactory createBeanFactory() {
return new DefaultListableBeanFactory(getInternalParentBeanFactory());
}
customizeBeanFactory

进行BeanFactory的一些配置

否允许bean在加载的工程中有两个相同的名称 默认不允许

是否允许循环依赖 默认是允许的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/**
* 否允许bean在加载的工程中有两个相同的名称 默认不允许
* 是否允许循环依赖 默认是允许的
*
*/
protected void customizeBeanFactory(DefaultListableBeanFactory beanFactory) {
//1. 是否允许bean在加载的工程中有两个相同的名称 默认不允许
if (this.allowBeanDefinitionOverriding != null) {
beanFactory.setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding);
}
//2. 是否允许循环依赖 默认是允许的
if (this.allowCircularReferences != null) {
beanFactory.setAllowCircularReferences(this.allowCircularReferences);
}
}

加载BeanDefinition

一下部分都是调用loadBeanDefinitions来处理Spring的配置文件

loadBeanDefinitions

解析Spring配置文件,并把xml中的标签封装成BeanDefinition对象

重要程度 ★★★★★

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
/**
* 解析Spring配置文件,并把xml中的标签封装成BeanDefinition对象
*/
@Override
protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException, IOException {
// 1.为指定BeanFactory创建XmlBeanDefinitionReader
//创建xml的解析器,这里是一个委托模式
// Create a new XmlBeanDefinitionReader for the given BeanFactory.
XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);

// Configure the bean definition reader with this context's
// resource loading environment.
// 2.使用此上下文的资源加载环境配置 XmlBeanDefinitionReader
beanDefinitionReader.setEnvironment(this.getEnvironment());

// resourceLoader赋值为XmlWebApplicationContext
//这里传一个this进去,因为ApplicationContext是实现了ResourceLoader接口的
beanDefinitionReader.setResourceLoader(this);
beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this));

// Allow a subclass to provide custom initialization of the reader,
// then proceed with actually loading the bean definitions.
initBeanDefinitionReader(beanDefinitionReader);
//3. 加载beanBeanDefinition定义 主要看这个方法 重要程度 5
loadBeanDefinitions(beanDefinitionReader);
}

loadBeanDefinitions

加载beanBeanDefinition ,并委托XmlBeanDefinitionReader解析Spring的配置文件

重要程度 ★★★★★

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/**
* 加载beanBeanDefinition 主要看这个方法
* 并委托XmlBeanDefinitionReader解析Spring的配置文件
*
*/
protected void loadBeanDefinitions(XmlBeanDefinitionReader reader) throws BeansException, IOException {
//1. 返回创建容器是设置的配置文件数组configResources
Resource[] configResources = getConfigResources();
if (configResources != null) {
//1.1 加载beanBeanDefinition 主要看这个方法 重要程度 5
//委托给reader来解析xml
reader.loadBeanDefinitions(configResources);
}
//2. 获取需要加载的xml配置文件
String[] configLocations = getConfigLocations();
if (configLocations != null) {
//3. 加载beanBeanDefinition 主要看这个方法 重要程度 5
//委托给reader来解析xml
reader.loadBeanDefinitions(configLocations);
}
}

reader.loadBeanDefinitions

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/**
* 加载BeanDefinition
* 遍历所有的资源文件交给下一级处理
*
* @param resources
* @return
* @throws BeanDefinitionStoreException
*/
@Override
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;
}

XmlBeanDefinitionReader.loadBeanDefinitions

进入loadBeanDefinitions最终通过XmlBeanDefinitionReader实现类来进行加载BeanDefinition,并对resource进行编码

1
2
3
4
5
6
7
8
9
10
11
/**
* Load bean definitions from the specified XML file.
* 根据resource资源BeanDefinition
* 并对resource进行编码
*
*/
@Override
public int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException {
//EncodedResource带编码的对Resource对象的封装
return loadBeanDefinitions(new EncodedResource(resource));
}

XmlBeanDefinitionReader.loadBeanDefinitions

加载BeanDefinition 将资源文件转换为流,并开始真正的处理配置文件

重要程度 ★★★★★

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
/**
* 加载BeanDefinition 将资源文件转换为流并开始真正的处理配置文件
*
*/
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 == 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 {
//获取Resource对象中的xml文件流对象
InputStream inputStream = encodedResource.getResource().getInputStream();
try {
//InputSource是jdk中的sax xml文件解析对象
InputSource inputSource = new InputSource(inputStream);
if (encodedResource.getEncoding() != null) {
inputSource.setEncoding(encodedResource.getEncoding());
}
//实际从指定的XML文件加载bean定义。
//主要看这个方法 ** 重要程度 5
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();
}
}
}

XmlBeanDefinitionReader.doLoadBeanDefinitions

真实的从xml中加载beanDefinition,通过解析Spring配置文件 并注册BeanDefinition

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
/**
* <p>
* 真实的从xml中加载beanDefinition
* 通过解析Spring配置文件 并注册BeanDefinition
*
*/
protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource)
throws BeanDefinitionStoreException {

try {
//把inputSource 封装成Document文件对象,这是jdk的API
Document doc = doLoadDocument(inputSource, resource);
//主要看这个方法,根据解析出来的document对象,拿到里面的标签元素封装成BeanDefinition
int count = registerBeanDefinitions(doc, resource);
if (logger.isDebugEnabled()) {
logger.debug("Loaded " + count + " bean definitions from " + resource);
}
return count;
} 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);
}
}
doLoadDocument
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
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
protected Document doLoadDocument(InputSource inputSource, Resource resource) throws Exception {
// 1.getValidationModeForResource(resource): 获取XML配置文件的验证模式
// 2.documentLoader.loadDocument: 加载XML文件,并得到对应的 Document
return this.documentLoader.loadDocument(inputSource, getEntityResolver(), this.errorHandler,
getValidationModeForResource(resource), isNamespaceAware());
}

protected int getValidationModeForResource(Resource resource) {
int validationModeToUse = getValidationMode();
// 1.1 如果手动指定了XML文件的验证模式则使用指定的验证模式
if (validationModeToUse != VALIDATION_AUTO) {
return validationModeToUse;
}
// 1.2 如果未指定则使用自动检测
int detectedMode = detectValidationMode(resource);
// 1.3 如果检测出的验证模式不为 VALIDATION_AUTO, 则返回检测出来的验证模式
if (detectedMode != VALIDATION_AUTO) {
return detectedMode;
}
// Hmm, we didn't get a clear indication... Let's assume XSD,
// since apparently no DTD declaration has been found up until
// detection stopped (before finding the document's root tag).
// 1.4 如果最终没找到验证模式,则使用 XSD
return VALIDATION_XSD;
}


protected int detectValidationMode(Resource resource) {
// 1.2.1 校验resource是否为open stream
if (resource.isOpen()) {
throw new BeanDefinitionStoreException(
"Passed-in Resource [" + resource + "] contains an open stream: " +
"cannot determine validation mode automatically. Either pass in a Resource " +
"that is able to create fresh streams, or explicitly specify the validationMode " +
"on your XmlBeanDefinitionReader instance.");
}

InputStream inputStream;
try {
// 1.2.2 校验resource是否可以打开InputStream
inputStream = resource.getInputStream();
} catch (IOException ex) {
throw new BeanDefinitionStoreException(
"Unable to determine validation mode for [" + resource + "]: cannot open InputStream. " +
"Did you attempt to load directly from a SAX InputSource without specifying the " +
"validationMode on your XmlBeanDefinitionReader instance?", ex);
}

try {
// 1.2.3 根据inputStream检测验证模式
return this.validationModeDetector.detectValidationMode(inputStream);
} catch (IOException ex) {
throw new BeanDefinitionStoreException("Unable to determine validation mode for [" +
resource + "]: an error occurred whilst reading from the InputStream.", ex);
}
}

public int detectValidationMode(InputStream inputStream) throws IOException {
// Peek into the file to look for DOCTYPE.
BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
try {
boolean isDtdValidated = false;
String content;
// 1.2.3.1 按行遍历xml配置文件,获取xml文件的验证模式
while ((content = reader.readLine()) != null) {
content = consumeCommentTokens(content);
// 如果读取的行是空或者注释则略过
if (this.inComment || !StringUtils.hasText(content)) {
continue;
}
// 内容包含"DOCTYPE"则为DTD,否则为XSD
if (hasDoctype(content)) {
isDtdValidated = true;
break;
}
// 如果content带有 '<' 开始符号,则结束遍历。因为验证模式一定会在开始符号之前,所以到此可以认为没有验证模式
if (hasOpeningTag(content)) {
// End of meaningful data...
break;
}
}
// 1.2.3.2 根据遍历结果返回验证模式是 DTD 还是 XSD
return (isDtdValidated ? VALIDATION_DTD : VALIDATION_XSD);
}
catch (CharConversionException ex) {
// Choked on some character encoding...
// Leave the decision up to the caller.
return VALIDATION_AUTO;
}
finally {
reader.close();
}
}


@Override
public Document loadDocument(InputSource inputSource, EntityResolver entityResolver,
ErrorHandler errorHandler, int validationMode, boolean namespaceAware) throws Exception {
// 2.1 创建DocumentBuilderFactory
DocumentBuilderFactory factory = createDocumentBuilderFactory(validationMode, namespaceAware);
if (logger.isTraceEnabled()) {
logger.trace("Using JAXP provider [" + factory.getClass().getName() + "]");
}
// 2.2 通过DocumentBuilderFactory创建DocumentBuilder
DocumentBuilder builder = createDocumentBuilder(factory, entityResolver, errorHandler);
// 2.3 使用DocumentBuilder解析inputSource返回Document对象
return builder.parse(inputSource);
}
  1. 获取 XML 配置文件的验证模式。XML 文件的验证模式是用来保证 XML 文件的正确性,常见的验证模式有两种:DTD 和 XSD,以下简单展示下这两种验证模式的配置。
DTD 验证模式(已停止更新)

要使用 DTD 验证模式的时候需要在 XML 文件的头部声明,以下是在 Spring 中使用 DTD 声明方式的代码:

XSD 验证模式

从 Spring的 源码中可以看到,dtd 验证模式已经停止更新,因此目前使用的验证模式基本上是 XSD。

解析DOM前的一些处理

XmlBeanDefinitionReader.registerBeanDefinitions

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/**
* 注册BeanDefinition
* 通过解析DOM处理xml 来注册BeanDefinition
*
*/
public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {
// 1.使用DefaultBeanDefinitionDocumentReader实例化BeanDefinitionDocumentReader
//又来一记委托模式,BeanDefinitionDocumentReader委托这个类进行document的解析
BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();
// 2.记录统计前BeanDefinition的加载个数
int countBefore = getRegistry().getBeanDefinitionCount();
// 3.createReaderContext:根据resource创建一个XmlReaderContext
// 4.registerBeanDefinitions:加载及注册Bean定义
//主要看这个方法,createReaderContext(resource) XmlReaderContext上下文,封装了XmlBeanDefinitionReader对象
documentReader.registerBeanDefinitions(doc, createReaderContext(resource));
// 5.返回本次加载的BeanDefinition个数
return getRegistry().getBeanDefinitionCount() - countBefore;
}
createReaderContext
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
public XmlReaderContext createReaderContext(Resource resource) {
return new XmlReaderContext(resource, this.problemReporter, this.eventListener,
this.sourceExtractor, this, getNamespaceHandlerResolver());
}

public NamespaceHandlerResolver getNamespaceHandlerResolver() {
if (this.namespaceHandlerResolver == null) {
this.namespaceHandlerResolver = createDefaultNamespaceHandlerResolver();
}
return this.namespaceHandlerResolver;
}

protected NamespaceHandlerResolver createDefaultNamespaceHandlerResolver() {
return new DefaultNamespaceHandlerResolver(getResourceLoader().getClassLoader());
}

// DefaultNamespaceHandlerResolver.java
public DefaultNamespaceHandlerResolver(ClassLoader classLoader) {
this(classLoader, DEFAULT_HANDLER_MAPPINGS_LOCATION);
}

public DefaultNamespaceHandlerResolver(ClassLoader classLoader, String handlerMappingsLocation) {
Assert.notNull(handlerMappingsLocation, "Handler mappings location must not be null");
this.classLoader = (classLoader != null ? classLoader : ClassUtils.getDefaultClassLoader());
this.handlerMappingsLocation = handlerMappingsLocation;
}

这边会根据 resource 构建一个 XmlReaderContext,用于存放解析时会用到的一些上下文信息。

其中 namespaceHandlerResolver 会创建默认的 DefaultNamespaceHandlerResolver,DefaultNamespaceHandlerResolver的handlerMappingsLocation 属性会使用默认的值 “META-INF/spring.handlers”,并且这边有个重要的属性 handlerMappings,handlerMappings 用于存放命名空间和该命名空间handler类的映射,如下图:

handlerMappings 的值默认位于“META-INF/spring.handlers” 文件下,一般在我们定义自定义注解时需要用到。

DefaultBeanDefinitionDocumentReader.registerBeanDefinitions

获取Document的ROOT节点 开始真正解析DOM元素

1
2
3
4
5
6
7
8
9
10
11
/**
* (or DTD, historically).
* 获取Document的ROOT节点 开始真正解析DOM元素
*
*/
@Override
public void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) {
this.readerContext = readerContext;
//主要看这个方法,把root节点传进去
doRegisterBeanDefinitions(doc.getDocumentElement());
}

DefaultBeanDefinitionDocumentReader.doRegisterBeanDefinitions

真正开始解析DOM注册BeanDefinition

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
/**
* 真正开始解析DOM注册BeanDefinition
*/
@SuppressWarnings("deprecation") // for Environment.acceptsProfiles(String...)
protected void doRegisterBeanDefinitions(Element root) {
// Any nested <beans> elements will cause recursion in this method. In
// order to propagate and preserve <beans> default-* attributes correctly,
// keep track of the current (parent) delegate, which may be null. Create
// the new (child) delegate with a reference to the parent for fallback purposes,
// then ultimately reset this.delegate back to its original (parent) reference.
// this behavior emulates a stack of delegates without actually necessitating one.
BeanDefinitionParserDelegate parent = this.delegate;
//1. 创建BeanDefinition的解析器,交给具体类执行 又是个委托模式
this.delegate = createDelegate(getReaderContext(), root, parent);

// 1.校验root节点的命名空间是否为默认的命名空间(默认命名空间http://www.springframework.org/schema/beans)
if (this.delegate.isDefaultNamespace(root)) {
// 2.处理profile属性
String profileSpec = root.getAttribute(PROFILE_ATTRIBUTE);
if (StringUtils.hasText(profileSpec)) {
String[] specifiedProfiles = StringUtils.tokenizeToStringArray(
profileSpec, BeanDefinitionParserDelegate.MULTI_VALUE_ATTRIBUTE_DELIMITERS);
// We cannot use Profiles.of(...) since profile expressions are not supported
// in XML config. See SPR-12458 for details.
// 校验当前节点的 profile 是否符合当前环境定义的, 如果不是则直接跳过, 不解析该节点下的内容
if (!getReaderContext().getEnvironment().acceptsProfiles(specifiedProfiles)) {
if (logger.isDebugEnabled()) {
logger.debug("Skipped XML bean definition file due to specified profiles [" + profileSpec +
"] not matching: " + getReaderContext().getResource());
}
return;
}
}
}
//3. 空方法 解析之前的扩展点用来让子类重写来
preProcessXml(root);
//4. 主要看这个方法,标签具体解析过程,解析并注册bean定义
parseBeanDefinitions(root, this.delegate);
//5. 空方法解析完成后的扩展点让子类重写来
postProcessXml(root);
//返回解析类
this.delegate = parent;
}

profile 属性主要用于多环境开发,例如下图:

我们可以在配置文件中同时写上多套配置来适用于开发环境、测试环境、生产环境,这样可以方便的进行切换开发、部署环境,最常用的就是更换不同的数据库。具体使用哪个环境在 web.xml 中通过参数 spring.profiles.active 来配置。

DefaultBeanDefinitionDocumentReader.parseBeanDefinitions

解析 xml的根节点

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
/**
* 解析 xml的根节点
* 分为默认标签以及自定义标签解析
*/
protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {
// 1.默认命名空间的处理
if (delegate.isDefaultNamespace(root)) {
// 遍历root的子节点列表
NodeList nl = root.getChildNodes();
for (int i = 0; i < nl.getLength(); i++) {
Node node = nl.item(i);
if (node instanceof Element) {
Element ele = (Element) node;
if (delegate.isDefaultNamespace(ele)) {
// 1.1 默认命名空间节点的处理,例如: <bean id="test" class="" />
parseDefaultElement(ele, delegate);
} else {
// 1.2 自定义命名空间节点的处理,例如:<context:component-scan/>、<aop:aspectj-autoproxy/>
delegate.parseCustomElement(ele);
}
}
}
} else {
// 2.自定义命名空间的处理
delegate.parseCustomElement(root);
}
}

​ 最终,我们来到了解析 bean 定义的核心部分,这边会遍历 root 节点(正常为 <beans> 节点)下的所有子节点,对子节点进行解析处理。

​ 如果节点的命名空间是 Spring 默认的命名空间,则走 parseDefaultElement(ele, delegate) 方法进行解析,例如最常见的:

​ 如果节点的命名空间不是 Spring 默认的命名空间,也就是自定义命名空间,则走 delegate.parseCustomElement(ele) 方法进行解析,例如常见的:<context:component-scan/>、<aop:aspectj-autoproxy/>。

如何判断默认命名空间还是自定义命名空间?

默认的命名空间为:http://www.springframework.org/schema/beans,其他都是自定义命名空间,例如下图 aop 的命名空间为:http://www.springframework.org/schema/context

总结

本文主要介绍了加载 bean 定义的一些基本工作:

  • 创建一个新的 BeanFactory:DefaultListableBeanFactory。
  • 根据 spring.xml 中 contextConfigLocation 配置的路径,读取 Spring 配置文件,验证配置文件的内容,并封装成 Resource。
  • 根据 Resource 加载 XML 配置文件,并解析成 Document 对象 。
  • 拿到 Document 中的根节点,遍历根节点和所有子节点。

核心的节点解析将在之后的文章单独介绍

评论