主頁 > 後端開發 > 死磕Spring之IoC篇 - 深入了解Spring IoC(面試題)

死磕Spring之IoC篇 - 深入了解Spring IoC(面試題)

2021-02-21 06:15:26 後端開發

該系列文章是本人在學習 Spring 的程序中總結下來的,里面涉及到相關原始碼,可能對讀者不太友好,請結合我的原始碼注釋 Spring 原始碼分析 GitHub 地址 進行閱讀

Spring 版本:5.1.14.RELEASE

目錄
  • 1. 什么是 Spring Framework ?
  • 2. Spring Framework 的優勢和不足?
  • 3. 你對 IoC 的理解?
  • 4. 為什么需要 IoC ?
  • 5. IoC 和 DI 的區別?
  • 6. IoC 容器的職責?
  • 7. 什么是 Spring IoC 容器?
  • 8. 構造器注入和 Setter 注入
  • 9. BeanFactory 和 ApplicationContext 誰才是 Spring IoC 容器?
  • 10. Spring Bean 的生命周期?
  • 11. BeanDefinition 是什么?
  • 12. Spring 內建的 Bean 作用域有哪些?
  • 13. BeanPostProcessor 與 BeanFactoryPostProcessor 的區別?
  • 14. 依賴注入和依賴查找的來源是否相同?
  • 15. 如何基于 Extensible XML authoring 擴展 Spring XML 元素?
  • 16. Java 泛型擦寫發生在編譯時還是運行時?
  • 17. 簡述 Spring 事件機制原理?
  • 18. @EventListener 的作業原理?
  • 19. Spring 提供的注解有哪些?
  • 20. 簡述 Spring Environment ?
  • 21. Environment 完整的生命周期是怎樣的?
  • 22. Spring 應用背景關系的生命周期?
  • 23. Spring 應用背景關系生命周期有哪些階段?
  • 24. 簡述 ObjectFactory?
  • 25. 簡述 FactoryBean?
  • 26. ObjectFactory、FactoryBean 和 BeanFactory 的區別?
  • 27. @Bean 的處理流程是怎樣的?
  • 28. BeanFactory 是如何處理回圈依賴?
  • 29. Spring 中幾種初始化方法的執行順序?

1. 什么是 Spring Framework ?

官方檔案:

Spring makes it easy to create Java enterprise applications. It provides everything you need to embrace the Java language in an enterprise environment, with support for Groovy and Kotlin as alternative languages on the JVM, and with the flexibility to create many kinds of architectures depending on an application’s needs.

這個問題很難回答,在 Spring 官方檔案中的描述也很抽象,答案在于你對 Spring 是如何理解的,想必每個人都有自己的回答方式,以下是我個人對于 Spring 的理解:

整個 Spring 生態在涉及到 Java 的專案中被廣泛應用,它提供了非常多的組件,能夠讓你在開發 Java 應用的程序變得更加容易,彈性地支持其他軟體框架,可以比作一個“排插座”,其他軟體框架簡單地“插上”即可結合 Spring 一起使用,給開發人員帶來了非常多的便利,Spring 底層 IoC 容器的設計實作也是非常完美的,在整個 Spring 應用背景關系的生命周期和 Spring Bean 的生命周期的許多階段提供了相應的擴展點,供開發者自行擴展,使得框架非常的靈活,

2. Spring Framework 的優勢和不足?

優勢:Spring 面向模塊進行開發,根據不同的功能進行劃分,根據需求引入對應的模塊即可,對于開發人員非常友好,例如 Spring IoC 容器,將我們的 Java 物件作為 Spring Bean 進行管理,管理著 Bean 的整個生命周期;Spring MVC 提供“模型-視圖-控制器”(Model-View-Controller)架構和隨時可用的組件,用于開發靈活且松散耦合的 Web 應用程式;Spring AOP 提供面向切面編程的介面,可以很方便的使用;還有許多其他的功能模塊,就不一一講述了,

不足:整個 Spring 體系比較復雜,對于開發人員需要一定的學習成本,遇到相關問題時需要對底層實作有充分的了解,這也就需要開發人員投入更多的時間和精力去學習,當然,如今 Spring 體系整合了 Java 生態非常多的東西,為開發人員帶來的便利遠大于這些不足,我覺得是有必要對 Spring 進行充分的學習,去了解 Spring 的貢獻者們的設計思路,對自身也會有很大的提升,從中可以學習到許多的東西,

3. 你對 IoC 的理解?

Inversion of Control(IoC)是面向物件中的一種編程思想或原則,可以先回到傳統方式,當我依賴一個物件,我需要主動去創建它并進行屬性賦值,然后我才能去使用這個物件,對于 IoC 這種方式來說,它使得物件或者組件的創建更為透明,你不需要過多地關注細節,如創建物件、屬性賦值,這些作業交都由 IoC 容器來完成,已達到解耦的目的,

IoC 控制反轉,簡單來理解其實就是把獲取依賴物件的方式,交由 IoC 容器來實作,由“主動拉取”變為“被動獲取”,

4. 為什么需要 IoC ?

實際上,IoC 是為了屏蔽構造細節,例如 new 出來的物件的生命周期中的所有細節對于使用端都是知道的,如果在沒有 IoC 容器的前提下,IoC 是沒有存在的必要,不過在復雜的系統中,我們的應用更應該關注的是物件的運用,而非它的構造和初始化等細節,

5. IoC 和 DI 的區別?

