spring之旅第二篇-spring IOC概念及原理分析

来源:https://www.cnblogs.com/yuanqinnan/archive/2019/01/21/10301499.html
-Advertisement-
Play Games

一、IOC概念 上一篇已經瞭解了spring的相關概念,並且創建了一個spring項目。spring中有最重要的兩個概念:IOC和AOP,我們先從IOC入手。 IOC全稱Inversion of Control,中文通常翻譯為“控制反轉”,這其實不是一種技術,而是一種思想。 簡單理解就是把原先我們代 ...


一、IOC概念

上一篇已經瞭解了spring的相關概念,並且創建了一個spring項目。spring中有最重要的兩個概念:IOCAOP,我們先從IOC入手。

IOC全稱Inversion of Control,中文通常翻譯為“控制反轉”,這其實不是一種技術,而是一種思想。

簡單理解就是把原先我們代碼裡面需要實現的對象創建、依賴的代碼,反轉給容器來幫忙實現。

這裡分享Iteye的開濤對Ioc的精彩講解

地址:https://jinnianshilongnian.iteye.com/blog/1413846


 

IoC是什麼

  Ioc—Inversion of Control,即“控制反轉”,不是什麼技術,而是一種設計思想。在Java開發中,Ioc意味著將你設計好的對象交給容器控制,而不是傳統的在你的對象內部直接控制。如何理解好Ioc呢?理解好Ioc的關鍵是要明確“誰控制誰,控制什麼,為何是反轉(有反轉就應該有正轉了),哪些方面反轉了”,那我們來深入分析一下:

  ●誰控制誰,控制什麼:傳統Java SE程式設計,我們直接在對象內部通過new進行創建對象,是程式主動去創建依賴對象;而IoC是有專門一個容器來創建這些對象,即由Ioc容器來控制對 象的創建;誰控制誰?當然是IoC 容器控制了對象;控制什麼?那就是主要控制了外部資源獲取(不只是對象包括比如文件等)。

  ●為何是反轉,哪些方面反轉了:有反轉就有正轉,傳統應用程式是由我們自己在對象中主動控制去直接獲取依賴對象,也就是正轉;而反轉則是由容器來幫忙創建及註入依賴對象;為何是反轉?因為由容器幫我們查找及註入依賴對象,對象只是被動的接受依賴對象,所以是反轉;哪些方面反轉了?依賴對象的獲取被反轉了。

 用圖例說明一下,傳統程式設計如圖1-1,都是主動去創建相關對象然後再組合起來:

圖1-1 傳統應用程式示意圖

當有了IoC/DI的容器後,在客戶端類中不再主動去創建這些對象了,如圖2-2所示:

圖1-2有IoC/DI容器後程式結構示意圖

IoC能做什麼

IoC 不是一種技術,只是一種思想,一個重要的面向對象編程的法則,它能指導我們如何設計出松耦合、更優良的程式。傳統應用程式都是由我們在類內部主動創建依賴對象,從而導致類與類之間高耦合,難於測試;有了IoC容器後,把創建和查找依賴對象的控制權交給了容器,由容器進行註入組合對象,所以對象與對象之間是 鬆散耦合,這樣也方便測試,利於功能復用,更重要的是使得程式的整個體繫結構變得非常靈活。

  其實IoC對編程帶來的最大改變不是從代碼上,而是從思想上,發生了“主從換位”的變化。應用程式原本是老大,要獲取什麼資源都是主動出擊,但是在IoC/DI思想中,應用程式就變成被動的了,被動的等待IoC容器來創建並註入它所需要的資源了。

  IoC很好的體現了面向對象設計法則之一—— 好萊塢法則:“別找我們,我們找你”;即由IoC容器幫對象找相應的依賴對象並註入,而不是由對象主動去找。

IoC和DI

