- ubuntu12.04环境下使用kvm ioctl接口实现最简单的虚拟机
- Ubuntu 通过无线网络安装Ubuntu Server启动系统后连接无线网络的方法
- 在Ubuntu上搭建网桥的方法
- ubuntu 虚拟机上网方式及相关配置详解
CFSDN坚持开源创造价值,我们致力于搭建一个资源共享平台,让每一个IT人在这里找到属于你的精彩世界.
这篇CFSDN的博客文章这一次搞懂Spring的XML解析原理说明由作者收集整理,如果你对这篇文章有兴趣,记得点赞哟.
前言 。
Spring已经是我们Java Web开发必不可少的一个框架,其大大简化了我们的开发,提高了开发者的效率。同时,其源码对于开发者来说也是宝藏,从中我们可以学习到非常优秀的设计思想以及优雅的命名规范,但因其体系庞大、设计复杂对于刚开始阅读源码的人来说是非常困难的。所以在此之前首先你得下定决心,不管有多困难都得坚持下去;其次,最好先把设计模式掌握熟练;然后在开始阅读源码时一定要多画UML类图和时序图,多问自己为什么要这么设计?这样设计的好处是什么?还有没有更好的设计?当然,晕车是难免的,但还是那句话,一定要持之以恒(PS:源码版本5.1.3.RELEASE).
正文 。
熟悉IOC体系结构 。
要学习Spring源码,我们首先得要找准入口,那这个入口怎么找呢?我们不妨先思考一下,在Spring项目启动时,Spring做了哪些事情。这里我以最原始的xml配置方式来分析,那么在项目启动时,首先肯定要先定位——找到xml配置文件,定位之后肯定是加载——将我们的配置加载到内存,最后才是根据我们的配置实例化(本篇文章只讲前两个过程)。那么Spring是如何定位和加载xml文件的呢?涉及到哪些类呢?我们先来看张类图:
该图是IOC的体系图,整体上你需要有一个大概的印象,可以看到所有的IOC都是有继承关系的,这样设计的好处就是任何一个子类IOC可以直接使用父类IOC加载的Bean,有点像JVM类加载的双亲委派机制;而红色方框圈起来的是本篇涉及到的重要类,需要着重记忆它们的关系.
图中最重要的两个类是BeanFactory和ApplicationContext,这是所有IOC的父接口。其中BeanFactory提供了最基本的对bean的操作:
而ApplicationContex继承了BeanFactory,同时还继承了MessageSource、ResourceLoader、ApplicationEventPublisher等接口以提供国际化、资源加载、事件发布等高级功能。我们应该想到平时Spring加载xml文件应该是ApplicationContext的子类,从图中我们可以看到一个叫ClassPathXmlApplicationContext的类,联想到我们平时都会 将xml放到classPath下,所以我们直接从这个类开始就行,这就是优秀命名的好处.
探究配置加载的过程 。
在ClassPathXmlApplicationContext中有很多构造方法,其中有一个是传入一个字符串的(即配置文件的相对路径),但最终是调用的下面这个构造:
1
2
3
4
5
6
7
8
9
10
11
12
|
public
ClassPathXmlApplicationContext(
String[] configLocations,
boolean
refresh,
@Nullable
ApplicationContext parent)
throws
BeansException {
super
(parent);
//创建解析器,解析configLocations
setConfigLocations(configLocations);
if
(refresh) {
refresh();
}
}
|
首先调用父类构造器设置环境:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
public
AbstractApplicationContext(
@Nullable
ApplicationContext parent) {
this
();
setParent(parent);
}
public
void
setParent(
@Nullable
ApplicationContext parent) {
this
.parent = parent;
if
(parent !=
null
) {
Environment parentEnvironment = parent.getEnvironment();
if
(parentEnvironment
instanceof
ConfigurableEnvironment) {
getEnvironment().merge((ConfigurableEnvironment) parentEnvironment);
}
}
}
|
然后解析传入的相对路径保存到configLocations变量中,最后再调用父类AbstractApplicationContext的refresh方法刷新容器(启动容器都会调用该方法),我们着重来看这个方法:
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
|
public
void
refresh()
throws
BeansException, IllegalStateException {
synchronized
(
this
.startupShutdownMonitor) {
//为容器初始化做准备
prepareRefresh();
// 解析xml
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
// Prepare the bean factory for use in this context.
prepareBeanFactory(beanFactory);
try
{
// Allows post-processing of the bean factory in context subclasses.
postProcessBeanFactory(beanFactory);
// Invoke factory processors registered as beans in the context.
invokeBeanFactoryPostProcessors(beanFactory);
// Register bean processors that intercept bean creation.
registerBeanPostProcessors(beanFactory);
// Initialize message source for this context.
initMessageSource();
// Initialize event multicaster for this context.
initApplicationEventMulticaster();
// Initialize other special beans in specific context subclasses.
onRefresh();
// Check for listener beans and register them.
registerListeners();
// 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对象、加载解析xml并封装成BeanDefinition对象都是在这一步完成的.
1
2
3
4
|
protected
ConfigurableListableBeanFactory obtainFreshBeanFactory() {
refreshBeanFactory();
return
getBeanFactory();
}
|
点进去看是调用了refreshBeanFactory方法,但这里有两个实现,应该进哪一个类里面呢?
如果你还记得前面的继承体系,那你就会毫不犹豫的进入AbstractRefreshableApplicationContext类中,所以在阅读源码的过程中一定要记住类的继承体系.
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
final
void
refreshBeanFactory()
throws
BeansException {
//如果BeanFactory不为空,则清除BeanFactory和里面的实例
if
(hasBeanFactory()) {
destroyBeans();
closeBeanFactory();
}
try
{
//创建DefaultListableBeanFactory
DefaultListableBeanFactory beanFactory = createBeanFactory();
beanFactory.setSerializationId(getId());
//设置是否可以循环依赖 allowCircularReferences
//是否允许使用相同名称重新注册不同的bean实现.
customizeBeanFactory(beanFactory);
//解析xml,并把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);
}
}
|
在这个方法中首先会清除掉上一次创建的BeanFactory和对象实例,然后创建了一个DefaultListableBeanFactory对象并传入到了loadBeanDefinitions方法中,这也是一个模板方法,因为我们的配置不止有xml,还有注解等,所以这里我们应该进入AbstractXmlApplicationContext类中:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
protected
void
loadBeanDefinitions(DefaultListableBeanFactory beanFactory)
throws
BeansException, IOException {
//创建xml的解析器,这里是一个委托模式
XmlBeanDefinitionReader beanDefinitionReader =
new
XmlBeanDefinitionReader(beanFactory);
// Configure the bean definition reader with this context's
// resource loading environment.
beanDefinitionReader.setEnvironment(
this
.getEnvironment());
//这里传一个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);
//主要看这个方法
loadBeanDefinitions(beanDefinitionReader);
}
|
首先创建了一个XmlBeanDefinitionReader对象,见名知意,这个就是解析xml的类,需要注意的是该类的构造方法接收的是BeanDefinitionRegistry对象,而这里将DefaultListableBeanFactory对象传入了进去(别忘记了这个对象是实现了BeanDefinitionRegistry类的),如果你足够敏感,应该可以想到后面会委托给该类去注册。注册什么呢?自然是注册BeanDefintion。记住这个猜想,我们稍后来验证是不是这么回事.
接着进入loadBeanDefinitions方法获取之前保存的xml配置文件路径,并委托给XmlBeanDefinitionReader对象解析加载:
1
2
3
4
5
6
7
8
9
10
11
|
protected
void
loadBeanDefinitions(XmlBeanDefinitionReader reader)
throws
BeansException, IOException {
Resource[] configResources = getConfigResources();
if
(configResources !=
null
) {
reader.loadBeanDefinitions(configResources);
}
//获取需要加载的xml配置文件
String[] configLocations = getConfigLocations();
if
(configLocations !=
null
) {
reader.loadBeanDefinitions(configLocations);
}
}
|
最后会进入到抽象父类AbstractBeanDefinitionReader中:
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
|
public
int
loadBeanDefinitions(String location,
@Nullable
Set<Resource> actualResources)
throws
BeanDefinitionStoreException {
// 这里获取到的依然是DefaultListableBeanFactory对象
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
{
//把字符串类型的xml文件路径,形如:classpath*:user/**/*-context.xml,转换成Resource对象类型,其实就是用流
//的方式加载配置文件,然后封装成Resource对象
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;
}
}
|
这个方法中主要将xml配置加载到存中并封装成为Resource对象,这一步不重要,可以略过,主要的还是loadBeanDefinitions方法,最终还是调用到子类XmlBeanDefinitionReader的方法:
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
|
public
int
loadBeanDefinitions(EncodedResource encodedResource)
throws
BeanDefinitionStoreException {
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());
}
//主要看这个方法
return
doLoadBeanDefinitions(inputSource, encodedResource.getResource());
}
finally
{
inputStream.close();
}
}
}
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;
}
}
public
int
registerBeanDefinitions(Document doc, Resource resource)
throws
BeanDefinitionStoreException {
// 创建DefaultBeanDefinitionDocumentReader对象,并委托其做解析注册工作
BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();
int
countBefore = getRegistry().getBeanDefinitionCount();
//主要看这个方法,需要注意createReaderContext方法中创建的几个对象
documentReader.registerBeanDefinitions(doc, createReaderContext(resource));
return
getRegistry().getBeanDefinitionCount() - countBefore;
}
public
XmlReaderContext createReaderContext(Resource resource) {
// XmlReaderContext对象中保存了XmlBeanDefinitionReader对象和DefaultNamespaceHandlerResolver对象的引用,在后面会用到
return
new
XmlReaderContext(resource,
this
.problemReporter,
this
.eventListener,
this
.sourceExtractor,
this
, getNamespaceHandlerResolver());
}
|
接着看看DefaultBeanDefinitionDocumentReader中是如何解析的:
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
|
protected
void
doRegisterBeanDefinitions(Element root) {
// 创建了BeanDefinitionParserDelegate对象
BeanDefinitionParserDelegate parent =
this
.delegate;
this
.delegate = createDelegate(getReaderContext(), root, parent);
// 如果是Spring原生命名空间,首先解析 profile标签,这里不重要
if
(
this
.delegate.isDefaultNamespace(root)) {
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.
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
;
}
}
}
preProcessXml(root);
//主要看这个方法,标签具体解析过程
parseBeanDefinitions(root,
this
.delegate);
postProcessXml(root);
this
.delegate = parent;
}
|
在这个方法中重点关注preProcessXml、parseBeanDefinitions、postProcessXml三个方法,其中preProcessXml和postProcessXml都是空方法,意思是在解析标签前后我们自己可以扩展需要执行的操作,也是一个模板方法模式,体现了Spring的高扩展性。然后进入parseBeanDefinitions方法看具体是怎么解析标签的:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
protected
void
parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {
if
(delegate.isDefaultNamespace(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)) {
//默认标签解析
parseDefaultElement(ele, delegate);
}
else
{
//自定义标签解析
delegate.parseCustomElement(ele);
}
}
}
}
else
{
delegate.parseCustomElement(root);
}
}
|
这里有两种标签的解析:Spring原生标签和自定义标签。怎么区分这两种标签呢?
1
2
3
4
5
|
// 自定义标签
<context:component-scan/>
// 默认标签
<bean:/>
|
如上,带前缀的就是自定义标签,否则就是Spring默认标签,无论哪种标签在使用前都需要在Spring的xml配置文件里声明Namespace URI,这样在解析标签时才能通过Namespace URI找到对应的NamespaceHandler.
xmlns:context="http://www.springframework.org/schema/context" 。
http://www.springframework.org/schema/beans 。
isDefaultNamespace判断是不是默认标签,点进去看看是不是跟我上面说的一致:
1
2
3
4
5
6
7
8
|
public
boolean
isDefaultNamespace(Node node) {
return
isDefaultNamespace(getNamespaceURI(node));
}
public
static
final
String BEANS_NAMESPACE_URI =
"http://www.springframework.org/schema/beans"
;
public
boolean
isDefaultNamespace(
@Nullable
String namespaceUri) {
return
(!StringUtils.hasLength(namespaceUri) || BEANS_NAMESPACE_URI.equals(namespaceUri));
}
|
可以看到http://www.springframework.org/schema/beans所对应的就是默认标签。接着,我们进入parseDefaultElement方法:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
private
void
parseDefaultElement(Element ele, BeanDefinitionParserDelegate delegate) {
//import标签解析
if
(delegate.nodeNameEquals(ele, IMPORT_ELEMENT)) {
importBeanDefinitionResource(ele);
}
//alias标签解析
else
if
(delegate.nodeNameEquals(ele, ALIAS_ELEMENT)) {
processAliasRegistration(ele);
}
//bean标签
else
if
(delegate.nodeNameEquals(ele, BEAN_ELEMENT)) {
processBeanDefinition(ele, delegate);
}
else
if
(delegate.nodeNameEquals(ele, NESTED_BEANS_ELEMENT)) {
// recurse
doRegisterBeanDefinitions(ele);
}
}
|
这里面主要是对import、alias、bean标签的解析以及beans的字标签的递归解析,主要看看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
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
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
|
protected
void
processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate) {
// 解析elment封装为BeanDefinitionHolder对象
BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele);
if
(bdHolder !=
null
) {
// 该方法功能不重要,主要理解设计思想:装饰者设计模式以及SPI设计思想
bdHolder = delegate.decorateBeanDefinitionIfRequired(ele, bdHolder);
try
{
// 完成document到BeanDefinition对象转换后,对BeanDefinition对象进行缓存注册
BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, getReaderContext().getRegistry());
}
// Send registration event.
getReaderContext().fireComponentRegistered(
new
BeanComponentDefinition(bdHolder));
}
}
public
BeanDefinitionHolder parseBeanDefinitionElement(Element ele,
@Nullable
BeanDefinition containingBean) {
// 获取id和name属性
String id = ele.getAttribute(ID_ATTRIBUTE);
String nameAttr = ele.getAttribute(NAME_ATTRIBUTE);
// 获取别名属性,多个别名可用,;隔开
List<String> aliases =
new
ArrayList<>();
if
(StringUtils.hasLength(nameAttr)) {
String[] nameArr = StringUtils.tokenizeToStringArray(nameAttr, MULTI_VALUE_ATTRIBUTE_DELIMITERS);
aliases.addAll(Arrays.asList(nameArr));
}
String beanName = id;
if
(!StringUtils.hasText(beanName) && !aliases.isEmpty()) {
beanName = aliases.remove(
0
);
if
(logger.isTraceEnabled()) {
logger.trace(
"No XML 'id' specified - using '"
+ beanName +
"' as bean name and "
+ aliases +
" as aliases"
);
}
}
//检查beanName是否重复
if
(containingBean ==
null
) {
checkNameUniqueness(beanName, aliases, ele);
}
// 具体的解析封装过程还在这个方法里
AbstractBeanDefinition beanDefinition = parseBeanDefinitionElement(ele, beanName, containingBean);
if
(beanDefinition !=
null
) {
if
(!StringUtils.hasText(beanName)) {
try
{
if
(containingBean !=
null
) {
beanName = BeanDefinitionReaderUtils.generateBeanName(
beanDefinition,
this
.readerContext.getRegistry(),
true
);
}
else
{
beanName =
this
.readerContext.generateBeanName(beanDefinition);
// Register an alias for the plain bean class name, if still possible,
// if the generator returned the class name plus a suffix.
// This is expected for Spring 1.2/2.0 backwards compatibility.
String beanClassName = beanDefinition.getBeanClassName();
if
(beanClassName !=
null
&&
beanName.startsWith(beanClassName) && beanName.length() > beanClassName.length() &&
!
this
.readerContext.getRegistry().isBeanNameInUse(beanClassName)) {
aliases.add(beanClassName);
}
}
if
(logger.isTraceEnabled()) {
logger.trace(
"Neither XML 'id' nor 'name' specified - "
+
"using generated bean name ["
+ beanName +
"]"
);
}
}
catch
(Exception ex) {
error(ex.getMessage(), ele);
return
null
;
}
}
String[] aliasesArray = StringUtils.toStringArray(aliases);
return
new
BeanDefinitionHolder(beanDefinition, beanName, aliasesArray);
}
return
null
;
}
// bean的解析
public
AbstractBeanDefinition parseBeanDefinitionElement(
Element ele, String beanName,
@Nullable
BeanDefinition containingBean) {
this
.parseState.push(
new
BeanEntry(beanName));
// 获取class名称和父类名称
String className =
null
;
if
(ele.hasAttribute(CLASS_ATTRIBUTE)) {
className = ele.getAttribute(CLASS_ATTRIBUTE).trim();
}
String parent =
null
;
if
(ele.hasAttribute(PARENT_ATTRIBUTE)) {
parent = ele.getAttribute(PARENT_ATTRIBUTE);
}
try
{
// 创建GenericBeanDefinition对象
AbstractBeanDefinition bd = createBeanDefinition(className, parent);
// 解析bean标签的属性,并把解析出来的属性设置到BeanDefinition对象中
parseBeanDefinitionAttributes(ele, beanName, containingBean, bd);
bd.setDescription(DomUtils.getChildElementValueByTagName(ele, DESCRIPTION_ELEMENT));
//解析bean中的meta标签
parseMetaElements(ele, bd);
//解析bean中的lookup-method标签
parseLookupOverrideSubElements(ele, bd.getMethodOverrides());
//解析bean中的replaced-method标签
parseReplacedMethodSubElements(ele, bd.getMethodOverrides());
//解析bean中的constructor-arg标签
parseConstructorArgElements(ele, bd);
//解析bean中的property标签
parsePropertyElements(ele, bd);
parseQualifierElements(ele, bd);
bd.setResource(
this
.readerContext.getResource());
bd.setSource(extractSource(ele));
return
bd;
}
return
null
;
}
|
bean标签的解析步骤仔细理解并不复杂,就是将一个个标签属性的值装入到了BeanDefinition对象中,这里需要注意parseConstructorArgElements和parsePropertyElements方法,分别是对constructor-arg和property标签的解析,解析完成后分别装入了BeanDefinition对象的constructorArgumentValues和propertyValues中,而这两个属性在接下来c和p标签的解析中还会用到,而且还涉及一个很重要的设计思想——装饰器模式.
Bean标签解析完成后将生成的BeanDefinition对象、bean的名称以及别名一起封装到了BeanDefinitionHolder对象并返回,然后调用了decorateBeanDefinitionIfRequired进行装饰:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
public
BeanDefinitionHolder decorateBeanDefinitionIfRequired(
Element ele, BeanDefinitionHolder definitionHolder,
@Nullable
BeanDefinition containingBd) {
BeanDefinitionHolder finalDefinition = definitionHolder;
//根据bean标签属性装饰BeanDefinitionHolder,比如<bean class="xx" p:username="dark"/>
NamedNodeMap attributes = ele.getAttributes();
for
(
int
i =
0
; i < attributes.getLength(); i++) {
Node node = attributes.item(i);
finalDefinition = decorateIfRequired(node, finalDefinition, containingBd);
}
//根据bean标签子元素装饰BeanDefinitionHolder\
NodeList children = ele.getChildNodes();
for
(
int
i =
0
; i < children.getLength(); i++) {
Node node = children.item(i);
if
(node.getNodeType() == Node.ELEMENT_NODE) {
finalDefinition = decorateIfRequired(node, finalDefinition, containingBd);
}
}
return
finalDefinition;
}
|
在这个方法中分别对Bean标签的属性和子标签迭代,获取其中的自定义标签进行解析,并装饰之前创建的BeanDefinition对象,如同下面的c和p:
// c:和p:表示通过构造器和属性的setter方法给属性赋值,是constructor-arg和property的简化写法 。
<bean class="com.dark.bean.Student" id="student" p:username="Dark" p:password="111" c:age="12" c:sex="1"/> 。
两个步骤是一样的,我们点进decorateIfRequired方法中:
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
BeanDefinitionHolder decorateIfRequired(
Node node, BeanDefinitionHolder originalDef,
@Nullable
BeanDefinition containingBd) {
//根据node获取到node的命名空间,形如:http://www.springframework.org/schema/p
String namespaceUri = getNamespaceURI(node);
if
(namespaceUri !=
null
&& !isDefaultNamespace(namespaceUri)) {
// 根据配置文件获取namespaceUri对应的处理类,SPI思想
NamespaceHandler handler =
this
.readerContext.getNamespaceHandlerResolver().resolve(namespaceUri);
if
(handler !=
null
) {
//调用NamespaceHandler处理类的decorate方法,开始具体装饰过程,并返回装饰完的对象
BeanDefinitionHolder decorated =
handler.decorate(node, originalDef,
new
ParserContext(
this
.readerContext,
this
, containingBd));
if
(decorated !=
null
) {
return
decorated;
}
}
else
if
(namespaceUri.startsWith(
"http://www.springframework.org/"
)) {
error(
"Unable to locate Spring NamespaceHandler for XML schema namespace ["
+ namespaceUri +
"]"
, node);
}
else
{
// A custom namespace, not to be handled by Spring - maybe "xml:...".
if
(logger.isDebugEnabled()) {
logger.debug(
"No Spring NamespaceHandler found for XML schema namespace ["
+ namespaceUri +
"]"
);
}
}
}
return
originalDef;
}
|
这里也和我们之前说的一样,首先获取到标签对应的namespaceUri,然后通过这个Uri去获取到对应的NamespceHandler,最后再调用NamespceHandler的decorate方法进行装饰。我们先来看看获取NamespceHandler的过程,这涉及到一个非常重要的高扩展性的思想——SPI(有关SPI,在我之前的文章Dubbo——SPI及自适应扩展原理中已经详细讲解过,这里不再赘述):
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
|
public
NamespaceHandler resolve(String namespaceUri) {
// 获取spring中所有jar包里面的 "META-INF/spring.handlers"文件,并且建立映射关系
Map<String, Object> handlerMappings = getHandlerMappings();
//根据namespaceUri:http://www.springframework.org/schema/p,获取到这个命名空间的处理类
Object handlerOrClassName = handlerMappings.get(namespaceUri);
if
(handlerOrClassName ==
null
) {
return
null
;
}
else
if
(handlerOrClassName
instanceof
NamespaceHandler) {
return
(NamespaceHandler) handlerOrClassName;
}
else
{
String className = (String) handlerOrClassName;
try
{
Class<?> handlerClass = ClassUtils.forName(className,
this
.classLoader);
if
(!NamespaceHandler.
class
.isAssignableFrom(handlerClass)) {
throw
new
FatalBeanException(
"Class ["
+ className +
"] for namespace ["
+ namespaceUri +
"] does not implement the ["
+ NamespaceHandler.
class
.getName() +
"] interface"
);
}
NamespaceHandler namespaceHandler = (NamespaceHandler) BeanUtils.instantiateClass(handlerClass);
//调用处理类的init方法,在init方法中完成标签元素解析类的注册
namespaceHandler.init();
handlerMappings.put(namespaceUri, namespaceHandler);
return
namespaceHandler;
}
}
}
// AOP标签对应的NamespaceHandler,可以发现NamespaceHandler的作用就是管理和注册与自己相关的标签解析器
public
void
init() {
// In 2.0 XSD as well as in 2.1 XSD.
registerBeanDefinitionParser(
"config"
,
new
ConfigBeanDefinitionParser());
registerBeanDefinitionParser(
"aspectj-autoproxy"
,
new
AspectJAutoProxyBeanDefinitionParser());
registerBeanDefinitionDecorator(
"scoped-proxy"
,
new
ScopedProxyBeanDefinitionDecorator());
// Only in 2.0 XSD: moved to context namespace as of 2.1
registerBeanDefinitionParser(
"spring-configured"
,
new
SpringConfiguredBeanDefinitionParser());
}
|
看到这里我们应该就清楚了Spring是如何解析xml里的标签了以及我们如果要扩展自己的标签该怎么做。只需要创建一个我们的自定义标签和解析类,并指定它的命名空间以及NamespaceHandler,最后在META-INF/spring.handlers文件中指定命名空间和NamespaceHandler的映射关系即可,就像Spring的c和p标签一样:
http\://www.springframework.org/schema/c=org.springframework.beans.factory.xml.SimpleConstructorNamespaceHandler 。
http\://www.springframework.org/schema/p=org.springframework.beans.factory.xml.SimplePropertyNamespaceHandler 。
像这样使用SPI的思想设计我们的项目的话,当需要扩展时,不需要改动任何的代码,非常的方便优雅.
接着,我们回到handler的decorate方法,这里有三个默认的实现类:NamespaceHandlerSupport、SimpleConstructorNamespaceHandler、SimplePropertyNamespaceHandler。第一个是一个抽象类,与我们这里的流程无关,感兴趣的可自行了解,第二个和第三个则分别是c和p标签对应的NamespaceHandler,两个装饰的处理逻辑基本上是一样的,我这里进入的是SimpleConstructorNamespaceHandler类:
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
|
public
BeanDefinitionHolder decorate(Node node, BeanDefinitionHolder definition, ParserContext parserContext) {
if
(node
instanceof
Attr) {
Attr attr = (Attr) node;
String argName = StringUtils.trimWhitespace(parserContext.getDelegate().getLocalName(attr));
String argValue = StringUtils.trimWhitespace(attr.getValue());
ConstructorArgumentValues cvs = definition.getBeanDefinition().getConstructorArgumentValues();
boolean
ref =
false
;
// handle -ref arguments
if
(argName.endsWith(REF_SUFFIX)) {
ref =
true
;
argName = argName.substring(
0
, argName.length() - REF_SUFFIX.length());
}
ValueHolder valueHolder =
new
ValueHolder(ref ?
new
RuntimeBeanReference(argValue) : argValue);
valueHolder.setSource(parserContext.getReaderContext().extractSource(attr));
// handle "escaped"/"_" arguments
if
(argName.startsWith(DELIMITER_PREFIX)) {
String arg = argName.substring(
1
).trim();
// fast default check
if
(!StringUtils.hasText(arg)) {
cvs.addGenericArgumentValue(valueHolder);
}
// assume an index otherwise
else
{
int
index = -
1
;
try
{
index = Integer.parseInt(arg);
}
catch
(NumberFormatException ex) {
parserContext.getReaderContext().error(
"Constructor argument '"
+ argName +
"' specifies an invalid integer"
, attr);
}
if
(index <
0
) {
parserContext.getReaderContext().error(
"Constructor argument '"
+ argName +
"' specifies a negative index"
, attr);
}
if
(cvs.hasIndexedArgumentValue(index)) {
parserContext.getReaderContext().error(
"Constructor argument '"
+ argName +
"' with index "
+ index+
" already defined using <constructor-arg>."
+
" Only one approach may be used per argument."
, attr);
}
cvs.addIndexedArgumentValue(index, valueHolder);
}
}
// no escaping -> ctr name
else
{
String name = Conventions.attributeNameToPropertyName(argName);
if
(containsArgWithName(name, cvs)) {
parserContext.getReaderContext().error(
"Constructor argument '"
+ argName +
"' already defined using <constructor-arg>."
+
" Only one approach may be used per argument."
, attr);
}
valueHolder.setName(Conventions.attributeNameToPropertyName(argName));
cvs.addGenericArgumentValue(valueHolder);
}
}
return
definition;
}
|
很简单,拿到c标签对应的值,封装成ValueHolder,再添加到BeanDefinition的ConstructorArgumentValues属性中去,这样就装饰完成了.
讲到这里你可能会觉得,这和平时看到装饰器模式不太一样。其实,设计模式真正想要表达的是各种模式所代表的思想,而不是死搬硬套的实现,只有灵活的运用其思想才算是真正的掌握了设计模式,而装饰器模式的精髓就是动态的将属性、功能、责任附加到对象上,这样你再看这里是否是运用了装饰器的思想呢?
装饰完成后返回BeanDefinitionHolder对象并调用BeanDefinitionReaderUtils.registerBeanDefinition方法将该对象缓存起来,等待容器去实例化。这里就是将其缓存到DefaultListableBeanFactory的beanDefinitionMap属性中,自己看看代码也就明白了,我就不贴代码了。至此,Spring的XML解析原理分析完毕,下面是我画的时序图,可以对照看看:
总结 。
本篇是Spring源码分析的第一篇,只是分析了refresh中的obtainFreshBeanFactory方法,我们可以看到仅仅是对XML的解析和bean定义的注册缓存,Spring就做了这么多事,并考虑到了各个可能会扩展的地方,那我们平时做的项目呢?看似简单的背后是否有深入思考过呢?
以上这篇这一次搞懂Spring的XML解析原理说明就是小编分享给大家的全部内容了,希望能给大家一个参考,也希望大家多多支持我.
原文链接:https://blog.csdn.net/l6108003/article/details/106297618 。
最后此篇关于这一次搞懂Spring的XML解析原理说明的文章就讲到这里了,如果你想了解更多关于这一次搞懂Spring的XML解析原理说明的内容请搜索CFSDN的文章或继续浏览相关文章,希望大家以后支持我的博客! 。
我是一名优秀的程序员,十分优秀!