Spring 容器的初始化

讀完這篇文章你將會收穫到node

  • 瞭解到 Spring 容器初始化流程
  • ThreadLocal 在 Spring 中的最佳實踐
  • 面試中回答 Spring 容器初始化流程

引言

咱們先從一個簡單常見的代碼入手分析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 代碼主要作了面試

  • 資源的獲取(定位)
  • 建立一個 beanFactory
  • 根據 beanFactory (實現了 BeanDefinitionRegistry 接口) 建立一個 beanDefinitionReader
  • 裝載資源並 registry 資源裏面的 beanDefinition

因此整體而言就是資源的加載、加載、註冊三個步驟spring

  • 對於資源的加載能夠看看我另外一篇文章 Spring-資源加載(源碼分析)微信

  • 加載的過程則是將 Resource 對象轉爲一系列的 BeanDefinition 對象app

  • 註冊則是將 BeanDefinition 注入到 BeanDefinitionRegistry 中編輯器

組件介紹

在分析源碼流程以前咱們一塊兒先對一些重要的組件混個眼熟ide

DefaultListableBeanFactory

defaultListableBeanFactory 是整個 bean 加載的核心部分,是 bean 註冊及加載 bean 的默認實現函數

defaultListableBeanFactory 類圖
defaultListableBeanFactory 類圖

對於 AliasRegistry 能夠參考我另外一篇文章 Spring-AliasRegistry 。關於這個類咱們只要記住兩點,一個是它是一個 beanFactory、一個是它是一個 BeanDefinitionRegistry源碼分析

XmlBeanDefinitionReader

從 XML 資源文件中讀取並轉換爲 BeanDefinition 的各個功能

XmlBeanDefinitionReader 類圖
XmlBeanDefinitionReader 類圖

DocumentLoader

對 Resource 文件進行轉換、將 Resource 文件轉換爲 Document 文件

DocumentLoader 類圖
DocumentLoader 類圖

BeanDefinitionDocumentReader

讀取 Document 並向 BeanDefinitionRegistry 註冊

BeanDefinitionDocumentReader
BeanDefinitionDocumentReader

源碼分析

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 是否還有元素了,若是沒有則直接調用 ThreadLocalremove 方法。這個就是 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);  }  }  } 複製代碼

這個方法的做用很簡單、就是使用一開始咱們傳給 XmlBeanDefinitionReaderBeanDefinitionRegistry 對 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);  } 複製代碼
  1. 調用 XmlBeanDefinitionReader 的方法 loadBeanDefinitions
  2. 將 Resource 包裹成 EncodeResource
  3. 經過 ThreadLocal 判斷是否 Resource 循環依賴
  4. 使用 DocumentLoader 將 Resource 轉換爲 Document
  5. 使用 BeanDefinitionDocumentReader 解釋 Document 的標籤
  6. 解釋 Spring 提供的默認標籤/自定義的標籤解釋
    • 解釋 import 標籤的時候會回調到步驟2中
    • 解釋 alias 標籤會向 AliasRegistry 註冊
    • 解釋 bean 標籤會向 BeanDefinitionRegistry 註冊 beanName 和 BeanDefinition ,也會註冊 bean 標籤裏面 id 和 name 的關係(其實就是 alias )

大體的流程就是如此了,面試的時候大體說出 XmlBeanDefinitionReader,DocumentLoader,BeanDefinitionDocumentReader,BeanDefinitionRegistry,AliasRegistry 這幾個組件,面試官大機率會認爲你是真的看過 Spring 的這部分代碼的

往期相關文章

Spring-資源加載(源碼分析)

Spring-AliasRegistry

編譯Spring5.2.0源碼

此次必定?
此次必定?

本文使用 mdnice 排版

相關文章
相關標籤/搜索