DI—Dependency Injection,即“依賴註入”組件之間依賴關係由容器在運行期決定,形象的說,即由容器動態的將某個依賴關係註入到組件之中依賴註入的目的並非為軟體系統帶來更多功能,而是為了提升組件重用的頻率,併為系統搭建一個靈活、可擴展的平臺。通過依賴註入機制,我們只需要通過簡單的配置,而無需任何代碼就可指定目標需要的資源,完成自身的業務邏輯,而不需要關心具體的資源來自何處,由誰實現。

  理解DI的關鍵是:“誰依賴誰,為什麼需要依賴,誰註入誰,註入了什麼”,那我們來深入分析一下:

  ●誰依賴於誰:當然是應用程式依賴於IoC容器

  ●為什麼需要依賴:**應用程式需要IoC容器來提供對象需要的外部資源**;

  ●誰註入誰:很明顯是IoC容器註入應用程式某個對象,應用程式依賴的對象

  ●註入了什麼:就是註入某個對象所需要的外部資源(包括對象、資源、常量數據)

  IoC和DI由什麼關係呢?其實它們是同一個概念的不同角度描述,由於控制反轉概念比較含糊(可能只是理解為容器控制對象這一個層面,很難讓人想到誰來維護對象關係),所以2004年大師級人物Martin Fowler又給出了一個新的名字:“依賴註入”,相對IoC 而言,“**依賴註入”明確描述了“被註入對象依賴IoC容器配置依賴對象”。


相信通過上面的文章,對IOC的理解會更深。下麵講講三種依賴註入的方式

構造方法註入

顧名思義,構造方法註入,就是被註入對象可以通過在其構造方法中聲明依賴對象的參數列表, 讓外部(通常是IoC容器)知道它需要哪些依賴對象

public classA(IinterfaceA a,IinterfaceB b){
    this.a=a;
    this.b=b;
}

構造方法註入方式比較直觀,對象被構造完成後,即進入就緒狀態,可以馬上使用。

setter 方法註入

對於JavaBean對象來說,通常會通過setXXX()和getXXX()方法來訪問對應屬性。這些setXXX()方法統稱為setter方法,getXXX()當然就稱為getter方法。

public class classB(){
    private IinterfaceA a;
    private IinterfaceB b;
    public IinterfaceA getIinterfaceA(){
        return a;
    }
    public void setIinterfaceA(IinterfaceA a){
        this.a=a;
    }
    public IinterfaceB getIinterfaceB(){
        return b;
    }
    public void setIinterfaceB(IinterfaceB b){
        this.b=b;
    }
}

介面註入  

相對於前兩種註入方式來說,介面註入沒有那麼簡單明瞭。被註入對象如果想要IoC Service Provider為其註入依賴對象,就必須實現某個介面。這個介面提供一個方法,用來為其註入依賴對象。IoC Service Provider最終通過這些介面來瞭解應該為被註入對象註入什麼依賴對象。

創建Person (被註入對象)要實現的介面

interface UserInject{
        void injectUser(User user);//這裡必須 是被註入對象依賴的對象
    }

Person 對象實現介面

class Person implements UserInject{ 
    private User user; public Person(){} 
    @Override 
    public void injectUser(User user)
    {
        this.user = user;//實現註入方法,外部通過此方法給此對象註入User對象
    }
}

外部調injectUser方法為Persion對象註入User對象,此即介面註入

三種註入方式的比較

  • 介面註入。從註入方式的使用上來說,介面註入是現在不甚提倡的一種方式,基本處於“退役狀態”。因為它強制被註入對象實現不必要的介面,帶有侵入性。而構造方法註入和setter方法註入則不需要如此。

  • 構造方法註入。這種註入方式的優點就是,對象在構造完成之後,即已進入就緒狀態,可以 9馬上使用。缺點就是,當依賴對象比較多的時候,構造方法的參數列表會比較長。而通過反射構造對象的時候,對相同類型的參數的處理會比較困難,維護和使用上也比較麻煩。而且在Java中,構造方法無法被繼承,無法設置預設值。對於非必須的依賴處理,可能需要引入多個構造方法,而參數數量的變動可能造成維護上的不便。

  • setter方法註入。因為方法可以命名,所以setter方法註入在描述性上要比構造方法註入好一些。 另外,setter方法可以被繼承,允許設置預設值,而且有良好的IDE支持。缺點當然就是對象無法在構造完成後馬上進入就緒狀態。

    綜上所述,構造方法註入和setter方法註入因為其侵入性較弱,且易於理解和使用,所以是現在使用最多的註入方式;而介面註入因為侵入性較強,近年來已經不流行了。

