輕鬆理解 Spirng IoC/控制反轉

Spring IoC 概述

IoC:Inverse of Control(控制反轉)

控制反轉不是一種技術,而是一種思想

既然說是轉就說先明白什麼是,什麼是

  • 正控:就是我們平時最常見的那種使用形式,要使用某個對象,需要自己去負責對象的創建,屬於自力更生。
  • 反控:若要使用某個對象,無需自己創建,只需要從IoC容器中去獲取,創建對象的過程交給Spring來處理,Spring來維護這個IoC容器,屬於富二代,需要啥找管家。

所謂控制反轉,就是把原先我們代碼裏面需要實現的對象創建、依賴的代碼,通過描述反轉給容器來幫忙實現。

在 Java 中可以是 XML 或者註解,通過Spring去產生或獲取特定對象

Spring 會提供 IoC 容器來管理和容納我們所開發的各種各樣的 Bean,並且我們可以從中獲取各種發佈在 Spring IoC 容器裏的 Bean,並且通過描述可以得到它。

一個例子

比如我想要一個機器人,我有兩種方法:

  • 造出來
  • 買過來
image-20210411164802090

正控的方式其實就是需要機器人的時候自己造,顯然它不如買的方便,開銷也不見得比買的小,造出來的未必比買的好。

IoC其實就相當於是買,這個過程中我們沒有去創造機器人,但是最終也得到了機器人,而且大概率要比我們自己造的好。

最終的結果都是得到機器人,關鍵的區別就在於,機器人是誰創造的。如果我們還需要其他的東西例如、汽車、電視等等,自己就造就顯得很蠢,找人買顯然更聰明。

Spring IoC 的好處

當上面的例子作用於龐雜的軟件工程中的時候,自己造的方式顯然是難以維護的。

好處顯而易見:

  • 降低對象之間的耦合
  • 不需要理解一個類的具體實現,直接向 IoC 容器拿

IoC實例

ClassPathXmlApplicationContext是ApplicationContext的子類,ApplicationContext下面有所介紹

我們先來看一個最簡單例子

  1. 創建一個實體類
public class Source {
	private String taste;
	private String sugar;
  private String size;
  
  /**setter and getter **/
}
  1. 先在src目錄下創建一個 bean.xml 文件:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
    <!-- 通過 xml 方式裝配 bean -->
    <bean name="source" class="pojo.Source">
        <property name="taste" value="蘋果味"/>
        <property name="sugar" value="糖"/>
        <property name="size" value="中杯"/>
    </bean>
</beans>
  1. 上面定義了一個 bean ,這樣 Spring IoC 容器在初始化的時候就能找到它們,然後使用 ClassPathXmlApplicationContext 容器就可以將其初始化:
ApplicationContext context = new ClassPathXmlApplicationContext("bean.xml");
Source source = (Source) context.getBean("source", Source.class);

System.out.println(source.getTaste());
System.out.println(source.getSugar());
System.out.println(source.getSize());

這樣就會使用 Application 的實現類 ClassPathXmlApplicationContext 去初始化 Spring IoC 容器,然後開發者就可以通過 IoC 容器來獲取資源了。

bean的裝配還可以通過註解的方式,SpringBoot中常用註解來裝配,文末有案例

Spring IoC 容器的設計

設計

Spring Bean的創建是典型的工廠模式,這一系列的Bean工廠,也即IOC容器爲開發者管理對象間的依賴關係提供了很多便利和基礎服務。

Spring提供了多個IoC容器可供使用。

Spring IoC 容器的設計主要是基於以下兩個接口:

  • BeanFactory(Spring IoC 容器所定義的最底層接口)
  • ApplicationContext

ApplicationContext 是 BeanFactory 的子接口之一,並對 BeanFactory 功能做了許多的擴展大多數時候,都是使用它來作爲IoC容器

img

BeanFactory

  • 是Spring中最底層的接口,只提供了最簡單的IoC功能,負責配置,創建和管理bean。
  • 在應用中,一般不使用 BeanFactory,而推薦使用ApplicationContext(應用上下文)。

從上圖可以看到, BeanFactory 位於設計的最底層,它提供了 Spring IoC 最底層的設計,下面簡單說下它所定義的方法

  • getBean() 對應了多個方法來獲取配置給 Spring IoC 容器的 Bean。
    • 按照類型拿 bean(有多個type Spring 就懵了,不知道該獲取哪一個)
    • 按照 bean 的名字拿 bean
    • 按照名字和類型拿 bean(推薦)

單例就是無論獲取多少次,都是同一個對象。原型就是每次獲取都是一個新創建的對象。一般默認是單例的

  • isSingleton() 用於判斷是否單例
  • isPrototype() 用於判斷是否爲原型
  • isTypeMatch() 是否匹配類型
  • getType() 獲取bean的類型
  • getAliases()方法是獲取別名的方法
  • containsBean() 是否包含bean

所有關於 Spring IoC 的容器將會遵守BeanFactory所定義的方法。

ApplicationContext

根據 ApplicationContext 的類繼承關係圖,可以看到 ApplicationContext 接口擴展了許許多多的接口,因此它的功能十分強大。

  • 繼承了 BeanFactory,擁有了基本的 IoC 功能;
  • 支持國際化;
  • 支持消息機制;
  • 支持統一的資源加載;
  • 支持AOP功能;

ApplicationContext 常見實現類:

1.ClassPathXmlApplicationContext

讀取classpath中的資源

ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");

2:FileSystemXmlApplicationContext

讀取指定路徑的資源