DI 依賴注入不完全等同于 IoC,更應該說 DI 依賴注入是 IoC 的一種實作方式或策略,

依賴查找依賴注入都是 IoC 的實作策略,依賴查找就是在應用程式里面主動呼叫 IoC 容器提供的介面去獲取對應的 Bean 物件,而依賴注入是在 IoC 容器啟動或者初始化的時候,通過構造器、欄位、setter 方法或者介面等方式注入依賴,依賴查找相比于依賴注入對于開發者而言更加繁瑣,具有一定的代碼入侵性,需要借助 IoC 容器提供的介面,所以我們總是強調后者,依賴注入在 IoC 容器中的實作也是呼叫相關的介面獲取 Bean 物件,只不過這些作業都是在 IoC 容器啟動時由容器幫你實作了,在應用程式中我們通常很少主動去呼叫介面獲取 Bean 物件,

6. IoC 容器的職責?

主要有以下職責:

  • 依賴處理,通過依賴查找或者依賴注入

  • 管理托管的資源(Java Bean 或其他資源)的生命周期

  • 管理配置(容器配置、外部化配置、托管的資源的配置)

IoC 容器有非常多,例如 JDK 的 Java Beans,Java EE 的 EJB,Apache Avalon,Google guice,Spring,其中 Spring 是最成功的的一個,目前被廣泛應用,

其中 Spring 借鑒了 JDK 的 Java Beans 設計思想,也使用到其中相關類(例如 java.beans.PropertyEditor 屬性編輯器),開發過 IDE 的 GUI 界面的伙伴應該對 Java Beans 比較熟悉,

7. 什么是 Spring IoC 容器?

Spring 框架是一個 IoC 容器的實作,DI 依賴注入是它的實作的一個原則,提供依賴查找和依賴注入兩種依賴處理,管理著 Bean 的生命周期,Spring 還提供了 AOP 抽象、事件抽象、事件監聽機制、SPI 機制、強大的第三方整合、易測驗性等其他特性,

8. 構造器注入和 Setter 注入

構造器注入:通過構造器的引數注入相關依賴物件

Setter 注入:通過 Setter 方法注入依賴物件,也可以理解為欄位注入

對于兩種注入方式的看法:

  • 構造器注入可以避免一些尷尬的問題,比如說狀態不確定性地被修改,在初始化該物件時才會注入依賴物件,一定程度上保證了 Bean 初始化后就是不變的物件,這樣對于我們的程式和維護性都會帶來更多的便利;

  • 構造器注入不允許出現回圈依賴,因為它要求被注入的物件都是成熟態,保證能夠實體化,而 Setter 注入或欄位注入沒有這樣的要求;

  • 構造器注入可以保證依賴的物件能夠有序的被注入,而 Setter 注入或欄位注入底層是通過反射機制進行注入,無法完全保證注入的順序;

  • 如果構造器注入出現比較多的依賴導致代碼不夠優雅,我們應該考慮自身代碼的設計是否存在問題,是否需要重構代碼結構,

除了上面的注入方式外,Spring 還提供了介面回呼注入,通過實作 Aware 介面(例如 BeanNameAware、ApplicationContextAware)可以注入相關物件,Spring 在初始化這類 Bean 時會呼叫其 setXxx 方法注入物件,例如注入 beanName、ApplicationContext

9. BeanFactory 和 ApplicationContext 誰才是 Spring IoC 容器?

BeanFactory 是 Spring 底層 IoC 容器,ApplicationContext 是 BeanFactory 的子介面,是 BeanFactory 的一個超集,提供 IoC 容器以外更多的功能,ApplicationContext 除了扮演 IoC 容器角色,還提供了這些企業特性:面向切面(AOP)、配置元資訊、資源管理、事件機制、國際化、注解、Environment 抽象等,我們一般稱 ApplicationContext 是 Spring 應用背景關系,BeanFactory 為 Spring 底層 IoC 容器,

10. Spring Bean 的生命周期?