二、源碼分析

在學習spring的具體配置之前,先瞭解下源碼的基本結構。上一篇的測試代碼

ApplicationContext ctx=new ClassPathXmlApplicationContext("META-INF/applicationContext.xml");
  //獲取bean的實例
HelloWorld t=(HelloWorld) ctx.getBean("hello");

我們大致分析下過程:

  1. 通過Resource對象載入配置文件

  2. 解析配置文件,得到bean

  3. 解析bean,id作為bean的名字,class用於反射得到bean的實例(Class.forName(className));

  4. 調用getBean的時候,從容器中返回對象實例。

當然這隻是簡單的理解,IOC核心內容是beanFactory與ApplicationContext

BeanFactory

BeanFactory 是 Spring 的“心臟”。它就是 Spring IoC 容器的真面目。Spring 使用 BeanFactory 來實例化、配置和管理 Bean,BeanFactory有著龐大的繼承、實現體系,有眾多的子介面、實現類。

  1. BeanFactory作為一個主介面不繼承任何介面,暫且稱為一級介面

  2. 有3個子介面繼承了它,進行功能上的增強。這3個子介面稱為二級介面

  3. ConfigurableBeanFactory可以被稱為三級介面,對二級介面HierarchicalBeanFactory進行了再次增強,它還繼承了另一個外來的介面SingletonBeanRegistry

  4. ConfigurableListableBeanFactory是一個更強大的介面,繼承了上述的所有介面,無所不包,稱為四級介面。(這4級介面是BeanFactory的基本介面體系。繼續,下麵是繼承關係的2個抽象類和2個實現類:)

  5. AbstractBeanFactory作為一個抽象類,實現了三級介面ConfigurableBeanFactory大部分功能。

  6. AbstractAutowireCapableBeanFactory同樣是抽象類,繼承自AbstractBeanFactory,並額外實現了二級介面AutowireCapableBeanFactory

  7. DefaultListableBeanFactory繼承自AbstractAutowireCapableBeanFactory,實現了最強大的四級介面ConfigurableListableBeanFactory,並實現了一個外來介面BeanDefinitionRegistry,它並非抽象類。

  8. 最後是最強大的XmlBeanFactory,繼承自DefaultListableBeanFactory,重寫了一些功能,使自己更強大。

最基本的IOC容器介面BeanFactory

public interface BeanFactory {

    /**
     * 用來引用一個實例,或把它和工廠產生的Bean區分開,就是說,如果一個FactoryBean的名字為a,那麼,&a會得到那個Factory
     */
    String FACTORY_BEAN_PREFIX = "&";