ApplicationContext ac = new FileSystemXmlApplicationContext("/projact/applicationContext.xml");

3.XmlWebApplicationContext
需要在Web的環境下才可以運行

XmlWebApplicationContext ac = new XmlWebApplicationContext(); // 這時並沒有初始化容器
ac.setServletContext(servletContext); // 需要指定ServletContext對象
ac.setConfigLocation("/WEB-INF/applicationContext.xml"); // 指定配置文件路徑,開頭的斜線表示Web應用的根目錄
ac.refresh(); // 初始化容器

Bean的定義與初始化

Bean 的定義和初始化是兩個步驟

定義:

  1. Resource 定位
    Spring IoC 容器先根據開發者的配置,進行資源的定位,通過 XML 或者註解,定位的內容是由開發者提供的。

  2. BeanDefinition 的載入
    這個時候只是將 Resource 定位到的信息,保存到 Bean 定義(BeanDefinition)中,此時並不會創建 Bean 的實例

  3. BeanDefinition 的註冊

    這個過程就是將 BeanDefinition 的信息發佈到 Spring IoC 容器中,此時仍然沒有對應的 Bean 的實例。

做完了以上 3 步,Bean 就在 Spring IoC 容器中被定義了,但是沒有被初始化、沒有完成依賴注入,它還不能使用。

初始化和依賴注入:

Bean 有一個配置選項——lazy-init,作用在於是否懶加載。

懶加載的意思就是,如果不獲取它,就不創建實例,當獲取它時才創建實例。通俗理解就是,不到飯點不起牀。

在沒有任何配置的情況下,它的默認值爲 false,也就是 默認會自動初始化 Bean

如果將其設置爲 true,那麼只有當我們使用 Spring IoC 容器的 getBean() 方法獲取它時,它纔會進行 Bean 的初始化,完成依賴注入。


依賴注入(DI)

依賴注入就是將實例變量傳入到一個對象中去(Dependency injection means giving an object its instance variables)。

什麼是依賴

例如

public class Human {
    ...
    Father father;
    ...
    public Human() {
        father = new Father();
    }
}

上面這段代碼中,Human就是依賴於Father,如果Father出錯,Human類就無法正常運行。

什麼是依賴注入

上面將依賴在構造函數中直接初始化是一種硬編碼,弊端在於兩個類不夠獨立。

public class Human {
    ...
    Father father;
    ...
    public Human(Father father) {
        this.father = father;
    }
}

我們將 father 對象作爲構造函數的一個參數傳入,在調用 Human 的構造方法之前外部就已經初始化好了 Father 對象。

這種非自己主動初始化依賴,而通過外部來傳入依賴的方式,我們就稱爲依賴注入。

這種方式的好處顯而易見,那就是降低了耦合。

IoC和DI的關係

有些人會把控制反轉和依賴注入等同,但實際上它們有着本質上的不同。

  • 控制反轉是一種思想
  • 依賴注入是一種設計模式

IoC框架使用依賴注入作爲實現控制反轉的方式,Spring中的IoC就是使用DI的方式來實現的

但控制反轉不止這一種實現方式,只是這種應用的更爲廣泛。

如何自己實現一個的IoC容器

簡單聊一下,大概就分成三步:

  1. 讀取註解或者配置文件,查看依賴,拿到類名
  2. 使用反射,基於類名實例化對應的對象實例
  3. 將對象實例,通過構造函數或者 setter,傳遞出去

Spring的IoC大致就是這樣一個路數,這其中還有更多的細節,但大致的思路就是如此。

SpringBoot中IoC的使用案例

在Spring Boot當中我們主要是通過註解來裝配Bean到Spring IoC容器中。

  1. 首先定義一個Java簡單對象
public class User {
    private Long id;
    private String userName;
    private String note;

    /**setter and getter **/
}
  1. 再定義一個Java配置文件
    • @Configuration代表這是一個Java配置文件,Spring的容器會根據它來生成IoC容器去裝配Bean;
    • @Bean代表將initUser()方法返回的POJO裝配到IoC容器中,而其屬性name定義這個Bean的名稱,如果沒有配置它,則將方法名稱“initUser”作爲Bean的名稱保存到Spring IoC容器中。
@Configuration
public class AppConfig {
    @Bean(name = "user")
    public User initUser() {
        User user = new User();
        user.setId(1L);
        user.setUserName("user_name_1");
        user.setNote("note_1");
        return user;
    }
}
  1. 調用測試
public class IoCTest {
    private static Logger log = Logger.getLogger(IoCTest.class);
    public static void main(String[] args) {
        ApplicationContext ctx 
             = new AnnotationConfigApplicationContext(AppConfig.class);
        User user = ctx.getBean(User.class);
        log.info(user.getId());
    }
}
......
17:53:03.018 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Returning cached instance of singleton bean 'user'
17:53:03.018 [main] INFO com.springboot.chapter3.config.IoCTest - 1

顯然,配置在配置文件中的名稱爲user的Bean已經被裝配到IoC容器中,並且可以通過getBean()方法獲取對應的Bean,並將Bean的屬性信息輸出出來。

當然這只是很簡單的方法,而註解@Bean也不是唯一創建Bean的方法,還有其他的方法可以讓IoC容器裝配Bean

總結

IoC不是什麼技術,而是一種設計思想。在 Spring 開發中,由 IOC 容器控制對象的創建、初始化、銷燬等。這也就實現了對象控制權的反轉,由我們對對象的控制轉變成了Spring IOC 對對象的控制

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章