生命周期:

  1. Spring Bean 元資訊配置階段,可以通過面向資源(XML 或 Properties)、面向注解、面向 API 進行配置

  2. Spring Bean 元資訊決議階段,對上一步的配置元資訊進行決議,決議成 BeanDefinition 物件,該物件包含定義 Bean 的所有資訊,用于實體化一個 Spring Bean

  3. Spring Bean 元資訊注冊階段,將 BeanDefinition 配置元資訊 保存至 BeanDefinitionRegistry 的 ConcurrentHashMap 集合中

  4. Spring BeanDefinition 合并階段,定義的 Bean 可能存在層次性關系,則需要將它們進行合并,存在相同配置則覆寫父屬性,最終生成一個 RootBeanDefinition 物件

  5. Spring Bean 的實體化階段,首先的通過類加載器加載出一個 Class 物件,通過這個 Class 物件的構造器創建一個實體物件,構造器注入在此處會完成,在實體化階段 Spring 提供了實體化前后兩個擴展點(InstantiationAwareBeanPostProcessor 的 postProcessBeforeInstantiation、postProcessAfterInstantiation 方法)

  6. Spring Bean 屬性賦值階段,在 Spring 實體化后,需要對其相關屬性進行賦值,注入依賴的物件,首先獲取該物件所有屬性與屬性值的映射,可能已定義,也可能需要注入,在這里都會進行賦值(反射機制),提示一下,依賴注入的實作通過 CommonAnnotationBeanPostProcessor(@Resource、@PostConstruct、@PreDestroy)和 AutowiredAnnotationBeanPostProcessor(@Autowired、@Value)兩個處理器實作的,

  7. Aware 介面回呼階段,如果 Spring Bean 是 Spring 提供的 Aware 介面型別(例如 BeanNameAware、ApplicationContextAware),這里會進行介面的回呼,注入相關物件(例如 beanName、ApplicationContext)

  8. Spring Bean 初始化階段,這里會呼叫 Spring Bean 配置的初始化方法,執行順序:@PostConstruct 標注方法、實作 InitializingBean 介面的 afterPropertiesSet() 方法、自定義初始化方法,在初始化階段 Spring 提供了初始化前后兩個擴展點(BeanPostProcessor 的 postProcessBeforeInitialization、postProcessAfterInitialization 方法)

  9. Spring Bean 初始化完成階段,在所有的 Bean(不是抽象、單例模式、不是懶加載方式)初始化后,Spring 會再次遍歷所有初始化好的單例 Bean 物件,如果是 SmartInitializingSingleton 型別則呼叫其 afterSingletonsInstantiated() 方法,這里也屬于 Spring 提供的一個擴展點

  10. Spring Bean 銷毀階段,當 Spring 應用背景關系關倍訓者你主動銷毀某個 Bean 時則進入 Spring Bean 的銷毀階段,執行順序:@PreDestroy 注解的銷毀動作、實作了 DisposableBean 介面的 Bean 的回呼、destroy-method 自定義的銷毀方法,這里也有一個銷毀前階段,也屬于 Spring 提供的一個擴展點,@PreDestroy 就是基于這個實作的

  11. Spring 垃圾收集(GC)

總結:

  1. 上面 123 屬于 BeanDefinition 配置元資訊階段,算是 Spring Bean 的前身,想要生成一個 Bean 物件,需要將這個 Bean 的所有資訊都定義好;

  2. 其中 45 屬于實體化階段,想要生成一個 Java Bean 物件,那么肯定需要根據 Bean 的元資訊先實體化一個物件;

  3. 接下來的 6 屬于屬性賦值階段,實體化后的物件還是一個空物件,我們需要根據 Bean 的元資訊對該物件的所有屬性進行賦值;

  4. 后面的 789 屬于初始化階段,在 Java Bean 物件生成后,可能需要對這個物件進行相關初始化作業才予以使用;

  5. 最后面的 1011 屬于銷毀階段,當 Spring 應用背景關系關倍訓者主動銷毀某個 Bean 時,可能需要對這個物件進行相關銷毀作業,最后等待 JVM 進行回收,

11. BeanDefinition 是什么?

BeanDefinition 是 Spring Bean 的“前身”,其內部包含了初始化一個 Bean 的所有元資訊,在 Spring 初始化一個 Bean 的程序中需要根據該物件生成一個 Bean 物件并進行一系列的初始化作業,

12. Spring 內建的 Bean 作用域有哪些?

來源 說明
singleton 默認 Spring Bean 作用域,一個 BeanFactory 有且僅有一個實體
prototype 原型作用域,每次依賴查找和依賴注入生成新 Bean 物件
request 將 Spring Bean 存盤在 ServletRequest 背景關系中
session 將 Spring Bean 存盤在 HttpSession 中
application 將 Spring Bean 存盤在 ServletContext 中

13. BeanPostProcessor 與 BeanFactoryPostProcessor 的區別?

BeanPostProcessor 提供 Spring Bean 初始化前和初始化后的生命周期回呼,允許對關心的 Bean 進行擴展,甚至是替換,其相關子類也提供 Spring Bean 生命周期中其他階段的回呼,

BeanFactoryPostProcessor 提供 Spring BeanFactory(底層 IoC 容器)的生命周期的回呼,用于擴展 BeanFactory(實際為 ConfigurableListableBeanFactory),BeanFactoryPostProcessor 必須由 Spring ApplicationContext 執行,BeanFactory 無法與其直接互動,

14. 依賴注入和依賴查找的來源是否相同?

否,依賴查找的來源僅限于 Spring BeanDefinition 以及單例物件,而依賴注入的來源還包括 Resolvable Dependency(Spring 應用背景關系定義的可已處理的注入物件,例如注入 BeanFactory 注入的是 ApplicationContext 物件)以及 @Value 所標注的外部化配置

15. 如何基于 Extensible XML authoring 擴展 Spring XML 元素?

Spring XML 擴展

  1. 撰寫 XML Schema 檔案(XSD 檔案):定義 XML 結構

  2. 自定義 NamespaceHandler 實作:定義命名空間的處理器

  3. 自定義 BeanDefinitionParser 實作:系結命名空間下不同的 XML 元素與其對應的決議器

  4. 注冊 XML 擴展(META-INF/spring.handlers 檔案):命名空間與命名空間處理器的映射

  5. 撰寫 Spring Schema 資源映射檔案(META-INF/spring.schemas 檔案):XML Schema 檔案通常定義為網路的形式,在無網的情況下無法訪問,所以一般在本地的也有一個 XSD 檔案,可通過撰寫 spring.schemas 檔案,將網路形式的 XSD 檔案與本地的 XSD 檔案進行映射,這樣會優先從本地獲取對應的 XSD 檔案

Mybatis 對 Spring 的集成專案中的 <mybatis:scan /> 標簽就是這樣實作的,可以參考:NamespaceHandler、MapperScannerBeanDefinitionParser、XSD 等檔案

具體實作邏輯參考后續《決議自定義標簽(XML 檔案)》一文