    /*
     * 四個不同形式的getBean方法,獲取實例
     */
    //根據bean的名字,獲取在IOC容器中得到bean實例   
    Objecpublic interface BeanFactory {

    /**
     * 用來引用一個實例,或把它和工廠產生的Bean區分開,就是說,如果一個FactoryBean的名字為a,那麼,&a會得到那個Factory
     */
    String FACTORY_BEAN_PREFIX = "&";

    /*
     * 四個不同形式的getBean方法,獲取實例
     */
    //根據bean的名字,獲取在IOC容器中得到bean實例   
    Object getBean(String name) throws BeansException;
   //根據bean的名字和Class類型來得到bean實例,增加了類型安全驗證機制。    
    <T> T getBean(String name, Class<T> requiredType) throws BeansException;
    
    <T> T getBean(Class<T> requiredType) throws BeansException;

    Object getBean(String name, Object... args) throws BeansException;
    // 是否存在
    boolean containsBean(String name); 
    // 是否為單實例
    boolean isSingleton(String name) throws NoSuchBeanDefinitionException;
   // 是否為原型(多實例)
    boolean isPrototype(String name) throws NoSuchBeanDefinitionException;
    // 名稱、類型是否匹配
    boolean isTypeMatch(String name, Class<?> targetType)
            throws NoSuchBeanDefinitionException;
    //得到bean實例的Class類型
    Class<?> getType(String name) throws NoSuchBeanDefinitionException; 

    String[] getAliases(String name);// 根據實例的名字獲取實例的別名 getBean(String name) throws BeansException;
   //根據bean的名字和Class類型來得到bean實例,增加了類型安全驗證機制。    
    <T> T getBean(String name, Class<T> requiredType) throws BeansException;
    
    <T> T getBean(Class<T> requiredType) throws BeansException;

    Object getBean(String name, Object... args) throws BeansException;
    // 是否存在
    boolean containsBean(String name); 
    // 是否為單實例
    boolean isSingleton(String name) throws NoSuchBeanDefinitionException;
   // 是否為原型(多實例)
    boolean isPrototype(String name) throws NoSuchBeanDefinitionException;
    // 名稱、類型是否匹配
    boolean isTypeMatch(String name, Class<?> targetType)
            throws NoSuchBeanDefinitionException;
    //得到bean實例的Class類型
    Class<?> getType(String name) throws NoSuchBeanDefinitionException; 

    String[] getAliases(String name);// 根據實例的名字獲取實例的別名

BeanFactory介面只是做了最基本的定義,裡面不管如何定義和載入,只關心如何得到對象,要知道如何得到對象,必須看具體的實現類,其中XmlBeanFactory就是針對最基本的ioc容器的實現。

public class XmlBeanFactory extends DefaultListableBeanFactory {
    private final XmlBeanDefinitionReader reader;

    public XmlBeanFactory(Resource resource) throws BeansException {
        this(resource, (BeanFactory)null);
    }

    public XmlBeanFactory(Resource resource, BeanFactory parentBeanFactory) throws BeansException {
        super(parentBeanFactory);
        this.reader = new XmlBeanDefinitionReader(this);
        this.reader.loadBeanDefinitions(resource);
    }
}

使用:

//根據Xml配置文件創建Resource資源對象,該對象中包含了BeanDefinition的信息
 Resource resource = new ClassPathResource("META-INF/applicationContext.xml");
 //創建XmlBeanDefinitionReader讀取器,用於載入BeanDefinition。之所以需要BeanFactory作為參數,是因為會將讀取的信息回調配置給factory
 BeanFactory beanFactory = new XmlBeanFactory(resource);
 HelloWorld helloWorld = beanFactory.getBean("hello",HelloWorld.class);
 System.out.println(helloWorld.getInfo());

ApplicationContext

ApplicationContext是Spring提供的一個高級的IoC容器,它除了能夠提供IoC容器的基本功能外,還為用戶提供了以下的附加服務。

  1. 支持信息源,可以實現國際化。(實現MessageSource介面)

  2. 訪問資源。(實現ResourcePatternResolver介面)

  3. 支持應用事件。(實現ApplicationEventPublisher介面)

兩者的區別

1.BeanFactroy採用的是延遲載入形式來註入Bean的,即只有在使用到某個Bean時(調用getBean()),才對該Bean進行載入實例化,這樣,我們就不能發現一些存在的Spring的配置問題。而ApplicationContext則相反,它是在容器啟動時,一次性創建了所有的Bean。這樣,在容器啟動時,我們就可以發現Spring中存在的配置錯誤。 相對於基本的BeanFactory,ApplicationContext 唯一的不足是占用記憶體空間。當應用程式配置Bean較多時,程式啟動較慢。

BeanFacotry延遲載入,如果Bean的某一個屬性沒有註入,BeanFacotry載入後,直至第一次使用調用getBean方法才會拋出異常;而ApplicationContext則在初始化自身是檢驗,這樣有利於檢查所依賴屬性是否註入;所以通常情況下我們選擇使用 ApplicationContext。 應用上下文則會在上下文啟動後預載入所有的單實例Bean。通過預載入單實例bean ,確保當你需要的時候,你就不用等待,因為它們已經創建好了。

2.BeanFactory和ApplicationContext都支持BeanPostProcessor、BeanFactoryPostProcessor的使用,但兩者之間的區別是:BeanFactory需要手動註冊,而ApplicationContext則是自動註冊。(Applicationcontext比 beanFactory 加入了一些更好使用的功能。而且 beanFactory 的許多功能需要通過編程實現而 Applicationcontext 可以通過配置實現。比如後處理 bean , Applicationcontext 直接配置在配置文件即可而 beanFactory 這要在代碼中顯示的寫出來才可以被容器識別。 )

3.beanFactory主要是面對與 spring 框架的基礎設施,面對 spring 自己。而 Applicationcontex 主要面對與 spring 使用的開發者。基本都會使用 Applicationcontex 並非 beanFactory 。

spring 的IOC實現當然不止這些,這些以後再學,推薦一篇大牛寫的博客:https://www.cnblogs.com/ITtangtang/p/3978349.html

看完這些相信對spring IOC概念及其實現會有了一些理性認識了,這裡面參考了很多園子里大神的文字,下一篇開始學習spring的配置


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

-Advertisement-
Play Games
更多相關文章
  • 程式的三種基本結構 複合語句 將若幹個C語句使用花括弧{ }包括起來形成複合語句。花括弧內可以包含任何C語句, 其一般形式為: { 語句1; 語句2; …… 語句n; } 1.if條件分支語句 if語句有三種語法形式,構成三種分支結構。 1)流程(單選控制結構) 語句形式如下: if (表達式)語句 ...
  • 關於博客訪問量的問題,影響因素有很多,例如你的權重,你的博客數量,包括你的標題是否吸引人都是一個衡量的標準。 這些東西需要的是日積月累,今天我們從其中的一個維度入手:發帖時間。相信大家都明白,不論是csdn,博客園這種技術博客 還是今日頭條百度貼吧或者抖音快手這種娛樂論壇,都有自己的線上高峰期。例如 ...
  • 大概的樣子 這是大致的樣子~ 寫之前想說的 思路 這裡有一個問題,就是在使用ssh的時候會有一個等待用戶輸入密碼的過程。不能直接一條命令鏈接ssh。我們可以通過 expect 來解決。 不懂的可以百度一下,很簡單的。O(∩_∩)O哈哈~ 開始寫 首先 首先解決ssh等待輸入密碼的問題,首先 我們要安 ...
  • 1. 虎嗅網文章數據 寫在前面 今天繼續使用 爬取數據,很不幸,虎嗅資訊網被我選中了,網址為 爬的就是它的資訊頻道,本文章僅供學習交流使用,切勿用作其他用途。 常規操作,分析待爬取的頁面 拖拽頁面到最底部,會發現一個 按鈕,點擊之後,抓取一下請求,得到如下地址 2. 虎嗅網文章數據 分析請求 查閱該 ...
  • 背景:併發知識是一個程式員段位升級的體現,同樣也是進入BAT的必經之路,有必要把併發知識重新梳理一遍。 ConcurrentHashMap:在有了併發的基礎知識以後,再來研究concurrent包。普通的HashMap為非線程安全的,在高併發場景下要使用線程安全版本的ConcurrentHashMa ...
  • 一、裝飾器 二、迭代器 三、生成器 四、面向過程編程 xxxx ...
  • 剛剛開始學習PHP時,一直使用phpstudy,後面發現很多東西自己單獨配置安裝會理解更深刻,所以自己總結了一下windows下開發環境的部署教程。 以前經常在CSDN和博客園看別人的教程,今天才註冊博客園帳號,開通博客功能,第一次在網上分享自己的經驗,寫的不好的地方歡迎大家指正。 安裝Nginx ...
  • 文件操作 文件操作也是編程中需要熟練掌握的技能,尤其是在後臺介面編寫和數據分析過程中,對各種類型的文件進行操作,獲取文件信息或者對信息進行存儲是十分重要的。本篇博客中將主要對常見的文本格式文件和Excel文件的相關操作進行介紹。 一、文本文件 1.1 文件操作流程 1、打開文件,獲得文件句柄,並賦值 ...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...