關注公衆號,你們能夠在公衆號後臺回覆「博客園」,免費得到做者 Java 知識體系/面試必看資料java
展現的代碼摘取了一些核心方法,去掉一些默認設置和日誌輸出,還有大多數錯誤異常也去掉了,小夥伴想看詳細代碼,註釋和 demo,能夠下載我上傳的筆記項目📒node
碼雲 Gitee 地址 https://gitee.com/vip-augus/spring-analysis-note.gitGithub 地址 https://github.com/Vip-Augus/spring-analysis-note
經過閱讀源碼的過程,瞭解設計者的設計思路和從中學習,對 spring
有個基礎的瞭解。git
ClassPathXmlApplicationContext
ClassPathXmlApplicationContext
的繼承體系結構圖:github
這種結構圖是經過 IDEA
編輯器的 Diagrams
功能展現的,對當前類右鍵選擇,能夠看到繼承體系,繼承了哪些類和引用了哪些接口,方便咱們去了解~ClassPathXmlApplicationContext
繼承自 AbstractApplicationContext
,而AbstractRefreshableApplicationContext
是 AbstractApplicationContext
的抽象子類,使用的類註冊工廠是 DefaultListableBeanFactory
,這個註冊工廠也很重要,後面會有它的介紹。web
簡單來講,DefaultListableBeanFactory
是 Spring
註冊及加載 bean
的默認實現,它會將註冊的 bean
放入 beanDefinitionMap
進行 key-value
形式存儲。面試
在圖片的右上角能看到,ResourceLoader
是它的頂層接口,表示這個類實現了資源加載功能。構造器的代碼:spring
public ClassPathXmlApplicationContext( String[] configLocations, boolean refresh, @Nullable ApplicationContext parent) throws BeansException { super(parent); // 註釋 1.1 獲取資源文件 setConfigLocations(configLocations); if (refresh) { refresh(); }}
設置配置文件路徑數據庫
org.springframework.context.support.AbstractRefreshableConfigApplicationContext設計模式
public void setConfigLocations(@Nullable String... locations) {
if (locations != null) {
Assert.noNullElements(locations, "Config locations must not be null");
// 註釋 1.2 將配置資源路徑放入 configLocations 數組中
this.configLocations = new String[locations.length];
for (int i = 0; i < locations.length; i++) {
this.configLocations[i] = resolvePath(locations[i]).trim();
}
}
else {
this.configLocations = null;
}
}
數組
resolvePath
,
用途是:解析給定的路徑,用對應的佔位符(placeholder)替換佔位符
例如 new ClassPathXmlApplicationContext("classpath:config.xml");
,就須要解析classpath
,變成正確路徑。
protected String resolvePath(String path) { return getEnvironment().resolveRequiredPlaceholders(path);}
咱們有不一樣的運行環境,dev
,test
或者 prod
,這個時候加載的配置文件和屬性應該有所不一樣,這個時候就須要使用到 Environment
來進行區分。
Spring
環境和屬性是由四個部分組成:
-
Environment
: 環境,由Profile
和PropertyResolver
組合。 -
Profile
: 配置文件,能夠理解爲,容器裏多個配置組別的屬性和bean
,只有激活的profile
,它對應的組別屬性和bean
纔會被加載 -
PropertySource
: 屬性源, 使用CopyOnWriteArrayList
數組進行屬性對key-value
形式存儲 -
PropertyResolver
:屬性解析器,這個用途就是解析屬性
Profile
經過這個屬性,能夠同時在配置文件中部署兩套配置,用來適用於生產環境和開發環境,這樣能夠方便的進行切換開發、部署環境,經常使用來更換不一樣的數據庫或者配置文件。
demo
:(引用自參考資料第四條)
<!-- 測試環境配置文件 --><beans profile="test"> <context:property-placeholder location="classpath:test/*.properties, classpath:common/*.properties" /></beans><!-- 生產環境配置文件 --><beans profile="production"> <context:property-placeholder location="classpath:production/*.properties, classpath:common/*.properties" /></beans><!-- 開發環境配置文件 --><beans profile="development"> <context:property-placeholder location="classpath:dev/*.properties, classpath:common/*.properties" /></beans>
有兩種方式能夠設置選擇使用哪套配置:
① 在 web.xml
中設置
<context-param> <param-name>spring.profiles.active</param-name> <param-value>test</param-value></context-param>
② 在代碼啓動時設置
context.getEnvironment().setActiveProfiles("test");
PropertySource 接口
繼承體系如圖:
從 PropertySource
繼承體系來看,customizePropertySources
方法的調用鏈路是從子類一直往上調用 :
AbstractEnvironment
->StandardServletEnvironment
-> StandardEnvironment
最終在 StandardEnvironment
使用 CopyOnWriteArrayList
數組進行屬性存儲
protected void customizePropertySources(MutablePropertySources propertySources) { propertySources.addLast(new MapPropertySource(SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME, getSystemProperties())); propertySources.addLast(new SystemEnvironmentPropertySource(SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME, getSystemEnvironment()));}
例如從上面能夠看出,propertySourceList
將會存儲系統的參數:
到時這些參數就能在啓動的應用中,經過上下文 context
進行獲取
((MutablePropertySources)((StandardEnvironment)context.environment).propertySources).propertySourceList
Bean 的解析和註冊
Spring bean
的解析和註冊有一個重要的方法 refresh()
AbstractApplicationContext.refresh()
public void refresh() throws BeansException, IllegalStateException { synchronized (this.startupShutdownMonitor) { // Prepare this context for refreshing. (爲更新準備上下文,設定一些標誌) prepareRefresh(); // Tell the subclass to refresh the internal bean factory. (告訴子類去更新它們的 bean factory) // 類的註冊到 bean factory 也是在這一步 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(); } }}
下面會圍繞這個方法進行跟蹤和分析。
具體校驗的方法
org.springframework.core.env.AbstractPropertyResolver#validateRequiredProperties
public void validateRequiredProperties() { MissingRequiredPropertiesException ex = new MissingRequiredPropertiesException(); for (String key : this.requiredProperties) { if (this.getProperty(key) == null) { ex.addMissingRequiredProperty(key); } } if (!ex.getMissingRequiredProperties().isEmpty()) { throw ex; }}
能夠看到,校驗邏輯是遍歷 requiredProperties
,它是一個字符 Set
,默認狀況下是空,表示不須要校驗任何元素,若是列表中有值,而後根據 key
獲取對應的環境變量爲空,將會拋出異常,致使 Spring
容器初始化失敗。
獲取 bean
容器
在這行代碼中 ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
具體調用的是 :
org.springframework.context.support.AbstractRefreshableApplicationContext#refreshBeanFactory
protected final void refreshBeanFactory() throws BeansException { // 在更新時,若是發現已經存在,將會把以前的 bean 清理掉,而且關閉老 bean 容器 if (hasBeanFactory()) { destroyBeans(); closeBeanFactory(); } try { DefaultListableBeanFactory beanFactory = createBeanFactory(); beanFactory.setSerializationId(getId()); customizeBeanFactory(beanFactory); // 註釋 1.3 開始加載 (bean 註冊) loadBeanDefinitions(beanFactory); synchronized (this.beanFactoryMonitor) { this.beanFactory = beanFactory; } } catch (IOException ex) { throw new ApplicationContextException("I/O error parsing bean definition source for " + getDisplayName(), ex); }}
這個入口方法很重要,在這一步新建了 bean
容器和解析 bean
,並將 bean
註冊到容器中。
BanFactory 自定義
具體方法以下,經過這個方法,能夠對工廠進行定製化設置,讓子類進行自由配置:
org.springframework.context.support.AbstractRefreshableApplicationContext#customizeBeanFactory
protected void customizeBeanFactory(DefaultListableBeanFactory beanFactory) { if (this.allowBeanDefinitionOverriding != null) { // 默認是 false,不容許覆蓋 beanFactory.setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding); } if (this.allowCircularReferences != null) { // 默認是 false,不容許循環引用 beanFactory.setAllowCircularReferences(this.allowCircularReferences); }}
EntityResolver
接口全路徑是:org.xml.sax.EntityResolver
,具體解析使用的方法是:
org.springframework.beans.factory.xml.ResourceEntityResolver#resolveEntity
該方法是用於解析 schema
和 dtd
,具體深究的話也很複雜,但解析 xml
不是我想了解的點,因此先跳過~
配置文件加載
入口方法:(因爲有多個重名方法,因此複製路徑時,將參數的類型也拷貝了)
org.springframework.beans.factory.support.AbstractBeanDefinitionReader#loadBeanDefinitions(java.lang.String, java.util.Set<org.springframework.core.io.Resource>)
核心方法是這兩行
public int loadBeanDefinitions(String location, @Nullable Set<Resource> actualResources) throws BeanDefinitionStoreException { // 獲取資源文件(資源加載器從路徑識別資源文件) Resource[] resources = ((ResourcePatternResolver) resourceLoader).getResources(location) // 註釋 1.6 根據資源文件加載 bean int count = loadBeanDefinitions(resources); ···}
獲取資源文件後,開始解析資源文件(也就是一開始傳參的 config.xml
),將它轉換成 Document
跟蹤代碼能夠看到,進行解析的資源文件從 Resource
包裝成 EncodeResouce
,爲輸入流添加了字符編碼(默認爲 null
),體現了設計模式裝飾器模式。
遍歷資源文件,進行轉換,核心方法是如下兩行:
org.springframework.beans.factory.xml.XmlBeanDefinitionReader#loadBeanDefinitions(org.springframework.core.io.support.EncodedResource)
public int loadBeanDefinitions(EncodedResource encodedResource) throws BeanDefinitionStoreException { // 註釋 1.7 從資源文件中獲取輸入流 InputStream inputStream = encodedResource.getResource().getInputStream(); InputSource inputSource = new InputSource(inputStream); return doLoadBeanDefinitions(inputSource, encodedResource.getResource());}
默認標籤解析
這部分不會細說,以後再寫一篇進行補充,因此簡單的過下代碼中,是如何解析默認標籤的
-
IMPORT:導入標籤
-
ALIAS:別名標籤
-
BEAN:
bean
標籤 -
NESTED_BEANS:
beans
標籤(嵌套的beans
)
org.springframework.beans.factory.xml.DefaultBeanDefinitionDocumentReader#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); }}
讓咱們來看下如何解析 bean
標籤
獲取 id 和 name
org.springframework.beans.factory.xml.BeanDefinitionParserDelegate#parseBeanDefinitionElement(org.w3c.dom.Element, org.springframework.beans.factory.config.BeanDefinition)
public BeanDefinitionHolder parseBeanDefinitionElement(Element ele, @Nullable BeanDefinition containingBean) { // 獲取 ID 屬性 String id = ele.getAttribute(ID_ATTRIBUTE); // 獲取 NAME 屬性 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()) { // 若是沒有指定 id,將 name 的第一個值做爲 id beanName = aliases.remove(0); } // 默認 null if (containingBean == null) { // 檢查名字是否惟一,若是 id 重複了,將拋出錯誤 // 內部 usedNames 是一個 HashSet,將會存儲加載過的 name 和 aliases checkNameUniqueness(beanName, aliases, ele); } // 將公共屬性放入 AbstractBeanDefinition,具體實如今子類 GenericBeanDefinition AbstractBeanDefinition beanDefinition = parseBeanDefinitionElement(ele, beanName, containingBean); if (beanDefinition != null) { if (!StringUtils.hasText(beanName)) { if (containingBean != null) { // 若是 id 和 name 都是空,那個 spring 會給它生成一個默認的名稱 beanName = BeanDefinitionReaderUtils.generateBeanName( beanDefinition, this.readerContext.getRegistry(), true); } else { beanName = this.readerContext.generateBeanName(beanDefinition); String beanClassName = beanDefinition.getBeanClassName(); if (beanClassName != null && beanName.startsWith(beanClassName) && beanName.length() > beanClassName.length() && !this.readerContext.getRegistry().isBeanNameInUse(beanClassName)) { aliases.add(beanClassName); } } } } String[] aliasesArray = StringUtils.toStringArray(aliases); return new BeanDefinitionHolder(beanDefinition, beanName, aliasesArray); } return null;}
獲取 id
和 name
屬性的流程,按照代碼註釋一步一步往下走就清晰了
該方法主要工做流程以下:
-
提取元素中的
id
name
屬性 -
進一步解析其它全部屬性並統一封裝到
GenericBeanDefinition
類型的實例中 -
檢測到
bean
沒有指定beanName
使用默認規則生成beanName
-
將獲取到的信息封裝到
BeanDefinitionHolder
的實例中
對標籤中其它屬性的解析
org.springframework.beans.factory.xml.BeanDefinitionParserDelegate#parseBeanDefinitionElement(org.w3c.dom.Element, java.lang.String, org.springframework.beans.factory.config.BeanDefinition)
public AbstractBeanDefinition parseBeanDefinitionElement( Element ele, String beanName, @Nullable BeanDefinition containingBean) { 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;}
初始化 BeanDefiniton
在這個方法中:(具體實現是它的子類 GenericBeanDefinition
噢~)
BeanDefinitionReaderUtils.createBeanDefinition(parentName, className, this.readerContext.getBeanClassLoader())
public static AbstractBeanDefinition createBeanDefinition( @Nullable String parentName, @Nullable String className, @Nullable ClassLoader classLoader) throws ClassNotFoundException { GenericBeanDefinition bd = new GenericBeanDefinition(); bd.setParentName(parentName); if (className != null) { if (classLoader != null) { bd.setBeanClass(ClassUtils.forName(className, classLoader)); } else { bd.setBeanClassName(className); } } return bd;}
後面就是解析其它標籤的內容,以後會補坑~
BeanDefinitionHolder 修飾
org.springframework.beans.factory.xml.BeanDefinitionParserDelegate#decorateBeanDefinitionIfRequired(org.w3c.dom.Element, org.springframework.beans.factory.config.BeanDefinitionHolder, org.springframework.beans.factory.config.BeanDefinition)
public BeanDefinitionHolder decorateBeanDefinitionIfRequired( Element ele, BeanDefinitionHolder definitionHolder, @Nullable BeanDefinition containingBd) { // 方法中的第三個參數是父類 bean // 當對某個嵌套配置進行分析時,這裏須要傳遞,是爲了使用父類的 scope 屬性,以備子類沒設定 scope,可使用父類的 scope 屬性 BeanDefinitionHolder finalDefinition = definitionHolder; // Decorate based on custom attributes first. NamedNodeMap attributes = ele.getAttributes(); // 遍歷全部的屬性,進行屬性的修飾 for (int i = 0; i < attributes.getLength(); i++) { Node node = attributes.item(i); finalDefinition = decorateIfRequired(node, finalDefinition, containingBd); } // Decorate based on custom nested elements. 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;}
在以前的常規屬性解析後,在這一步操做中,主要用來完成自定義標籤元素的解析,這裏繼續留個坑~
prepareBeanFactory
準備類加載器的環境,對前面獲取到的 beanFactory(ConfigurationListableBeanFactory)
進行相關的設置,包括 ClassLoader
, post-processors
等
invokeBeanFactoryPostProcessors
實例化並調用全部註冊的 BeanFactoryPostProcessorBean
,這些是後處理器,處理類型是BeanFactory
, Spring
容器容許在實例化 bean
前,讀取 bean
信息和修改它的屬性。
至關於在實例化前,給用戶最後一次機會去修改 bean
信息。
還有一點,執行也能夠有前後順序,依據這些處理器是否實現 PriorityOrdered
、Order
接口,根據 order
值進行排序。
initMessageSource
初始化此上下文的消息源
onRefresh
模板方法,可被重寫以添加特定於上下文的刷新工做。
在實例化單例以前調用特殊 bean
的初始化。(霧,不知道是啥特殊 bean
,留個坑=-=)
此實現爲空。
finishBeanFactoryInitialization
完成 bean
容器的初始化,實例化全部剩餘的(非惰性初始化)單例
resetCommonCaches
是真註冊的最後一步,用來清除緩存
重置 Spring 核心中的公共內省緩存,由於咱們可能不再須要單例 bean 的元數據了
踩坑記錄
Javadoc 編譯錯誤
Javadoc generation failed. Generated Javadoc options file (useful for troubleshooting)
在編譯時,發現沒法成功,提示 Javadoc
的錯誤,解決方法是在 gradle
文件中添加如下配置:
tasks.withType(Javadoc) { options.addStringOption('Xdoclint:none', '-quiet') options.addStringOption('encoding', 'UTF-8')}
參考資料
一、spring-analysis/note/Spring.md
二、Spring Framework 5.0.0.M3中文文檔
三、Spring 源碼深度解析 / 郝佳編著. – 北京 : 人民郵電出版社
四、使用Spring3.1後的的Profile配置使不一樣環境加載不一樣配置文件
五、spring4.1.8擴展實戰之一:自定義環境變量驗證
Java 極客技術公衆號,是由一羣熱愛 Java 開發的技術人組建成立,專一分享原創、高質量的 Java 文章。若是您以爲咱們的文章還不錯,請幫忙讚揚、在看、轉發支持,鼓勵咱們分享出更好的文章。