16. Java 泛型擦寫發生在編譯時還是運行時?

運行時,編譯時,泛型引數型別還是存在的,運行時會忽略,

17. 簡述 Spring 事件機制原理?

主要有以下幾個角色:

  • Spring 事件 - org.springframework.context.ApplicationEvent,實作了 java.util.EventListener 介面

  • Spring 事件監聽器 - org.springframework.context.ApplicationListener,實作了 java.util.EventObject 類

  • Spring 事件發布器 - org.springframework.context.ApplicationEventPublisher

  • Spring 事件廣播器 - org.springframework.context.event.ApplicationEventMulticaster

Spring 內建的事件:

  • ContextRefreshedEvent:Spring 應用背景關系就緒事件
  • ContextStartedEvent:Spring 應用背景關系啟動事件
  • ContextStoppedEvent:Spring 應用背景關系停止事件
  • ContextClosedEvent:Spring 應用背景關系關閉事件

Spring 應用背景關系就是一個 ApplicationEventPublisher 事件發布器,其內部有一個 ApplicationEventMulticaster 事件廣播器(被觀察者),里面保存了所有的 ApplicationListener 事件監聽器(觀察者),Spring 應用背景關系發布一個事件后會通過 ApplicationEventMulticaster 事件廣播器進行廣播,能夠處理該事件型別的 ApplicationListener 事件監聽器則進行處理,

18. @EventListener 的作業原理?

@EventListener 用于標注在方法上面,該方法則可以用來處理 Spring 的相關事件,

Spring 內部有一個處理器 EventListenerMethodProcessor,它實作了 SmartInitializingSingleton 介面,在所有的 Bean(不是抽象、單例模式、不是懶加載方式)初始化后,Spring 會再次遍歷所有初始化好的單例 Bean 物件時會執行該處理器對該 Bean 進行處理,在 EventListenerMethodProcessor 中會對標注了 @EventListener 注解的方法進行決議,如果符合條件則生成一個 ApplicationListener 事件監聽器并注冊,

19. Spring 提供的注解有哪些?

核心注解有以下:

  • Spring 模式注解
Spring 注解 場景說明 起始版本
@Repository 資料倉儲模式注解 2.0
@Component 通用組件模式注解 2.5
@Service 服務模式注解 2.5
@Controller Web 控制器模式注解 2.5
@Configuration 配置類模式注解 3.0

Spring 模式注解都是 @Component 的派生注解,Spring 為什么會提供這么多派生注解?

@Component 注解是一個通用組件注解,標注這個注解后表明你需要將其作為一個 Spring Bean 進行使用,而其他注解都有各自的作用,例如 @Controller 及其派生注解用于 Web 場景下處理 HTTP 請求,@Configuration 注解通常會將這個 Spring Bean 作為一個配置類,也會被 CGLIB 提供,幫助實作 AOP 特性,這也是領域驅動設計中的一種思想,

領域驅動設計:Domain-Driven Design,簡稱 DDD,過去系統分析和系統設計都是分離的,這樣割裂的結果導致需求分析的結果無法直接進行設計編程,而能夠進行編程運行的代碼卻扭曲需求,導致客戶運行軟體后才發現很多功能不是自己想要的,而且軟體不能快速跟隨需求變化,DDD 則打破了這種隔閡,提出了領域模型概念,統一了分析和設計編程,使得軟體能夠更靈活快速跟隨需求變化,

  • 裝配注解
Spring 注解 場景說明 起始版本
@ImportResource 替換 XML 元素 <import> 2.5
@Import 匯入 Configuration 類 2.5
@ComponentScan 掃描指定 package 下標注 Spring 模式注解的類 3.1
  • 依賴注入注解
Spring 注解 場景說明 起始版本
@Autowired Bean 依賴注入,支持多中依賴查找方式 2.5
@Qualifier 細粒度的 @Autowired 依賴查找 2.5
  • @Enable 模塊驅動
Spring 注解 場景說明 起始版本
@EnableWebMvc 啟動整個 Web MVC 模塊 3.1
@EnableTransactionManagement 啟動整個事務管理模塊 3.1
@EnableCaching 啟動整個快取模塊 3.1
@EnableAsync 啟動整個異步處理模塊 3.1

@Enable 模塊驅動是以 @Enable 為前綴的注解驅動編程模型,所謂“模塊”是指具備相同領域的功能組件集合,組合所形成一個獨立的單元,比如 Web MVC 模塊、AspectJ 代理模塊、Caching(快取)模塊、JMX(Java 管理擴展)模塊、Async(異步處理)模塊等,

這類注解底層原理就是通過 @Import 注解匯入相關類(Configuration Class、 ImportSelector 介面實作、ImportBeanDefinitionRegistrar 介面實作),來實作引入某個模塊或功能,

  • 條件注解
Spring 注解 場景說明 起始版本
@Conditional 條件限定,引入某個 Bean 4.0
@Profile 從 Spring 4.0 開始,@Profile 基于 @Conditional 實作,限定 Bean 的 Spring 應用環境 4.0

20. 簡述 Spring Environment ?

統一 Spring 配置屬性的存盤,用于占位符處理和型別轉換,還支持更豐富的配置屬性源(PropertySource);

通過 Environment Profiles 資訊,幫助 Spring 容器提供條件化地裝配 Bean,

21. Environment 完整的生命周期是怎樣的?

