通過 Windows 命令提示符(cmd)在桌面新建文件夾,并在該文件夾中編譯、運行一段 Java 程序段
777
2025-04-01
Spring 源碼解讀繼續。
本文是 Spring 系列第八篇,如果小伙伴們還沒閱讀過本系列前面的文章,建議先看看,這有助于更好的理解本文。
Spring 源碼解讀計劃
Spring 源碼第一篇開整!配置文件是怎么加載的?
Spring 源碼第二彈!XML 文件解析流程
Spring 源碼第三彈!EntityResolver 是個什么鬼?
Spring 源碼第四彈!深入理解 BeanDefinition
手把手教你搭建 Spring 源碼分析環境
Spring 源碼第六彈!松哥和大家聊聊容器的始祖 DefaultListableBeanFactory
1.前文回顧
不知道小伙伴們是否還記得,在前面我們講 Spring 文檔加載的時候,涉及到如下一段源碼:
protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource) throws BeanDefinitionStoreException { try { Document doc = doLoadDocument(inputSource, resource); 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); } }
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
這段代碼就兩個核心方法:
首先調用 doLoadDocument 方法獲取 Spring 的 XML 配置文件加載出來的 Document 文檔對象,這個方法的執行流程我們在前面已經介紹過了,這里就不再贅述。
接下來就是調用 registerBeanDefinitions 方法,講加載出來的文檔對象進行解析,定義出相應的 BeanDefinition 對象出來。
BeanDefinition 是什么,有什么作用,松哥在之前的 Spring 源碼第四彈!深入理解 BeanDefinition 一文中已經做過介紹,這里就不再贅述。
本文我們就來看看 Document 對象是如何一步一步加載成 BeanDefinition 的。
2.parseDefaultElement
我們就從 registerBeanDefinitions 方法開始看起:
public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException { BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader(); int countBefore = getRegistry().getBeanDefinitionCount(); documentReader.registerBeanDefinitions(doc, createReaderContext(resource)); return getRegistry().getBeanDefinitionCount() - countBefore; }
1
2
3
4
5
6
這里通過調用 createBeanDefinitionDocumentReader 方法獲取到一個 BeanDefinitionDocumentReader 的實例,具體的對象則是 DefaultBeanDefinitionDocumentReader,也就是說接下來調用 DefaultBeanDefinitionDocumentReader#registerBeanDefinitions 進行解析。繼續來看該方法的定義:
@Override public void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) { this.readerContext = readerContext; doRegisterBeanDefinitions(doc.getDocumentElement()); }
1
2
3
4
5
這里又調用到了 doRegisterBeanDefinitions 方法繼續完成注冊:
protected void doRegisterBeanDefinitions(Element root) { // Any nested
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
這個方法流程比較簡單,首先檢查了一下有沒有 profile 需要處理(如果有人不清楚 Spring 中的 profile,可以在公眾號后臺回復 spring5 獲取松哥錄制的免費的 Spring 入門教程)。處理完 profile 之后,接下來就是解析了,解析有一個前置處理方法 preProcessXml 和后置處理方法 postProcessXml,不過這兩個方法默認都是空方法,真正的解析方法是 parseBeanDefinitions:
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); } }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
在該方法中進行節點的解析,最終會來到 parseDefaultElement 方法中。我們一起來看下該方法:
private void parseDefaultElement(Element ele, BeanDefinitionParserDelegate delegate) { if (delegate.nodeNameEquals(ele, IMPORT_ELEMENT)) { importBeanDefinitionResource(ele); } else if (delegate.nodeNameEquals(ele, ALIAS_ELEMENT)) { processAliasRegistration(ele); } else if (delegate.nodeNameEquals(ele, BEAN_ELEMENT)) { processBeanDefinition(ele, delegate); } else if (delegate.nodeNameEquals(ele, NESTED_BEANS_ELEMENT)) { // recurse doRegisterBeanDefinitions(ele); } }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
終于來到期盼已久的 parseDefaultElement 方法中了。
在該方法中,我們可以看到,節點一共被分為了四大類:
import
alias
bean
beans
每一個節點都好理解,因為我們在開發中可能多多少少都有用過,需要注意的是,如果是 beans 節點,又會再次調用 doRegisterBeanDefinitions 方法進行遞歸解析,源碼上面還給了一個注釋 recurse,意思就是遞歸。
四種類型的節點解析,我們就從 bean 的解析看起吧,因為 beans 節點是我們最常用的節點,這個搞清楚了,另外三個節點就可以舉一反三了。
我們來看 processBeanDefinition 方法:
protected void processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate) { BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele); if (bdHolder != null) { bdHolder = delegate.decorateBeanDefinitionIfRequired(ele, bdHolder); try { // Register the final decorated instance. BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, getReaderContext().getRegistry()); } catch (BeanDefinitionStoreException ex) { getReaderContext().error("Failed to register bean definition with name '" + bdHolder.getBeanName() + "'", ele, ex); } // Send registration event. getReaderContext().fireComponentRegistered(new BeanComponentDefinition(bdHolder)); } }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
在這段代碼中,首先調用代理類 BeanDefinitionParserDelegate 對元素進行解析,解析的結果會保存在 bdHolder 中,也就是 bean 節點中配置的元素 class、id、name 等屬性,在經過這一步的解析之后,都會保存到 bdHolder 中。
如果 bdHolder 不為空,那么接下來對子節點的屬性繼續解析,同時對 bdHolder 進行注冊,最終發出事件,通知這個 bean 節點已經加載完了。
如此看來,整個解析的核心過程應該在 delegate.parseBeanDefinitionElement(ele) 方法中,追蹤該方法的執行,我們最終來到這里:
@Nullable public BeanDefinitionHolder parseBeanDefinitionElement(Element ele, @Nullable BeanDefinition containingBean) { String id = ele.getAttribute(ID_ATTRIBUTE); String nameAttr = ele.getAttribute(NAME_ATTRIBUTE); List
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
這個方法中所作的事情我們可以大致分為 5 個步驟:
提取出 id 和 name 屬性值。
檢查 beanName 是否唯一。
對節點做進一步的解析,解析出 beanDefinition 對象,真是的類型是 GenericBeanDefinition。
如果 beanName 屬性沒有值,則使用默認的規則生成 beanName(默認規則是類名全路徑)。
最終將獲取到的信息封裝成一個 BeanDefinitionHolder 返回。
在這一層面主要完成了對 id 和 name 的處理,如果用戶沒有給 bean 定義名稱的話,則生成一個默認的名稱,至于其他屬性的解析,則主要是在 parseBeanDefinitionElement 方法中完成的。
@Nullable public AbstractBeanDefinition parseBeanDefinitionElement( Element ele, String beanName, @Nullable BeanDefinition containingBean) { this.parseState.push(new BeanEntry(beanName)); 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 { AbstractBeanDefinition bd = createBeanDefinition(className, parent); parseBeanDefinitionAttributes(ele, beanName, containingBean, bd); bd.setDescription(DomUtils.getChildElementValueByTagName(ele, DESCRIPTION_ELEMENT)); parseMetaElements(ele, bd); parseLookupOverrideSubElements(ele, bd.getMethodOverrides()); parseReplacedMethodSubElements(ele, bd.getMethodOverrides()); parseConstructorArgElements(ele, bd); parsePropertyElements(ele, bd); parseQualifierElements(ele, bd); bd.setResource(this.readerContext.getResource()); bd.setSource(extractSource(ele)); return bd; } catch (ClassNotFoundException ex) { error("Bean class [" + className + "] not found", ele, ex); } catch (NoClassDefFoundError err) { error("Class that bean class [" + className + "] depends on not found", ele, err); } catch (Throwable ex) { error("Unexpected failure during bean definition parsing", ele, ex); } finally { this.parseState.pop(); } return null; }
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
首先解析出 className 屬性。
解析出 parent 屬性。
調用 createBeanDefinition 方法創建出用于保存對象的 BeanDefinition,既 GenericBeanDefinition。
parseBeanDefinitionAttributes 用來解析出各種各樣的節點屬性。
parseMetaElements 用來解析 Meta 數據。
parseLookupOverrideSubElements 解析 lookup-method 屬性。
parseReplacedMethodSubElements 解析 replace-method 屬性。
parseConstructorArgElements 解析構造函數參數。
parsePropertyElements 解析 property 子元素。
parseQualifierElements 解析 qualifier 子元素。
最終返回 bd。
可以看到,bean 節點中所有的屬性都解析了,有的是我們日常常見的屬性,有的是我們不常見的甚至從來都沒見到過的,無論哪種情況,現在全部都解析了。解析完成后,將獲得的 GenericBeanDefinition 返回。
3. 常規屬性解析
這里有一些屬性的解析可能比較冷門,這個我一會說,還有一些比較常規,例如 parseBeanDefinitionAttributes 方法用來解析各種各樣的節點屬性,這些節點屬性可能大家都比較熟悉,我們一起來看下:
public AbstractBeanDefinition parseBeanDefinitionAttributes(Element ele, String beanName, @Nullable BeanDefinition containingBean, AbstractBeanDefinition bd) { if (ele.hasAttribute(SINGLETON_ATTRIBUTE)) { error("Old 1.x 'singleton' attribute in use - upgrade to 'scope' declaration", ele); } else if (ele.hasAttribute(SCOPE_ATTRIBUTE)) { bd.setScope(ele.getAttribute(SCOPE_ATTRIBUTE)); } else if (containingBean != null) { // Take default from containing bean in case of an inner bean definition. bd.setScope(containingBean.getScope()); } if (ele.hasAttribute(ABSTRACT_ATTRIBUTE)) { bd.setAbstract(TRUE_VALUE.equals(ele.getAttribute(ABSTRACT_ATTRIBUTE))); } String lazyInit = ele.getAttribute(LAZY_INIT_ATTRIBUTE); if (isDefaultValue(lazyInit)) { lazyInit = this.defaults.getLazyInit(); } bd.setLazyInit(TRUE_VALUE.equals(lazyInit)); String autowire = ele.getAttribute(AUTOWIRE_ATTRIBUTE); bd.setAutowireMode(getAutowireMode(autowire)); if (ele.hasAttribute(DEPENDS_ON_ATTRIBUTE)) { String dependsOn = ele.getAttribute(DEPENDS_ON_ATTRIBUTE); bd.setDependsOn(StringUtils.tokenizeToStringArray(dependsOn, MULTI_VALUE_ATTRIBUTE_DELIMITERS)); } String autowireCandidate = ele.getAttribute(AUTOWIRE_CANDIDATE_ATTRIBUTE); if (isDefaultValue(autowireCandidate)) { String candidatePattern = this.defaults.getAutowireCandidates(); if (candidatePattern != null) { String[] patterns = StringUtils.commaDelimitedListToStringArray(candidatePattern); bd.setAutowireCandidate(PatternMatchUtils.simpleMatch(patterns, beanName)); } } else { bd.setAutowireCandidate(TRUE_VALUE.equals(autowireCandidate)); } if (ele.hasAttribute(PRIMARY_ATTRIBUTE)) { bd.setPrimary(TRUE_VALUE.equals(ele.getAttribute(PRIMARY_ATTRIBUTE))); } if (ele.hasAttribute(INIT_METHOD_ATTRIBUTE)) { String initMethodName = ele.getAttribute(INIT_METHOD_ATTRIBUTE); bd.setInitMethodName(initMethodName); } else if (this.defaults.getInitMethod() != null) { bd.setInitMethodName(this.defaults.getInitMethod()); bd.setEnforceInitMethod(false); } if (ele.hasAttribute(DESTROY_METHOD_ATTRIBUTE)) { String destroyMethodName = ele.getAttribute(DESTROY_METHOD_ATTRIBUTE); bd.setDestroyMethodName(destroyMethodName); } else if (this.defaults.getDestroyMethod() != null) { bd.setDestroyMethodName(this.defaults.getDestroyMethod()); bd.setEnforceDestroyMethod(false); } if (ele.hasAttribute(FACTORY_METHOD_ATTRIBUTE)) { bd.setFactoryMethodName(ele.getAttribute(FACTORY_METHOD_ATTRIBUTE)); } if (ele.hasAttribute(FACTORY_BEAN_ATTRIBUTE)) { bd.setFactoryBeanName(ele.getAttribute(FACTORY_BEAN_ATTRIBUTE)); } return bd; }
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
可以看到,這里解析的節點屬性,從上往下,依次是:
解析 singleton 屬性(該屬性已廢棄,使用 scope 替代)。
解析 scope 屬性,如果未指定 scope 屬性,但是存在 containingBean,則使用 containingBean 的 scope 屬性值。
解析 abstract 屬性。
解析 lazy-init 屬性。
解析 autowire 屬性。
解析 depends-on 屬性。
解析 autowire-candidate 屬性。
解析 primary 屬性。
解析 init-method 屬性。
解析 destroy-method 屬性。
解析 factory-method 屬性。
解析 factory-bean 屬性。
這些屬性作用大家都比較熟悉。因為日常用的多一些。
前面提到的解析中,lookup-method、replace-method、以及 qualifier 等屬性可能大家日常都很少用到,甚至沒有聽說過,如果用都沒用過,那源碼肯定不好理解,所以接下來松哥會錄制一個視頻,來和大家講一講這些冷門屬性的使用,然后我們再繼續深入解析這里的 parseMetaElements、parseLookupOverrideSubElements 等方法。
4. Bean 的生成
有了 BeanDefinitionHolder 之后,接下來 Bean 的生成就很容易了。
大家回顧如下兩篇文章來理解有了 BeanDefinition 之后,如何轉化為具體的 Bean:
Spring 源碼第四彈!深入理解 BeanDefinition
Spring 源碼第六彈!松哥和大家聊聊容器的始祖 DefaultListableBeanFactory
好啦,今天的文章就先說這么多~
Spring
版權聲明:本文內容由網絡用戶投稿,版權歸原作者所有,本站不擁有其著作權,亦不承擔相應法律責任。如果您發現本站中有涉嫌抄襲或描述失實的內容,請聯系我們jiasou666@gmail.com 處理,核實后本網站將在24小時內刪除侵權內容。
版權聲明:本文內容由網絡用戶投稿,版權歸原作者所有,本站不擁有其著作權,亦不承擔相應法律責任。如果您發現本站中有涉嫌抄襲或描述失實的內容,請聯系我們jiasou666@gmail.com 處理,核實后本網站將在24小時內刪除侵權內容。