通俗理解spring源碼(二)—— 資源定位與載入

来源:https://www.cnblogs.com/xiaohang123/archive/2020/04/14/12689314.html
-Advertisement-
Play Games

通俗理解spring源碼(二)—— 資源定位與載入 最開始學習spring的時候,一般都是這樣用: ApplicationContext context = new ClassPathXmlApplicationContext("spring.xml"); User user = (User)con ...


通俗理解spring源碼(二)—— 資源定位與載入

最開始學習spring的時候,一般都是這樣用:

        ApplicationContext context = new ClassPathXmlApplicationContext("spring.xml");
        User user = (User)context.getBean("user");

這裡的ApplicationContext也是一個容器,只不過是引用了一個DefaultListableBeanFactory,暫時先不用管,真正的容器還是DefaultListableBeanFactory,我們還是以DefaultListableBeanFactory的初始化為例,可以這樣寫:

public static void main(String[] args) {
DefaultListableBeanFactory factory = new DefaultListableBeanFactory();
BeanDefinitionReader reader = new XmlBeanDefinitionReader(factory);
Resource resource = new ClassPathResource("spring.xml");
reader.loadBeanDefinitions(resource);
User user = (User)factory.getBean("user");
}

1、DefaultListableBeanFactory實例化

  • new DefaultListableBeanFactory()這步操作實例化了一個工廠對象,初始化了一個容器,最終所有的bean都會放到這個容器中。
  • 在它的構造器中,首先調用父類構造:
    public DefaultListableBeanFactory() {
        super();
    }
  • 進入抽象類AbstractAutowireCapableBeanFactory中,AbstractAutowireCapableBeanFactory是DefaultListableBeanFactory的抽象父類,不記得的可以看看我上一篇博文,有個大致印象即可。在該抽象類構造中:
    public AbstractAutowireCapableBeanFactory() {
        super();
        //添加忽略給定介面的自動裝配功能
        ignoreDependencyInterface(BeanNameAware.class);
        ignoreDependencyInterface(BeanFactoryAware.class);
        ignoreDependencyInterface(BeanClassLoaderAware.class);
    }
  • 這裡的super(),可以點進去看看,沒有任何操作。
  • ignoreDependencyInterface(),表示添加忽略給定介面的自動裝配功能,暫不做詳細介紹,大家可以參考這篇文章

2、Resource資源封裝

  • spring將所有的資源都封裝成一個Resource,包括文件系統資源(FileSystemResource)、classpath資源(ClassPathResource)、url資源(UrlResource)等。
  • Resource介面定義了獲取當前資源屬性的方法,如存在性(Exists)、可讀性(isReadable)、是否處於打開狀態(isOpen)等。
    • public interface InputStreamSource {
      InputStream getInputStream() throws IOException;
      }
      public interface Resource extends InputStreamSource {
      
          boolean exists();
          default boolean isReadable() {
              return exists();
          }
          default boolean isOpen() {
              return false;
          }
          default boolean isFile() {
              return false;
          }
          URL getURL() throws IOException;
          URI getURI() throws IOException;
          File getFile() throws IOException; 
          default ReadableByteChannel readableChannel() throws IOException {
              return Channels.newChannel(getInputStream());
          }
          long contentLength() throws IOException;
          long lastModified() throws IOException;
          Resource createRelative(String relativePath) throws IOException;
          @Nullable
          String getFilename();
          String getDescription(); 

2、XmlBeanDefinitionReader實例化

  • xml配置文件的讀取是spring中最重要的功能,因為spring的大部分功能都是以配置作為切入點的。
  • XmlBeanDefinitionReader負責讀取和解析已封裝好xml配置文件的Resource,即ClassPathResource。

  • 這裡先大致瞭解一下spring的資源載入體系:

    • ResourceLoader:定義資源載入器,主要應用於根據給定的資源文件地址返回對應的Resource,如根據不同的首碼“file:”“http:”“jar:”等判斷不同的資源。
    • BeanDefinitionReader:主要定義資源文件讀取並轉換為BeanDefinition的各個方法。BeanDefinition就是對你所定義的bean的class、id、alias、property等屬性的封裝,此時還沒有實例化bean。
    • EnvironmentCapable:定義獲取Environment方法,Environment就是對當前所激活的profile的封裝。
    • DocumentLoader:定義從資源文件載入到轉換為Document的功能。Document是對xml文檔的封裝,從中可以獲取各個節點的數據。
    • AbstractBeanDefinitionReader:對EnvironmentCapable、BeanDefinitionReader類定義的功能進行實現。
    • BeanDefinitionDocumentReader:定義讀取Document並註冊BeanDefinition功能。
    • BeanDefinitionParserDelegate:定義解析Element的各種方法。真正解析xml的就是這個類,典型的委派模式。

3、loadBeanDefinitions方法

loadBeanDefinitions是整個資源載入的切入點,下麵是該方法執行時序圖:

在該方法中會調用XmlBeanDefinitionReader的重載方法loadBeanDefinitions(Resource resource)。

public int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException {
        return loadBeanDefinitions(new EncodedResource(resource));
    }

EncodedResource是處理資源文件的編碼的,其中主要邏輯體現在getReader()方法中,我們可以在EncodedResource構造器中設置編碼,spring就會使用相應的編碼作為輸入流的編碼。

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());
        }
    }