在 Spring 應用背景關系進入重繪階段之前,可以通過 setEnvironment(Environment) 方法提前設定 Environment 物件,在重繪階段如果沒有 Environment 物件則會創建一個新的 Environment 物件

22. Spring 應用背景關系的生命周期?

Spring 應用背景關系就是 ApplicationContext,生命周期主要體現在 org.springframework.context.support.AbstractApplicationContext#refresh() 方法中,大致如下:

  1. Spring 應用背景關系啟動準備階段,設定相關屬性,例如啟動時間、狀態標識、Environment 物件

  2. BeanFactory 初始化階段,初始化一個 BeanFactory 物件,加載出 BeanDefinition 們;設定相關組件,例如 ClassLoader 類加載器、運算式語言處理器、屬性編輯器,并添加幾個 BeanPostProcessor 處理器

  3. BeanFactory 后置處理階段,主要是執行 BeanFactoryPostProcessor 和 BeanDefinitionRegistryPostProcessor 的處理,對 BeanFactory 和 BeanDefinitionRegistry 進行后置處理,這里屬于 Spring 應用背景關系的一個擴展點

  4. BeanFactory 注冊 BeanPostProcessor 階段,主要初始化 BeanPostProcessor 型別的 Bean(依賴查找),在 Spring Bean 生命周期的許多節點都能見到該型別的處理器

  5. 初始化內建 Bean,初始化當前 Spring 應用背景關系的 MessageSource 物件(國際化文案相關)、ApplicationEventMulticaster 事件廣播器物件、ThemeSource 物件

  6. Spring 事件監聽器注冊階段,主要獲取到所有的 ApplicationListener 事件監聽器進行注冊,并廣播早期事件

  7. BeanFactory 初始化完成階段,主要是初始化所有還未初始化的 Bean(不是抽象、單例模式、不是懶加載方式)

  8. Spring 應用背景關系重繪完成階段,清除當前 Spring 應用背景關系中的快取,例如通過 ASM(Java 位元組碼操作和分析框架)掃描出來的元資料,并發布背景關系重繪事件

  9. Spring 應用背景關系啟動階段,需要主動呼叫 AbstractApplicationContext#start() 方法,會呼叫所有 Lifecycle 的 start() 方法,最后會發布背景關系啟動事件

  10. Spring 應用背景關系停止階段,需要主動呼叫 AbstractApplicationContext#stop() 方法,會呼叫所有 Lifecycle 的 stop() 方法,最后會發布背景關系停止事件

  11. Spring 應用背景關系關閉階段,發布當前 Spring 應用背景關系關閉事件,銷毀所有的單例 Bean,關閉底層 BeanFactory 容器;注意這里會有一個鉤子函式(Spring 向 JVM 注冊的一個關閉當前 Spring 應用背景關系的執行緒),當 JVM “關閉” 時,會觸發這個執行緒的運行

總結:

  • 上面的 12345678 都屬于 Sping 應用背景關系的重繪階段,完成了 Spring 應用背景關系一系列的初始化作業;

  • 9 屬于 Spring 應用背景關系啟動階段,和 Lifecycle 生命周期物件相關,會呼叫這些物件的 start() 方法,最后發布背景關系啟動事件;

  • 10 屬于 Spring 應用背景關系停止階段,和 Lifecycle 生命周期物件相關,會呼叫這些物件的 stop() 方法,最后發布背景關系停止事件;

  • 11 屬于 Spring 應用背景關系關閉階段,發布背景關系關閉事件,銷毀所有的單例 Bean,關閉底層 BeanFactory 容器,

23. Spring 應用背景關系生命周期有哪些階段?

參考Spring 應用背景關系的生命周期

  • 重繪階段 - ConfigurableApplicationContext#refresh()
  • 啟動階段 - ConfigurableApplicationContext#start()
  • 停止階段 - ConfigurableApplicationContext#stop()
  • 關閉階段 - ConfigurableApplicationContext#close()

24. 簡述 ObjectFactory?

ObjectFactory(或 ObjectProvider) 可關聯某一型別的 Bean,僅提供一個 getObject() 方法用于回傳目標 Bean 物件,ObjectFactory 物件被依賴注入或依賴查找時并未實時查找到關聯型別的目標 Bean 物件,在呼叫 getObject() 方法才會依賴查找到目標 Bean 物件,

根據 ObjectFactory 的特性,可以說它提供的是延遲依賴查找,通過這一特性在 Spring 處理回圈依賴(欄位注入)的程序中就使用到了 ObjectFactory,在某個 Bean 還沒有完全初始化好的時候,會先快取一個 ObjectFactory 物件(呼叫其 getObject() 方法可回傳當前正在初始化的 Bean 物件),如果初始化的程序中依賴的物件又依賴于當前 Bean,會先通過快取的 ObjectFactory 物件獲取到當前正在初始化的 Bean,這樣一來就解決了回圈依賴的問題,

注意這里是延遲依賴查找而不是延遲初始化,ObjectFactory 無法決定是否延遲初始化,而需要通過配置 Bean 的 lazy 屬性來決定這個 Bean 物件是否需要延遲初始化,非延遲初始化的 Bean 在 Spring 應用背景關系重繪程序中就會初始化,

提示:如果是 ObjectFactory(或 ObjectProvider)型別的 Bean,在被依賴注入或依賴查找時回傳的是 DefaultListableBeanFactory#DependencyObjectProvider 私有內部類,實作了 ObjectProvider<T> 介面,關聯的型別為 Object,

25. 簡述 FactoryBean?

