激情久久久_欧美视频区_成人av免费_不卡视频一二三区_欧美精品在欧美一区二区少妇_欧美一区二区三区的

服務器之家:專注于服務器技術及軟件下載分享
分類導航

PHP教程|ASP.NET教程|Java教程|ASP教程|編程技術|正則表達式|C/C++|IOS|C#|Swift|Android|VB|R語言|JavaScript|易語言|vb.net|

服務器之家 - 編程語言 - Java教程 - 這一次搞懂Spring的XML解析原理說明

這一次搞懂Spring的XML解析原理說明

2020-08-27 14:16夜勿語 Java教程

這篇文章主要介紹了這一次搞懂Spring的XML解析原理說明,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧

前言

Spring已經是我們Java Web開發必不可少的一個框架,其大大簡化了我們的開發,提高了開發者的效率。同時,其源碼對于開發者來說也是寶藏,從中我們可以學習到非常優秀的設計思想以及優雅的命名規范,但因其體系龐大、設計復雜對于剛開始閱讀源碼的人來說是非常困難的。所以在此之前首先你得下定決心,不管有多困難都得堅持下去;其次,最好先把設計模式掌握熟練;然后在開始閱讀源碼時一定要多畫UML類圖和時序圖,多問自己為什么要這么設計?這樣設計的好處是什么?還有沒有更好的設計?當然,暈車是難免的,但還是那句話,一定要持之以恒(PS:源碼版本5.1.3.RELEASE)。

正文

熟悉IOC體系結構

要學習Spring源碼,我們首先得要找準入口,那這個入口怎么找呢?我們不妨先思考一下,在Spring項目啟動時,Spring做了哪些事情。這里我以最原始的xml配置方式來分析,那么在項目啟動時,首先肯定要先定位——找到xml配置文件,定位之后肯定是加載——將我們的配置加載到內存,最后才是根據我們的配置實例化(本篇文章只講前兩個過程)。那么Spring是如何定位和加載xml文件的呢?涉及到哪些類呢?我們先來看張類圖:

這一次搞懂Spring的XML解析原理說明

該圖是IOC的體系圖,整體上你需要有一個大概的印象,可以看到所有的IOC都是有繼承關系的,這樣設計的好處就是任何一個子類IOC可以直接使用父類IOC加載的Bean,有點像JVM類加載的雙親委派機制;而紅色方框圈起來的是本篇涉及到的重要類,需要著重記憶它們的關系。

圖中最重要的兩個類是BeanFactory和ApplicationContext,這是所有IOC的父接口。其中BeanFactory提供了最基本的對bean的操作:

這一次搞懂Spring的XML解析原理說明

而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方法,但這里有兩個實現,應該進哪一個類里面呢?

這一次搞懂Spring的XML解析原理說明

如果你還記得前面的繼承體系,那你就會毫不猶豫的進入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的XML解析原理說明

總結

本篇是Spring源碼分析的第一篇,只是分析了refresh中的obtainFreshBeanFactory方法,我們可以看到僅僅是對XML的解析和bean定義的注冊緩存,Spring就做了這么多事,并考慮到了各個可能會擴展的地方,那我們平時做的項目呢?看似簡單的背后是否有深入思考過呢?

以上這篇這一次搞懂Spring的XML解析原理說明就是小編分享給大家的全部內容了,希望能給大家一個參考,也希望大家多多支持服務器之家。

原文鏈接:https://blog.csdn.net/l6108003/article/details/106297618

延伸 · 閱讀

精彩推薦
主站蜘蛛池模板: 亚洲日韩精品欧美一区二区 | 黄色网址你懂的 | 欧美爱爱视频网站 | 麻豆传传媒久久久爱 | 久久精品伊人网 | 久久久亚洲高清 | 欧美91看片特黄aaaa | 美女视频免费一区二区 | 精品一区二区久久久久久久网精 | 亚洲第一黄色网 | 天天草天天爱 | 久草在线观看资源 | 毛片在线免费 | 成人片免费视频 | 中国美女一级黄色大片 | 九九热在线精品视频 | 精品小视频 | 国产毛片aaa一区二区三区视频 | 黄色成人在线 | av在线播放免费观看 | 欧美精品一区二区三区在线 | 三级18视频 | 国产午夜亚洲精品午夜鲁丝片 | 久草手机在线观看视频 | h视频免费在线观看 | 香蕉国产在线视频 | 久久夜夜视频 | 综合网天天色 | 成人精品免费在线观看 | 久久久精品视频国产 | 久久成人精品视频 | 午夜视频在线观 | 九九热免费观看 | 天堂精品| 少妇一级淫片免费放正片 | 国产精品久久久久久模特 | 欧美黄色大片免费观看 | 成人做爰s片免费看网站 | 日本不卡一区二区在线观看 | 亚洲网站免费看 | 国产精品久久久久久久四虎电影 |