處理完編碼後,進入到loadBeanDefinitions(new EncodedResource(resource))中:

    public int loadBeanDefinitions(EncodedResource encodedResource) throws BeanDefinitionStoreException {
        Assert.notNull(encodedResource, "EncodedResource must not be null");
        if (logger.isTraceEnabled()) {
            logger.trace("Loading XML bean definitions from " + encodedResource);
        }
        //記錄已經載入的資源
        Set<EncodedResource> currentResources = this.resourcesCurrentlyBeingLoaded.get();
        if (currentResources == null) {
            currentResources = new HashSet<>(4);
            this.resourcesCurrentlyBeingLoaded.set(currentResources);
        }
        //如果該資源已被載入,拋出異常
        if (!currentResources.add(encodedResource)) {
            throw new BeanDefinitionStoreException(
                    "Detected cyclic loading of " + encodedResource + " - check your import definitions!");
        }
        try {
            //從encodedResource中取出原來的resource,得到輸入流inputStream
            InputStream inputStream = encodedResource.getResource().getInputStream();
            try {
                //inputSource不屬於spring,是解析xml的一個工具,全路徑為org.xml.sax.InputSource
                InputSource inputSource = new InputSource(inputStream);
                if (encodedResource.getEncoding() != null) {
                    inputSource.setEncoding(encodedResource.getEncoding());
                }
                //真正的核心部分
                return doLoadBeanDefinitions(inputSource, encodedResource.getResource());
            }
            finally {
                inputStream.close();
            }
        }
        catch (IOException ex) {
            throw new BeanDefinitionStoreException(
                    "IOException parsing XML document from " + encodedResource.getResource(), ex);
        }
        finally {
            currentResources.remove(encodedResource);
            if (currentResources.isEmpty()) {
                this.resourcesCurrentlyBeingLoaded.remove();
            }
        }
    }

   首先對傳入的resource參數做封裝,目的是考慮到resource可能存在編碼要求的情況,其次,用過SAX讀取xml文件的方式離開準備InputSource對象,最後將準備的數據通過參數傳入真正的核心處理部分doLoadBeanDefinitions(inputSource, encodedResource.getResource())。

    protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource)
            throws BeanDefinitionStoreException {

        try {
            //從資源文件轉換為document對象
            Document doc = doLoadDocument(inputSource, resource);
            //解析document,並註冊beanDefiniton到工廠中
            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);
        }
    }

這個方法的核心代碼就兩句,

            //從資源文件轉換為document對象
            Document doc = doLoadDocument(inputSource, resource);
            //解析document,並註冊beanDefiniton到工廠中
            int count = registerBeanDefinitions(doc, resource);

其中registerBeanDefinitions(doc, resource)又是核心,邏輯非常複雜,先來看doLoadDocument(inputSource, resource)方法:

    protected Document doLoadDocument(InputSource inputSource, Resource resource) throws Exception {
        return this.documentLoader.loadDocument(inputSource, getEntityResolver(), this.errorHandler,
                getValidationModeForResource(resource), isNamespaceAware());
    }

