讀完這篇文章你將會收穫到node
咱們先從一個簡單常見的代碼入手分析web
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN 2.0//EN" "https://www.springframework.org/dtd/spring-beans-2.0.dtd"> <beans> <bean class="com.demo.data.Person"> <description> 微信搜一搜:CoderLi(不妨關注➕一下?此次必定?) </description> </bean> </beans> 複製代碼
public static void main(String[] args) {
Resource resource = new ClassPathResource("coderLi.xml"); DefaultListableBeanFactory defaultListableBeanFactory = new DefaultListableBeanFactory(); XmlBeanDefinitionReader xmlBeanDefinitionReader = new XmlBeanDefinitionReader(defaultListableBeanFactory); xmlBeanDefinitionReader.loadBeanDefinitions(resource); } 複製代碼
上面這段 Java 代碼主要作了面試
因此整體而言就是資源的加載、加載、註冊三個步驟spring
對於資源的加載能夠看看我另外一篇文章 Spring-資源加載(源碼分析)微信
加載的過程則是將 Resource 對象轉爲一系列的 BeanDefinition 對象app
註冊則是將 BeanDefinition 注入到 BeanDefinitionRegistry 中編輯器
在分析源碼流程以前咱們一塊兒先對一些重要的組件混個眼熟ide
defaultListableBeanFactory 是整個 bean 加載的核心部分,是 bean 註冊及加載 bean 的默認實現函數
對於 AliasRegistry 能夠參考我另外一篇文章 Spring-AliasRegistry 。關於這個類咱們只要記住兩點,一個是它是一個 beanFactory、一個是它是一個 BeanDefinitionRegistry源碼分析
從 XML 資源文件中讀取並轉換爲 BeanDefinition 的各個功能
對 Resource 文件進行轉換、將 Resource 文件轉換爲 Document 文件
讀取 Document 並向 BeanDefinitionRegistry 註冊
loadBeanDefinitions(Resource)
XmlBeanDefinitionReader#loadBeanDefinitions(Resource)
咱們先從這個入口方法開始進去
@Override
public int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException { return loadBeanDefinitions(new EncodedResource(resource)); } 複製代碼
EncodedResource
是 Resource 的子類, Spring-資源加載(源碼分析)
public class EncodedResource implements InputStreamSource {
private final Resource resource; @Nullable private final String encoding; @Nullable private final Charset charset; .......... .......... public Reader getReader() throws IOException { if (this.charset != null) { return new InputStreamReader(this.resource.getInputStream(), this.charset); } else if (this.encoding != null) { return new InputStreamReader(this.resource.getInputStream(), this.encoding); } else { return new InputStreamReader(this.resource.getInputStream()); } } } 複製代碼
只是一個簡單的 Wrapper 類,針對不一樣的字符集和字符編碼返回不同的 Reader
loadBeanDefinitions(EncodedResource)
public int loadBeanDefinitions(EncodedResource encodedResource) throws BeanDefinitionStoreException {
// 從Thread Local 中獲取正在加載的的資源 Set<EncodedResource> currentResources = this.resourcesCurrentlyBeingLoaded.get(); // 判斷這個資源是否已經加載過了、主要是爲了是不是 資源的循環依賴 import if (!currentResources.add(encodedResource)) { throw new BeanDefinitionStoreException(""); } try (InputStream inputStream = encodedResource.getResource().getInputStream()) { InputSource inputSource = new InputSource(inputStream); // 有encode 就設置進去 if (encodedResource.getEncoding() != null) { inputSource.setEncoding(encodedResource.getEncoding()); } // 真正的加載 return doLoadBeanDefinitions(inputSource, encodedResource.getResource()); } catch (IOException ex) { throw new BeanDefinitionStoreException(""); } finally { // ThreadLocal的最佳實踐 currentResources.remove(encodedResource); if (currentResources.isEmpty()) { this.resourcesCurrentlyBeingLoaded.remove(); } } } 複製代碼
首先從 ThreadLocal
中獲取正在加載的 Resource
,這裏主要是檢查 import
標籤帶來的循環引用問題。
從這裏咱們能夠看到在 finally
中,對已經完成加載的資源進行移除,而且檢查 Set
是否還有元素了,若是沒有則直接調用 ThreadLocal
的 remove
方法。這個就是 ThreadLocal 的最佳實踐了,最後的 remove
方法的調用能夠避免 ThreadLocal 在 ThreadLocalMap 中做爲 WeakReference
而帶來的內存泄露問題。
這個方法裏基本作啥事情、最主要的事情就是調用了 doLoadBeanDefinitions
這個方法,而這個方法纔是真正幹活的。(在 Spring 中,頗有意思的是、真正幹活的方法前綴都是帶有 do
的,這個能夠留意下)
doLoadBeanDefinitions(InputSource Resource)
// 獲取 document 對象
Document doc = doLoadDocument(inputSource, resource); // 註冊 bean definition int count = registerBeanDefinitions(doc, resource); return count; 複製代碼
doLoadDocument
這個方法就是將 Resource 轉化爲 Document,這裏涉及到 xml 文件到驗證,創建對應的 Document Node ,使用到的就是上面說起到的 DocumentLoader
。這個不展開來探討。
咱們直接進入到 registerBeanDefinitions
方法中
registerBeanDefinitions(Document,Resource)
public int registerBeanDefinitions(Document doc, Resource resource) {
// 建立一個 bean definition 的 reader BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader(); // 註冊以前已經有的 bean definition 的個數 return this.beanDefinitionMap.size(); int countBefore = getRegistry().getBeanDefinitionCount(); documentReader.registerBeanDefinitions(doc, createReaderContext(resource)); return getRegistry().getBeanDefinitionCount() - countBefore; } 複製代碼
上面代碼中出現了一個咱們說起到的 BeanDefinitionDocumentReader
組件,他的功能就是讀取 Document 並向 BeanDefinitionRegistry 註冊
public void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) {
this.readerContext = readerContext; doRegisterBeanDefinitions(doc.getDocumentElement()); } 複製代碼
這裏又來了、do
纔是真正幹活的大哥
protected void doRegisterBeanDefinitions(Element root) {
BeanDefinitionParserDelegate parent = this.delegate; this.delegate = createDelegate(getReaderContext(), root, parent); if (this.delegate.isDefaultNamespace(root)) { // 處理 profiles String profileSpec = root.getAttribute(PROFILE_ATTRIBUTE); if (StringUtils.hasText(profileSpec)) { String[] specifiedProfiles = StringUtils.tokenizeToStringArray( profileSpec, BeanDefinitionParserDelegate.MULTI_VALUE_ATTRIBUTE_DELIMITERS); if (!getReaderContext().getEnvironment().acceptsProfiles(specifiedProfiles)) { return; } } } // 解釋前的處理 這裏默認爲空實現、子類能夠覆蓋此方法在解釋 Element 以前作些事情 preProcessXml(root); // 解釋 parseBeanDefinitions(root, this.delegate); // 解釋後處理 這裏默認爲空實現 postProcessXml(root); this.delegate = parent; } 複製代碼
這裏主要的方法就是 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)) { // spring 默認標籤解釋 parseDefaultElement(ele, delegate); } else { // 自定義 標籤解釋 delegate.parseCustomElement(ele); } } } } else { delegate.parseCustomElement(root); } } 複製代碼
Spring 的默認標籤有 import
, beans
, bean
, alias
private void parseDefaultElement(Element ele, BeanDefinitionParserDelegate delegate) {
// 解釋 import 標籤 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)) { doRegisterBeanDefinitions(ele); } } 複製代碼
解釋 import
標籤調用 importBeanDefinitionResource
最終會調用到咱們最開始處理 Resource
循環依賴的那個方法 loadBeanDefinitions
中
咱們直接進入到 processAliasRegistration
方法中
protected void processAliasRegistration(Element ele) {
String name = ele.getAttribute(NAME_ATTRIBUTE); String alias = ele.getAttribute(ALIAS_ATTRIBUTE); boolean valid = true; if (!StringUtils.hasText(name)) { getReaderContext().error("Name must not be empty", ele); valid = false; } if (!StringUtils.hasText(alias)) { getReaderContext().error("Alias must not be empty", ele); valid = false; } if (valid) { try { // 最重要的一行代碼 getReaderContext().getRegistry().registerAlias(name, alias); } catch (Exception ex) { getReaderContext().error("Failed to register alias '" + alias + "' for bean with name '" + name + "'", ele, ex); } getReaderContext().fireAliasRegistered(name, alias, extractSource(ele)); } } 複製代碼
最重要的一行代碼就是將 name 和 alias 進行註冊(這裏註冊的是 alias 標籤中的 name 和 alias 之間的關係),能夠參考這篇文章進行了解 Spring-AliasRegistry
咱們來到最主要的 processBeanDefinition
protected void processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate) { // 這裏得到了一個 BeanDefinitionHolder BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele); if (bdHolder != null) { bdHolder = delegate.decorateBeanDefinitionIfRequired(ele, bdHolder); try { BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, getReaderContext().getRegistry()); } catch (BeanDefinitionStoreException ex) { ..... } getReaderContext().fireComponentRegistered(new BeanComponentDefinition(bdHolder)); } } 複製代碼
咱們先分析 BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, getReaderContext().getRegistry())
這句代碼
public static void registerBeanDefinition( BeanDefinitionHolder definitionHolder, BeanDefinitionRegistry registry) throws BeanDefinitionStoreException { // 註冊 bean Name String beanName = definitionHolder.getBeanName(); registry.registerBeanDefinition(beanName, definitionHolder.getBeanDefinition()); // 註冊 alias . String[] aliases = definitionHolder.getAliases(); if (aliases != null) { for (String alias : aliases) { registry.registerAlias(beanName, alias); } } } 複製代碼
這個方法的做用很簡單、就是使用一開始咱們傳給 XmlBeanDefinitionReader
的 BeanDefinitionRegistry
對 bean 和 beanDefinition 的關係進行註冊。而且也對 beanName 和 alias 的關係進行註冊(這裏是對 bean 標籤中配置的 id 和 name 屬性關係進行配置)
delegate.parseBeanDefinitionElement(ele)
咱們再把眼光返回到這個方法、這個方法就是建立 BeanDefinition 的地方了
@Nullable
public BeanDefinitionHolder parseBeanDefinitionElement(Element ele) { return parseBeanDefinitionElement(ele, null); } 複製代碼
@Nullable
public BeanDefinitionHolder parseBeanDefinitionElement(Element ele, @Nullable BeanDefinition containingBean) { String id = ele.getAttribute(ID_ATTRIBUTE); String nameAttr = ele.getAttribute(NAME_ATTRIBUTE); List<String> aliases = new ArrayList<>(); // 判斷是否配置了 name 屬性、對name 進行分割 // 在 bean 標籤中 name 就是 alias 了 if (StringUtils.hasLength(nameAttr)) { String[] nameArr = StringUtils.tokenizeToStringArray(...); aliases.addAll(Arrays.asList(nameArr)); } String beanName = id; // 沒有配置id 而且 alias 列表不爲空、則選取第一個 alias 爲 bean Name if (!StringUtils.hasText(beanName) && !aliases.isEmpty()) { beanName = aliases.remove(0); } if (containingBean == null) { // 檢查 beanName 和alias 的惟一性 checkNameUniqueness(beanName, aliases, ele); } // 怎麼生成一個BeanDefinition 尼 AbstractBeanDefinition beanDefinition = parseBeanDefinitionElement(ele, beanName, containingBean); if (beanDefinition != null) { // 若是 beanName 爲 空 if (!StringUtils.hasText(beanName)) { try { if (containingBean != null) { beanName = BeanDefinitionReaderUtils.generateBeanName( beanDefinition, this.readerContext.getRegistry(), true); } else { // 沒有配置 beanName 和 alias的話、那麼這個類的第一個實例、將擁有 全類名的alias // org.springframework.beans.testfixture.beans.TestBean 這個是別名(TestBean#0 才擁有這個別名、其餘的不配擁有) // org.springframework.beans.testfixture.beans.TestBean#0 這個是 beanName 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); } } } catch (Exception ex) { ......... } } String[] aliasesArray = StringUtils.toStringArray(aliases); return new BeanDefinitionHolder(beanDefinition, beanName, aliasesArray); } // nothing return null; } 複製代碼
在 bean 標籤中 name 屬性對應的就是 alias ,id 屬性對應的就是 beanName 了
當咱們沒有配置 id 屬性可是配置了 name 屬性、那麼第一個 name 屬性就會成爲咱們的 id
當咱們既沒有配置 id 屬性 也沒有配置 name 屬性,那麼 Spring 就會幫咱們生成具體可看看 Spring-AliasRegistry
而後就建立了一個 BeanDefinitionHolder 返回了
上面的代碼咱們看到有這個關鍵的方法 parseBeanDefinitionElement(ele, beanName, containingBean)
這個方法生成了咱們期待的 BeanDefinition ,可是裏面的內容都是比較枯燥的
// 解釋class 屬性
String className = null; if (ele.hasAttribute(CLASS_ATTRIBUTE)) { className = ele.getAttribute(CLASS_ATTRIBUTE).trim(); } // 是否指定了 parent bean String parent = null; if (ele.hasAttribute(PARENT_ATTRIBUTE)) { parent = ele.getAttribute(PARENT_ATTRIBUTE); } // 建立 GenericBeanDefinition AbstractBeanDefinition bd = createBeanDefinition(className, parent); // 解釋各類默認的屬性 parseBeanDefinitionAttributes(ele, beanName, containingBean, bd); // 提取describe bd.setDescription(DomUtils.getChildElementValueByTagName(ele, DESCRIPTION_ELEMENT)); // 解釋元數據 parseMetaElements(ele, bd); // look up 方法 parseLookupOverrideSubElements(ele, bd.getMethodOverrides()); // replacer parseReplacedMethodSubElements(ele, bd.getMethodOverrides()); // 解析構造函數參數 parseConstructorArgElements(ele, bd); // 解釋property子元素 parsePropertyElements(ele, bd); // 解釋qualifier parseQualifierElements(ele, bd); bd.setResource(this.readerContext.getResource()); bd.setSource(extractSource(ele)); 複製代碼
都是去解析 bean 標籤裏面的各類屬性
那麼咱們整個 Spring 容器初始化流程就介紹完了
public static void main(String[] args) {
Resource resource = new ClassPathResource("coderLi.xml"); DefaultListableBeanFactory defaultListableBeanFactory = new DefaultListableBeanFactory(); XmlBeanDefinitionReader xmlBeanDefinitionReader = new XmlBeanDefinitionReader(defaultListableBeanFactory); xmlBeanDefinitionReader.loadBeanDefinitions(resource); } 複製代碼
大體的流程就是如此了,面試的時候大體說出 XmlBeanDefinitionReader,DocumentLoader,BeanDefinitionDocumentReader,BeanDefinitionRegistry,AliasRegistry
這幾個組件,面試官大機率會認爲你是真的看過 Spring 的這部分代碼的
往期相關文章
本文使用 mdnice 排版