Spring IoC 概述
IoC:Inverse of Control(控制反轉)
控制反轉不是一種技術,而是一種思想。
既然說是反轉就說先明白什么是正,什么是反
- 正控:就是我們平時最常見的那種使用形式,要使用某個對象,需要自己去負責對象的創建,屬於自力更生。
- 反控:若要使用某個對象,無需自己創建,只需要從IoC容器中去獲取,創建對象的過程交給Spring來處理,Spring來維護這個IoC容器,屬於富二代,需要啥找管家。
所謂控制反轉,就是把原先我們代碼里面需要實現的對象創建、依賴的代碼,通過描述反轉給容器來幫忙實現。
在 Java 中可以是 XML 或者注解,通過Spring去產生或獲取特定對象
Spring 會提供 IoC 容器來管理和容納我們所開發的各種各樣的 Bean,並且我們可以從中獲取各種發布在 Spring IoC 容器里的 Bean,並且通過描述可以得到它。
一個例子
比如我想要一個機器人,我有兩種方法:
- 造出來
- 買過來

正控的方式其實就是需要機器人的時候自己造,顯然它不如買的方便,開銷也不見得比買的小,造出來的未必比買的好。
IoC其實就相當於是買,這個過程中我們沒有去創造機器人,但是最終也得到了機器人,而且大概率要比我們自己造的好。
最終的結果都是得到機器人,關鍵的區別就在於,機器人是誰創造的。如果我們還需要其他的東西例如、汽車、電視等等,自己就造就顯得很蠢,找人買顯然更聰明。
Spring IoC 的好處
當上面的例子作用於龐雜的軟件工程中的時候,自己造的方式顯然是難以維護的。
好處顯而易見:
- 降低對象之間的耦合
- 不需要理解一個類的具體實現,直接向 IoC 容器拿
IoC實例
ClassPathXmlApplicationContext是ApplicationContext的子類,ApplicationContext下面有所介紹
我們先來看一個最簡單例子
- 創建一個實體類
public class Source {
private String taste;
private String sugar;
private String size;
/**setter and getter **/
}
- 先在
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>
- 上面定義了一個 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容器
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 的定義和初始化是兩個步驟
定義:
-
Resource 定位
Spring IoC 容器先根據開發者的配置,進行資源的定位,通過 XML 或者注解,定位的內容是由開發者提供的。 -
BeanDefinition 的載入
這個時候只是將 Resource 定位到的信息,保存到 Bean 定義(BeanDefinition)中,此時並不會創建 Bean 的實例 -
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容器
簡單聊一下,大概就分成三步:
- 讀取注解或者配置文件,查看依賴,拿到類名
- 使用反射,基於類名實例化對應的對象實例
- 將對象實例,通過構造函數或者 setter,傳遞出去
Spring的IoC大致就是這樣一個路數,這其中還有更多的細節,但大致的思路就是如此。
SpringBoot中IoC的使用案例
在Spring Boot當中我們主要是通過注解來裝配Bean到Spring IoC容器中。
- 首先定義一個Java簡單對象
public class User {
private Long id;
private String userName;
private String note;
/**setter and getter **/
}
- 再定義一個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;
}
}
- 調用測試
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
對對象的控制。