這裡獲取對xml文件的驗證模式,載入xml文件,並得到對應的document,獲取xml的驗證模式將在下一篇博客中講解。

 

走的太遠,不要忘記為什麼出發!現在再來看看這段代碼:

   DefaultListableBeanFactory factory = new DefaultListableBeanFactory();
   BeanDefinitionReader reader = new XmlBeanDefinitionReader(factory);
   Resource resource = new ClassPathResource("spring.xml");
   reader.loadBeanDefinitions(resource);

 是不是清晰一些?總結如下:

  • 實例化工廠,作為bean的容器;
  • 實例化BeanDefinitionReader,負責讀取xml;
  • 將資源文件路徑封裝為對應的resource對象;
  • 調用reader.loadBeanDefinitions(resource),實際上會先將resouce轉換為document,再將document轉換為beanDefinition,這一步是整個資源載入的核心。

 參考:spring源碼深度解析


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 1 import os 2 3 # 學生系統基本功能 4 # 增刪查改 5 6 # 如何實現該系統 7 # 1.顯示系統功能界面 8 # 2.讓用戶選擇功能 9 # 3.判斷用戶選擇的指定功能,然後完成相應的操作(增刪查改) 10 11 # 面向對象分析 12 # 1.抽象類 13 # 學生系統管理 ...
  • 概述 SpringMVC 是Spring框架的一部分,屬於Spring的Web模塊,主要的核心思想就是 MVC 。是目前最主流的Web框架之一。SpringMVC使用 註解 來簡化Java Web的開發,並且支持REST風格的URL請求。具有松耦合、可插拔的組件結構,比其他的MVC框架更具擴展性和靈 ...
  • Python安裝 想要使用Django,前提是要安裝好Python軟體及運行環境,因為Django是基於Python寫的。具體Python安裝過程詳見: "Python3 環境搭建" 查看安裝的Python版本 shell $python m pip install django i https:/ ...
  • 背景 公司賣了一個產品給甲方,甲方要求部署後,要以 來訪問。甲方提供了證書信息和私鑰,記錄一下部署過程。 實現 1、思路 在我們產品伺服器上部署一個 、證書信息也放在這個伺服器上。外界的 經過 變成 協議,大致思路如下: 2、安裝過程 (1)上傳證書、私鑰到伺服器 證書 放於 ; 私鑰 放於 ; ( ...
  • 並查集最常用來判斷圖是否為連通圖,或用來求圖的連通分量數。 並查集1--<=>求連通分量個數 題目描述 某省調查城鎮交通狀況,得到現有城鎮道路統計表,表中列出了每條道路直接連通的城鎮。省政府“暢通工程”的目標是使全省任何兩個城鎮間都可以實現交通(但不一定有直接的道路相連,只要互相間接通過道路可達即可 ...
  • Redis的全稱是:Remote Dictionary.Server,本質上是一個Key-Value類型的記憶體資料庫,很像 memcached,整個資料庫統統載入在記憶體當中進行操作,定期通過非同步操作把資料庫數據flush到硬碟 上進行保存。 因為是純記憶體操作,Redis的性能非常出色,每秒可以處理超... ...
  • 美團java一面問題 自我介紹 項目相關 線程與進程的區別 進程線程間的通信方式 hashmap與concurrenthashmap的區別 資料庫的事務 資料庫索引有哪些 大文件統計每個字元串的詞頻 有什麼想問的 一面面完之後面試官讓我回去等通知,一度以為掛了,沒想到出門沒有一個小時收到了美團2面的 ...
  • 前言 雖說已經工作,並且也做了兩個項目,但總覺得自己的基礎知識不牢固,所以從頭開始學起。學習過程中的一些代碼已上傳到 "Github" 和 "碼雲" 基礎知識 認識 PHP 略。。。 安裝與配置 略。。。 語言基礎 標記風格 XML 風格 腳本風格 簡短風格 ASP 風格 如果使用簡短風格和 ASP ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...