FactoryBean 關聯一個 Bean 物件,提供了一個 getObject() 方法用于回傳這個目標 Bean 物件,FactoryBean 物件在被依賴注入或依賴查找時,實際得到的 Bean 就是通過 getObject() 方法獲取到的目標型別的 Bean 物件,如果想要獲取 FactoryBean 本身這個物件,在 beanName 前面添加 & 即可獲取,

我們可以通過 FactoryBean 幫助實作復雜的初始化邏輯,例如在 Spring 繼集成 MyBatis 的專案中,Mapper 介面沒有實作類是如何被注入的?其實 Mapper 介面就是一個 FactoryBean 物件,當你注入該介面時,實際的到的就是其 getObject() 方法回傳的一個代理物件,關于資料庫的操作都是通過該代理物件來完成,

26. ObjectFactory、FactoryBean 和 BeanFactory 的區別?

根據其名稱可以知道其字面意思分別是:物件工廠,工廠 Bean

ObjectFactory、FactoryBean 和 BeanFactory 均提供依賴查找的能力,

  • ObjectFactory 提供的是延遲依賴查找,想要獲取某一型別的 Bean,需要呼叫其 getObject() 方法才能依賴查找到目標 Bean 物件,ObjectFactory 就是一個物件工廠,想要獲取該型別的物件,需要呼叫其 getObject() 方法生產一個物件,

  • FactoryBean 不提供延遲性,在被依賴注入或依賴查找時,得到的就是通過 getObject() 方法拿到的實際物件,FactoryBean 關聯著某個 Bean,可以說在 Spring 中它就是某個 Bean 物件,無需我們主動去呼叫 getObject() 方法,如果想要獲取 FactoryBean 本身這個物件,在 beanName 前面添加 & 即可獲取,

  • BeanFactory 則是 Spring 底層 IoC 容器,里面保存了所有的單例 Bean,ObjectFactory 和 FactoryBean 自身不具備依賴查找的能力,能力由 BeanFactory 輸出,

27. @Bean 的處理流程是怎樣的?

Spring 應用背景關系生命周期,在 BeanDefinition(@Component 注解、XML 配置)的加載完后,會執行所有 BeanDefinitionRegistryPostProcessor 型別的處理器,Spring 內部有一個 ConfigurationClassPostProcessor 處理器,它會對所有的配置類進行處理,決議其內部的注解(@PropertySource、@ComponentScan、@Import、@ImportResource、@Bean),其中 @Bean 注解標注的方法會生成對應的 BeanDefinition 物件并注冊,詳細步驟可查看后續文章,

28. BeanFactory 是如何處理回圈依賴?

前言,下面的“回圈依賴”換成“回圈依賴注入”比較合適,在 Spring 中通過 depends-on 配置的依賴物件如果出現回圈依賴會拋出例外

說明:這里的回圈依賴指的是單例模式下的 Bean 欄位注入時出現的回圈依賴,構造器注入對于 Spring 無法自動解決(應該考慮代碼設計是否有問題),可通過延遲初始化來處理,Spring 只解決單例模式下的回圈依賴,

在 Spring 底層 IoC 容器 BeanFactory 中處理回圈依賴的方法主要借助于以下 3 個 Map 集合:

  1. singletonObjects(一級 Map),里面保存了所有已經初始化好的單例 Bean,也就是會保存 Spring IoC 容器中所有單例的 Spring Bean;
  2. earlySingletonObjects(二級 Map),里面會保存從 三級 Map 獲取到的正在初始化的 Bean
  3. singletonFactories(三級 Map),里面保存了正在初始化的 Bean 對應的 ObjectFactory 實作類,呼叫其 getObject() 方法回傳正在初始化的 Bean 物件(僅實體化還沒完全初始化好),如果存在則將獲取到的 Bean 物件并保存至 二級 Map,同時從當前 三級 Map 移除該 ObjectFactory 實作類,

當通過 getBean 依賴查找時會首先依次從上面三個 Map 獲取,存在則回傳,不存在則進行初始化,這三個 Map 是處理回圈依賴的關鍵,

例如兩個 Bean 出現回圈依賴,A 依賴 B,B 依賴 A;當我們去依賴查找 A,在實體化后初始化前會先生成一個 ObjectFactory 物件(可獲取當前正在初始化 A)保存在上面的 singletonFactories 中,初始化的程序需注入 B;接下來去查找 B,初始 B 的時候又要去注入 A,又去查找 A ,由于可以通過 singletonFactories 直接拿到正在初始化的 A,那么就可以完成 B 的初始化,最后也完成 A 的初始化,這樣就避免出現回圈依賴,

問題一:為什么需要上面的 二級 Map

因為通過 三級 Map獲取 Bean 會有相關 SmartInstantiationAwareBeanPostProcessor#getEarlyBeanReference 的處理,避免重復處理,處理后回傳的可能是一個代理物件

例如在回圈依賴中一個 Bean 可能被多個 Bean 依賴, A -> B(也依賴 A) -> C -> A,當你獲取 A 這個 Bean 時,后續 B 和 C 都要注入 A,沒有上面的 二級 Map的話,三級 Map 保存的 ObjectFactory 實作類會被呼叫兩次,會重復處理,可能出現問題,這樣做在性能上也有所提升

問題二:為什么不直接呼叫這個 ObjectFactory#getObject() 方法放入 二級Map 中,而需要上面的 三級 Map

