前言
隻有光頭才能變強
回顧前面:
在刷Spring書籍的時候花了點時間去學習了單例模式和工廠模式,總的來說還是非常值得的!
本來想的是刷完《Spring 實戰 (第4版)》和《精通Spring4.x 企業應用開發實戰》的IOC章節後來重新編寫一篇IOC的文章的,看了一下之前已經寫過的入門系列
Spring入門這一篇就夠了和
Spring【依賴注入】就是這麼簡單。最主要的知識點都已經講過了,是以感覺就沒必要重新來編寫這些知識點了...
我個人又不喜歡将寫過的東西複制到新的文章中,是以建議大家可以先去閱讀上面兩篇文章再來看這篇(工廠模式那篇如果沒有看過的同學也有必要去看看)~~
- 為了這篇文章知識點的完整性,重要的知識點(IOC概念了解,建立Bean、注入的三種方式等)還是會出現,但是不會将上面兩篇博文的代碼摘抄過來了。
這篇文章主要是補充和強化一些比較重要的知識點,并會把上面的兩本書關于IOC的知識點整理出來并畫成一個思維導圖來全面了解Spring IOC的知識點!
那麼接下來就開始吧,如果有錯的地方希望能多多包涵,并不吝在評論區指正!
一、Spring IOC全面認知
結合《Spring 實戰 (第4版)》和《精通Spring4.x 企業應用開發實戰》兩本書的IOC章節将其知識點整理起來~
1.1IOC和DI概述
在《精通Spring4.x 企業應用開發實戰》中對IOC的定義是這樣的:
IoC(Inversion of Control)控制反轉,包含了兩個方面:一、控制。二、反轉
我們可以簡單認為:
- 控制指的是:目前對象對内部成員的控制權。
- 反轉指的是:這種控制權不由目前對象管理了,由其他(類,第三方容器)來管理。
IOC不夠開門見山,于是Martin Fowler提出了DI(dependency injection)來替代IoC,即讓調用類對某一接口實作類的依賴關系由第三方(容器或協作類)注入,以移除調用類對某一接口實作類的依賴。
在《Spring 實戰 (第4版)》中并沒有提及到IOC,而是直接來說DI的:
通過DI,對象的依賴關系将由系統中負責協調各對象的第三方元件在建立對象的時候進行設定,對象無需自行建立或管理它們的依賴關系,依賴關系将被自動注入到需要它們的對象當中去
從書上我們也可以發現:IoC和DI的定義(差別)并不是如此容易就可以說得清楚的了。這裡我就簡單摘抄一下:
- IoC(思想,設計模式)主要的實作方式有兩種:依賴查找,依賴注入。
- 依賴注入是一種更可取的方式(實作的方式)
對我們而言,其實也沒必要分得那麼清,混合一談也不影響我們的了解...
再通過昨天寫過的
,我們現在就可以很清楚的發現,其實所謂的IOC容器就是一個大工廠【第三方容器】(Spring實作的功能很強大!比我們自己手寫的工廠要好很多)。
使用IOC的好處(知乎@Intopass的回答):
- 不用自己組裝,拿來就用。
- 享受單例的好處,效率高,不浪費空間。
- 便于單元測試,友善切換mock元件。
- 便于進行AOP操作,對于使用者是透明的。
- 統一配置,便于修改。
參考資料:
- https://www.zhihu.com/question/23277575 --Spring IoC有什麼好處呢?
1.2IOC容器的原理
從上面就已經說了:IOC容器其實就是一個大工廠,它用來管理我們所有的對象以及依賴關系。
- 原理就是通過Java的反射技術來實作的!通過反射我們可以擷取類的所有資訊(成員變量、類名等等等)!
- 再通過配置檔案(xml)或者注解來描述類與類之間的關系
- 我們就可以通過這些配置資訊和反射技術來建構出對應的對象和依賴關系了!
上面描述的技術隻要學過點Java的都能說出來,這一下子可能就會被面試官問倒了,我們簡單來看看實際Spring IOC容器是怎麼實作對象的建立和依賴的:

