通俗理解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
  • 前言 本文介紹一款使用 C# 與 WPF 開發的音頻播放器,其界面簡潔大方,操作體驗流暢。該播放器支持多種音頻格式(如 MP4、WMA、OGG、FLAC 等),並具備標記、實時歌詞顯示等功能。 另外,還支持換膚及多語言(中英文)切換。核心音頻處理採用 FFmpeg 組件,獲得了廣泛認可,目前 Git ...
  • OAuth2.0授權驗證-gitee授權碼模式 本文主要介紹如何筆者自己是如何使用gitee提供的OAuth2.0協議完成授權驗證並登錄到自己的系統,完整模式如圖 1、創建應用 打開gitee個人中心->第三方應用->創建應用 創建應用後在我的應用界面,查看已創建應用的Client ID和Clien ...
  • 解決了這個問題:《winForm下,fastReport.net 從.net framework 升級到.net5遇到的錯誤“Operation is not supported on this platform.”》 本文內容轉載自:https://www.fcnsoft.com/Home/Sho ...
  • 國內文章 WPF 從裸 Win 32 的 WM_Pointer 消息獲取觸摸點繪製筆跡 https://www.cnblogs.com/lindexi/p/18390983 本文將告訴大家如何在 WPF 裡面,接收裸 Win 32 的 WM_Pointer 消息,從消息裡面獲取觸摸點信息,使用觸摸點 ...
  • 前言 給大家推薦一個專為新零售快消行業打造了一套高效的進銷存管理系統。 系統不僅具備強大的庫存管理功能,還集成了高性能的輕量級 POS 解決方案,確保頁面載入速度極快,提供良好的用戶體驗。 項目介紹 Dorisoy.POS 是一款基於 .NET 7 和 Angular 4 開發的新零售快消進銷存管理 ...
  • ABP CLI常用的代碼分享 一、確保環境配置正確 安裝.NET CLI: ABP CLI是基於.NET Core或.NET 5/6/7等更高版本構建的,因此首先需要在你的開發環境中安裝.NET CLI。這可以通過訪問Microsoft官網下載並安裝相應版本的.NET SDK來實現。 安裝ABP ...
  • 問題 問題是這樣的:第三方的webapi,需要先調用登陸介面獲取Cookie,訪問其它介面時攜帶Cookie信息。 但使用HttpClient類調用登陸介面,返回的Headers中沒有找到Cookie信息。 分析 首先,使用Postman測試該登陸介面,正常返回Cookie信息,說明是HttpCli ...
  • 國內文章 關於.NET在中國為什麼工資低的分析 https://www.cnblogs.com/thinkingmore/p/18406244 .NET在中國開發者的薪資偏低,主要因市場需求、技術棧選擇和企業文化等因素所致。歷史上,.NET曾因微軟的閉源策略發展受限,儘管後來推出了跨平臺的.NET ...
  • 在WPF開發應用中,動畫不僅可以引起用戶的註意與興趣,而且還使軟體更加便於使用。前面幾篇文章講解了畫筆(Brush),形狀(Shape),幾何圖形(Geometry),變換(Transform)等相關內容,今天繼續講解動畫相關內容和知識點,僅供學習分享使用,如有不足之處,還請指正。 ...
  • 什麼是委托? 委托可以說是把一個方法代入另一個方法執行,相當於指向函數的指針;事件就相當於保存委托的數組; 1.實例化委托的方式: 方式1:通過new創建實例: public delegate void ShowDelegate(); 或者 public delegate string ShowDe ...