對于不涉及到 AOP 的 Bean 確實可以不需要 singletonFactories(三級 Map),但是 Spring AOP 就是 Spring 體系中的一員,如果沒有singletonFactories(三級 Map),意味著 Bean 在實體化后就要完成 AOP 代理,這樣違背了 Spring 的設計原則,Spring 是通過 AnnotationAwareAspectJAutoProxyCreator 這個后置處理器在完全創建好 Bean 后來完成 AOP 代理,而不是在實體化后就立馬進行 AOP 代理,如果出現了回圈依賴,那沒有辦法,只有給 Bean 先創建代理物件,但是在沒有出現回圈依賴的情況下,設計之初就是讓 Bean 在完全創建好后才完成 AOP 代理,

29. Spring 中幾種初始化方法的執行順序?

有以下初始化方式:

  • Aware 介面:實作了 Spring 提供的相關 XxxAware 介面,例如 BeanNameAware、ApplicationContextAware,其 setXxx 方法會被回呼,可以注入相關物件

  • @PostConstruct 注解:該注解是 JSR-250 的標準注解,Spring 會呼叫該注解標注的方法

  • InitializingBean 介面:實作了該介面,Spring 會呼叫其 afterPropertiesSet() 方法

  • 自定義初始化方法:通過 init-method 指定的方法會被呼叫

在 Spring 初始 Bean 的程序中上面的初始化方式的執行順序如下:

  1. Aware 介面的回呼

  2. JSR-250 @PostConstruct 標注的方法的呼叫

  3. InitializingBean#afterPropertiesSet 方法的回呼

  4. init-method 初始化方法的呼叫

轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/261609.html

標籤:Java

上一篇:jwt身份驗證