- 根據Bean配置資訊在容器内部建立Bean定義系統資料庫
- 根據系統資料庫加載、執行個體化bean、建立Bean與Bean之間的依賴關系
- 将這些準備就緒的Bean放到Map緩存池中,等待應用程式調用
Spring容器(Bean工廠)可簡單分成兩種:
- BeanFactory
- 這是最基礎、面向Spring的
- ApplicationContext
- 這是在BeanFactory基礎之上,面向使用Spring架構的開發者。提供了一系列的功能!
幾乎所有的應用場合都是使用ApplicationContext!
BeanFactory的繼承體系:
ApplicationContext的繼承體系:
其中在ApplicationContext子類中又有一個比較重要的:WebApplicationContext
- 專門為Web應用準備的
Web應用與Spring融合:
我們看看BeanFactory的生命周期:
接下來我們再看看ApplicationContext的生命周期:
初始化的過程都是比較長,我們可以分類來對其進行解析:
- Bean自身的方法:如調用 Bean 構造函數執行個體化 Bean,調用 Setter 設定 Bean 的屬性值以及通過
- Bean級生命周期接口方法:如 BeanNameAware、 BeanFactoryAware、 InitializingBean 和 DisposableBean,這些接口方法由 Bean 類直接實作;
- 容器級生命周期接口方法:在上圖中帶“” 的步驟是由 InstantiationAwareBean PostProcessor 和 BeanPostProcessor 這兩個接口實作,一般稱它們的實作類為“ 後處理器” 。 後處理器接口一般不由 Bean 本身實作,它們獨立于 Bean,實作類以容器附加裝置的形式注冊到Spring容器中并通過接口反射為Spring容器預先識别。當Spring 容器建立任何 Bean 的時候,這些後處理器都會發生作用,是以這些後處理器的影響是全局性的。當然,使用者可以通過合理地編寫後處理器,讓其僅對感興趣Bean 進行加工處理
ApplicationContext和BeanFactory不同之處在于:
- ApplicationContext會利用Java反射機制自動識别出配置檔案中定義的BeanPostProcessor、 InstantiationAwareBeanPostProcesso 和BeanFactoryPostProcessor後置器,并自動将它們注冊到應用上下文中。而BeanFactory需要在代碼中通過手工調用
方法進行注冊addBeanPostProcessor()
- ApplicationContext在初始化應用上下文的時候就執行個體化所有單執行個體的Bean。而BeanFactory在初始化容器的時候并未執行個體化Bean,直到第一次通路某個Bean時才執行個體化目标Bean。
有了上面的知識點了,我們再來詳細地看看Bean的初始化過程:
簡要總結:
- BeanDefinitionReader讀取Resource所指向的配置檔案資源,然後解析配置檔案。配置檔案中每一個
解析成一個BeanDefinition對象,并儲存到BeanDefinitionRegistry中;<bean>
- 容器掃描BeanDefinitionRegistry中的BeanDefinition;調用InstantiationStrategy進行Bean執行個體化的工作;使用BeanWrapper完成Bean屬性的設定工作;
- 單例Bean緩存池:Spring 在DefaultSingletonBeanRegistry類中提供了一個用于緩存單執行個體 Bean 的緩存器,它是一個用HashMap實作的緩存器,單執行個體的Bean以beanName為鍵儲存在這個HashMap中。
1.3IOC容器裝配Bean
1.3.1裝配Bean方式
Spring4.x開始IOC容器裝配Bean有4種方式:
- XML配置
- 注解
- JavaConfig
- 基于Groovy DSL配置(這種很少見)
總的來說:我們以XML配置+注解來裝配Bean得多,其中注解這種方式占大部分!
1.3.2依賴注入方式
依賴注入的方式有3種方式:
- 屬性注入-->通過
方法注入setter()
- 構造函數注入
- 工廠方法注入
總的來說使用屬性注入是比較靈活和友善的,這是大多數人的選擇!
1.3.3對象之間關系
<bean>
對象之間有三種關系:
- 依賴-->挺少用的(使用depends-on就是依賴關系了-->前置依賴【依賴的Bean需要初始化之後,目前Bean才會初始化】)
- 繼承-->可能會用到(指定abstract和parent來實作繼承關系)
- 引用-->最常見(使用ref就是引用關系了)
1.3.4Bean的作用域
Bean的作用域:
- 單例Singleton
- 多例prototype
- 與Web應用環境相關的Bean作用域
- reqeust
- session
使用到了Web應用環境相關的Bean作用域的話,是需要我們手動配置代理的~
原因也很簡單:因為我們預設的Bean是單例的,為了适配Web應用環境相關的Bean作用域--->每個request都需要一個對象,此時我們傳回一個代理對象出去就可以完成我們的需求了!
将Bean配置單例的時候還有一個問題:
- 如果我們的Bean配置的是單例,而Bean對象裡邊的成員對象我們希望是多例的話。那怎麼辦呢??
- 預設的情況下我們的Bean單例,傳回的成員對象也預設是單例的(因為對象就隻有那麼一個)!
此時我們需要用到了
lookup
方法注入,使用也很簡單,看看例子就明白了:
1.3.6處理自動裝配的歧義性
昨天在刷書的時候剛好看到了有人在知乎邀請我回答這個問題:
結合兩本書的知識點,可以歸納成兩種解決方案:
- 使用
注解設定為首選的注入Bean@Primary
-
注解設定特定名稱的Bean來限定注入!@Qualifier
- 也可以使用自定義的注解來辨別
1.3.7引用屬性檔案以及Bean屬性
之前在寫配置檔案的時候都是直接将我們的資料庫配置資訊在裡面寫死的了:
其實我們有更優雅的做法:将這些配置資訊寫到配置檔案上(因為這些配置資訊很可能是會變的,而且有可能被多個配置檔案引用).
- 如此一來,我們改的時候就十分友善了。
引用配置檔案的資料使用的是
${}
除了引用配置檔案上的資料,我們還可以引用Bean的屬性:
引用Bean的屬性使用的是
#{}
在這種技術在《Spring 實戰 第四版》稱之為Spring EL,跟我們之前學過的EL表達式是類似的。主要的功能就是上面的那種,想要更深入了解可參考下面的連結:
1.3.8組合配置檔案
xml檔案之間組合:
xml和javaconfig互相組合的方式:
public static void main(String[] args) {
//1.通過構造函數加載配置類
ApplicationContext ctx = new AnnotationConfigApplicationContext(AppConf.class);
//2.通過編碼方式注冊配置類
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
ctx.register(DaoConfig.class);
ctx.register(ServiceConfig.class);
ctx.refresh();
//3.通過XML組裝@Configuration配置類所提供的配置資訊
ApplicationContext ctx = new ClassPathXmlApplicationContext("com/smart/conf/beans2.xml");
//4.通過@Configuration組裝XML配置所提供的配置資訊
ApplicationContext ctx = new AnnotationConfigApplicationContext(LogonAppConfig.class);
//5.@Configuration的配置類互相引用
ApplicationContext ctx = new AnnotationConfigApplicationContext(DaoConfig.class,ServiceConfig.class);
LogonService logonService = ctx.getBean(LogonService.class);
System.out.println((logonService.getLogDao() !=null));
logonService.printHelllo();
}
第一種的例子:
第二種的例子:
第三種的例子:
第四種的例子:
第五種的例子:
- 代碼由上可見
1.3.9裝配Bean總結
總的來說,Spring IOC容器就是在建立Bean的時候有很多的方式給了我們實作,其中也包括了很多關于Bean的配置~
對于Bean相關的注入教程代碼和簡化配置(p和c名稱空間)我就不一一說明啦,你們去看
就行了。
總的對比圖:
分别的應用場景:
至于一些小的知識點:
- 方法替換
- 使用某個Bean的方法替換成另一個Bean的方法
- 屬性編輯器
- Spring可以對基本類型做轉換就歸結于屬性編輯器的功勞!
- 國際化
- 使用不同語言(英語、中文)的作業系統去顯式不同的語言
- profile與條件化的Bean
- 滿足了某個條件才初始化Bean,這可以友善切換生産環境和開發環境~
- 容器事件
- 類似于我們的Servlet的監聽器,隻不過它是在Spring中實作了~
上面這些小知識點比較少情況會用到,這也不去講解啦。知道有這麼一回事,到時候查查就會用啦~~~
二、Spring IOC相關面試題
将SpringIOC相關知識點整理了一遍,要想知道哪些知識點是比較重要的。很簡單,我們去找找相關的面試題就知道了,如果該面試題是常見的,那麼說明這個知識點還是相對比較重要的啦!
以下的面試題從各種部落格上摘抄下來,摘抄量較大的會注明出處的~
2.1什麼是spring?
什麼是spring?
Spring 是個java企業級應用的開源開發架構。Spring主要用來開發Java應用,但是有些擴充是針對建構J2EE平台的web應用。Spring架構目标是簡化Java企業級應用開發,并通過POJO為基礎的程式設計模型促進良好的程式設計習慣。
2.2使用Spring架構的好處是什麼?
使用Spring架構的好處是什麼?
- 輕量:Spring 是輕量的,基本的版本大約2MB。
- 控制反轉:Spring通過控制反轉實作了松散耦合,對象們給出它們的依賴,而不是建立或查找依賴的對象們。
- 面向切面的程式設計(AOP):Spring支援面向切面的程式設計,并且把應用業務邏輯和系統服務分開。
- 容器:Spring 包含并管理應用中對象的生命周期和配置。
- MVC架構:Spring的WEB架構是個精心設計的架構,是Web架構的一個很好的替代品。
- 事務管理:Spring 提供一個持續的事務管理接口,可以擴充到上至本地事務下至全局事務(JTA)。
- 異常處理:Spring 提供友善的API把具體技術相關的異常(比如由JDBC,Hibernate or JDO抛出的)轉化為一緻的unchecked 異常。
2.3Spring由哪些子產品組成?
Spring由哪些子產品組成?
簡單可以分成6大子產品:
- Core
- AOP
- ORM
- DAO
- Web
- Spring EE
2.4BeanFactory 實作舉例
BeanFactory 實作舉例
Bean工廠是工廠模式的一個實作,提供了控制反轉功能,用來把應用的配置和依賴從正真的應用代碼中分離。
在spring3.2之前最常用的是XmlBeanFactory的,但現在被廢棄了,取而代之的是:XmlBeanDefinitionReader和DefaultListableBeanFactory
2.5什麼是Spring的依賴注入?
什麼是Spring的依賴注入?
依賴注入,是IOC的一個方面,是個通常的概念,它有多種解釋。這概念是說你不用建立對象,而隻需要描述它如何被建立。你不在代碼裡直接組裝你的元件和服務,但是要在配置檔案裡描述哪些元件需要哪些服務,之後一個容器(IOC容器)負責把他們組裝起來。
2.6有哪些不同類型的IOC(依賴注入)方式?
有哪些不同類型的IOC(依賴注入)方式?
- 構造器依賴注入:構造器依賴注入通過容器觸發一個類的構造器來實作的,該類有一系列參數,每個參數代表一個對其他類的依賴。
- Setter方法注入:Setter方法注入是容器通過調用無參構造器或無參static工廠 方法執行個體化bean之後,調用該bean的setter方法,即實作了基于setter的依賴注入。
- 工廠注入:這個是遺留下來的,很少用的了!
2.7哪種依賴注入方式你建議使用,構造器注入,還是 Setter方法注入?
哪種依賴注入方式你建議使用,構造器注入,還是 Setter方法注入?
你兩種依賴方式都可以使用,構造器注入和Setter方法注入。最好的解決方案是用構造器參數實作強制依賴,setter方法實作可選依賴。
2.8什麼是Spring beans?
什麼是Spring beans?
Spring beans 是那些形成Spring應用的主幹的java對象。它們被Spring IOC容器初始化,裝配,和管理。這些beans通過容器中配置的中繼資料建立。比如,以XML檔案中
<bean/>
的形式定義。
這裡有四種重要的方法給Spring容器提供配置中繼資料。
- XML配置檔案。
- 基于注解的配置。
- 基于java的配置。
- Groovy DSL配置
2.9解釋Spring架構中bean的生命周期
解釋Spring架構中bean的生命周期
- Spring容器 從XML 檔案中讀取bean的定義,并執行個體化bean。
- Spring根據bean的定義填充所有的屬性。
- 如果bean實作了BeanNameAware 接口,Spring 傳遞bean 的ID 到 setBeanName方法。
- 如果Bean 實作了 BeanFactoryAware 接口, Spring傳遞beanfactory 給setBeanFactory 方法。
- 如果有任何與bean相關聯的BeanPostProcessors,Spring會在postProcesserBeforeInitialization()方法内調用它們。
- 如果bean實作IntializingBean了,調用它的afterPropertySet方法,如果bean聲明了初始化方法,調用此初始化方法。
- 如果有BeanPostProcessors 和bean 關聯,這些bean的postProcessAfterInitialization() 方法将被調用。
- 如果bean實作了 DisposableBean,它将調用destroy()方法。
2.10解釋不同方式的自動裝配
解釋不同方式的自動裝配
- no:預設的方式是不進行自動裝配,通過顯式設定ref 屬性來進行裝配。
- byName:通過參數名 自動裝配,Spring容器在配置檔案中發現bean的autowire屬性被設定成byname,之後容器試圖比對、裝配和該bean的屬性具有相同名字的bean。
- byType::通過參數類型自動裝配,Spring容器在配置檔案中發現bean的autowire屬性被設定成byType,之後容器試圖比對、裝配和該bean的屬性具有相同類型的bean。如果有多個bean符合條件,則抛出錯誤。
- constructor:這個方式類似于byType, 但是要提供給構造器參數,如果沒有确定的帶參數的構造器參數類型,将會抛出異常。
- autodetect:首先嘗試使用constructor來自動裝配,如果無法工作,則使用byType方式。
隻用注解的方式時,注解預設是使用byType的!
2.11IOC的優點是什麼?
IOC的優點是什麼?
IOC 或 依賴注入把應用的代碼量降到最低。它使應用容易測試,單元測試不再需要單例和JNDI查找機制。最小的代價和最小的侵入性使松散耦合得以實作。IOC容器支援加載服務時的餓漢式初始化和懶加載。
2.12哪些是重要的bean生命周期方法? 你能重載它們嗎?
哪些是重要的bean生命周期方法? 你能重載它們嗎?
有兩個重要的bean 生命周期方法,第一個是
setup
, 它是在容器加載bean的時候被調用。第二個方法是
teardown
它是在容器解除安裝類的時候被調用。
The bean 标簽有兩個重要的屬性(
init-method
destroy-method
)。用它們你可以自己定制初始化和登出方法。它們也有相應的注解(
@PostConstruct
@PreDestroy
)。
2.13怎麼回答面試官:你對Spring的了解?
怎麼回答面試官:你對Spring的了解?
來源:
下面我就截幾個答案:
一、
二、
2.14Spring架構中的單例Beans是線程安全的麼?
Spring架構中的單例Beans是線程安全的麼?
Spring架構并沒有對單例bean進行任何多線程的封裝處理。關于單例bean的線程安全和并發問題需要開發者自行去搞定。但實際上,大部分的Spring bean并沒有可變的狀态(比如Serview類和DAO類),是以在某種程度上說Spring的單例bean是線程安全的。如果你的bean有多種狀态的話(比如 View Model 對象),就需要自行保證線程安全。
最淺顯的解決辦法就是将多态bean的作用域由“singleton”變更為“prototype”
2.15FileSystemResource和ClassPathResource有何差別?
FileSystemResource和ClassPathResource有何差別?
在FileSystemResource 中需要給出spring-config.xml檔案在你項目中的相對路徑或者絕對路徑。在ClassPathResource中spring會在ClassPath中自動搜尋配置檔案,是以要把ClassPathResource檔案放在ClassPath下。
如果将spring-config.xml儲存在了src檔案夾下的話,隻需給出配置檔案的名稱即可,因為src檔案夾是預設。
簡而言之,ClassPathResource在環境變量中讀取配置檔案,FileSystemResource在配置檔案中讀取配置檔案。
三、總結
這篇文章的主要知識點我畫了一張思維導圖來總結啦,當學習到AOP的時候,這張思維導圖會繼續補充的哦~~~
- 《Spring 實戰》
- 《精通Spring4.x 企業應用開發實戰》
- https://zhuanlan.zhihu.com/p/29344811 --Spring IOC原理總結
- https://blog.csdn.net/u014079773/article/details/52453002 ---Java面試題集(七)--Spring常見面試問題
- https://zhuanlan.zhihu.com/p/31527327 --69 個經典 Spring 面試題和答案
如果文章有錯的地方歡迎指正,大家互相交流。習慣在微信看技術文章,想要擷取更多的Java資源的同學,可以關注微信公衆号:Java3y。為了大家友善,剛建立了一下qq群:742919422,大家也可以去交流交流。謝謝支援了!希望能多介紹給其他有需要的朋友
文章的目錄導航:
更多的文章可往:
文章的目錄導航