下一篇:JAVA8時間插入mysql少了8小時的解決辦法

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 【C++】Microsoft C++、C 和匯編程式檔案

    ......

    uj5u.com 2020-09-10 00:57:23 more
  • 例外宣告

    相比于斷言適用于排除邏輯上不可能存在的狀態,例外通常是用于邏輯上可能發生的錯誤。 例外宣告 Item 1:當函式不可能拋出例外或不能接受拋出例外時,使用noexcept 理由 如果不打算拋出例外的話,程式就會認為無法處理這種錯誤,并且應當盡早終止,如此可以有效地阻止例外的傳播與擴散。 示例 //不可 ......

    uj5u.com 2020-09-10 00:57:27 more
  • Codeforces 1400E Clear the Multiset(貪心 + 分治)

    鏈接:https://codeforces.com/problemset/problem/1400/E 來源:Codeforces 思路:給你一個陣列,現在你可以進行兩種操作,操作1:將一段沒有 0 的區間進行減一的操作,操作2:將 i 位置上的元素歸零。最終問:將這個陣列的全部元素歸零后操作的最少 ......

    uj5u.com 2020-09-10 00:57:30 more
  • UVA11610 【Reverse Prime】

    本人看到此題沒有翻譯,就附帶了一個自己的翻譯版本 思考 這一題,它的第一個要求是找出所有 $7$ 位反向質數及其質因數的個數。 我們應該需要質數篩篩選1~$10^{7}$的所有數,這里就不慢慢介紹了。但是,重讀題,我們突然發現反向質數都是 $7$ 位,而將它反過來后的數字卻是 $6$ 位數,這就說明 ......

    uj5u.com 2020-09-10 00:57:36 more
  • 統計區間素數數量

    1 #pragma GCC optimize(2) 2 #include <bits/stdc++.h> 3 using namespace std; 4 bool isprime[1000000010]; 5 vector<int> prime; 6 inline int getlist(int ......

    uj5u.com 2020-09-10 00:57:47 more
  • C/C++編程筆記:C++中的 const 變數詳解,教你正確認識const用法

    1、C中的const 1、區域const變數存放在堆疊區中,會分配記憶體(也就是說可以通過地址間接修改變數的值)。測驗代碼如下: 運行結果: 2、全域const變數存放在只讀資料段(不能通過地址修改,會發生寫入錯誤), 默認為外部聯編,可以給其他源檔案使用(需要用extern關鍵字修飾) 運行結果: ......

    uj5u.com 2020-09-10 00:58:04 more
  • 【C++犯錯記錄】VS2019 MFC添加資源不懂如何修改資源宏ID

    1. 首先在資源視圖中,添加資源 2. 點擊新添加的資源,復制自動生成的ID 3. 在解決方案資源管理器中找到Resource.h檔案,編輯,使用整個專案搜索和替換的方式快速替換 宏宣告 4. Ctrl+Shift+F 全域搜索,點擊查找全部,然后逐個替換 5. 為什么使用搜索替換而不使用屬性視窗直 ......

    uj5u.com 2020-09-10 00:59:11 more
  • 【C++犯錯記錄】VS2019 MFC不懂的批量添加資源

    1. 打開資源頭檔案Resource.h,在其中預先定義好宏 ID(不清楚其實ID值應該設定多少,可以先新建一個相同的資源項,再在這個資源的ID值的基礎上遞增即可) 2. 在資源視圖中選中專案資源,按F7編輯資源檔案,按 ID 型別 相對路徑的形式添加 資源。(別忘了先把檔案拷貝到專案中的res檔案 ......

    uj5u.com 2020-09-10 01:00:19 more
  • C/C++編程筆記:關于C++的參考型別,專供新手入門使用

    今天要講的是C++中我最喜歡的一個用法——參考,也叫別名。 參考就是給一個變數名取一個變數名,方便我們間接地使用這個變數。我們可以給一個變數創建N個參考,這N + 1個變數共享了同一塊記憶體區域。(參考型別的變數會占用記憶體空間,占用的記憶體空間的大小和指標型別的大小是相同的。雖然參考是一個物件的別名,但 ......

    uj5u.com 2020-09-10 01:00:22 more
  • 【C/C++編程筆記】從頭開始學習C ++:初學者完整指南

    眾所周知,C ++的學習曲線陡峭,但是花時間學習這種語言將為您的職業帶來奇跡,并使您與其他開發人員區分開。您會更輕松地學習新語言,形成真正的解決問題的技能,并在編程的基礎上打下堅實的基礎。 C ++將幫助您養成良好的編程習慣(即清晰一致的編碼風格,在撰寫代碼時注釋代碼,并限制類內部的可見性),并且由 ......

    uj5u.com 2020-09-10 01:00:41 more
最新发布
  • Rust中的智能指標:Box<T> Rc<T> Arc<T> Cell<T> RefCell<T> Weak

    Rust中的智能指標是什么 智能指標(smart pointers)是一類資料結構,是擁有資料所有權和額外功能的指標。是指標的進一步發展 指標(pointer)是一個包含記憶體地址的變數的通用概念。這個地址參考,或 ” 指向”(points at)一些其 他資料 。參考以 & 符號為標志并借用了他們所 ......

    uj5u.com 2023-04-20 07:24:10 more
  • Java的值傳遞和參考傳遞

    值傳遞不會改變本身,參考傳遞(如果傳遞的值需要實體化到堆里)如果發生修改了會改變本身。 1.基本資料型別都是值傳遞 package com.example.basic; public class Test { public static void main(String[] args) { int ......

    uj5u.com 2023-04-20 07:24:04 more
  • [2]SpinalHDL教程——Scala簡單入門

    第一個 Scala 程式 shell里面輸入 $ scala scala> 1 + 1 res0: Int = 2 scala> println("Hello World!") Hello World! 檔案形式 object HelloWorld { /* 這是我的第一個 Scala 程式 * 以 ......

    uj5u.com 2023-04-20 07:23:58 more
  • 理解函式指標和回呼函式

    理解 函式指標 指向函式的指標。比如: 理解函式指標的偽代碼 void (*p)(int type, char *data); // 定義一個函式指標p void func(int type, char *data); // 宣告一個函式func p = func; // 將指標p指向函式func ......

    uj5u.com 2023-04-20 07:23:52 more
  • Django筆記二十五之資料庫函式之日期函式

    本文首發于公眾號:Hunter后端 原文鏈接:Django筆記二十五之資料庫函式之日期函式 日期函式主要介紹兩個大類,Extract() 和 Trunc() Extract() 函式作用是提取日期,比如我們可以提取一個日期欄位的年份,月份,日等資料 Trunc() 的作用則是截取,比如 2022-0 ......

    uj5u.com 2023-04-20 07:23:45 more
  • 一天吃透JVM面試八股文

    什么是JVM? JVM,全稱Java Virtual Machine(Java虛擬機),是通過在實際的計算機上仿真模擬各種計算機功能來實作的。由一套位元組碼指令集、一組暫存器、一個堆疊、一個垃圾回收堆和一個存盤方法域等組成。JVM屏蔽了與作業系統平臺相關的資訊,使得Java程式只需要生成在Java虛擬機 ......

    uj5u.com 2023-04-20 07:23:31 more
  • 使用Java接入小程式訂閱訊息!

    更新完微信服務號的模板訊息之后,我又趕緊把微信小程式的訂閱訊息給實作了!之前我一直以為微信小程式也是要企業才能申請,沒想到小程式個人就能申請。 訊息推送平臺🔥推送下發【郵件】【短信】【微信服務號】【微信小程式】【企業微信】【釘釘】等訊息型別。 https://gitee.com/zhongfuch ......

    uj5u.com 2023-04-20 07:22:59 more
  • java -- 緩沖流、轉換流、序列化流

    緩沖流 緩沖流, 也叫高效流, 按照資料型別分類: 位元組緩沖流:BufferedInputStream,BufferedOutputStream 字符緩沖流:BufferedReader,BufferedWriter 緩沖流的基本原理,是在創建流物件時,會創建一個內置的默認大小的緩沖區陣列,通過緩沖 ......

    uj5u.com 2023-04-20 07:22:49 more
  • Java-SpringBoot-Range請求頭設定實作視頻分段傳輸

    老實說,人太懶了,現在基本都不喜歡寫筆記了,但是網上有關Range請求頭的文章都太水了 下面是抄的一段StackOverflow的代碼...自己大修改過的,寫的注釋挺全的,應該直接看得懂,就不解釋了 寫的不好...只是希望能給視頻網站開發的新手一點點幫助吧. 業務場景:視頻分段傳輸、視頻多段傳輸(理 ......

    uj5u.com 2023-04-20 07:22:42 more
  • Windows 10開發教程_編程入門自學教程_菜鳥教程-免費教程分享

    教程簡介 Windows 10開發入門教程 - 從簡單的步驟了解Windows 10開發,從基本到高級概念,包括簡介,UWP,第一個應用程式,商店,XAML控制元件,資料系結,XAML性能,自適應設計,自適應UI,自適應代碼,檔案管理,SQLite資料庫,應用程式到應用程式通信,應用程式本地化,應用程式 ......

    uj5u.com 2023-04-20 07:22:35 more