javascript
Spring IOC 核心流程浓缩
一、基礎概念
- 1、IoC 和 DI
IoC (Inversion of Control),即控制反轉。這不是一種新的技術,而是 Spring 的一種設計思想。
在傳統的程序設計,我們直接在對象內部通過 new 來創建對象,是程序主動去創建依賴對象;而在 Spring 中有專門的一個容器來創建和管理這些對象,并將對象依賴的其他對象注入到該對象中,這個容器我們一般稱為 IoC 容器。
所有的類的創建、銷毀都由 Spring 來控制,也就是說控制對象生存周期的不再是引用它的對象,而是 Spring。對于某個具體的對象而言,以前是它控制其他對象,現在是所有對象都被 Spring 控制,所以這叫控制反轉。
DI(Dependency Injection),即依賴注入,由 Martin Fowler 提出。可以認為 IoC 和 DI 其實是同一個概念的不同角度描述。依賴注入是指組件之間的依賴關系由容器在運行期決定,形象的說,即由容器動態的將某個依賴關系注入到組件之中。依賴注入的目的并非為軟件系統帶來更多功能,而是為了提升組件重用的頻率,并為系統搭建一個靈活、可擴展的平臺。通過依賴注入機制,我們只需要通過簡單的配置,而無需任何代碼就可指定目標需要的資源,完成自身的業務邏輯,而不需要關心具體的資源來自何處,由誰實現。
- 2、bean(構成應用程序主干并由 Spring IoC 容器管理的對象)
官方概念:在 Spring 中,構成應用程序主干并由 Spring IoC 容器管理的對象稱為 bean。 bean 是一個由 Spring IoC 容器實例化,組裝和管理的對象。大白話:bean 可以認為是那些我們想注入到 Spring IoC 容器的 Java 對象實例的抽象。
我們經常會在 Service 上使用 @Service 注解,然后在要使用該 Service 的類中通過 @Autowire 注解來注入,這個 Service 就是一個 bean。在這個地方,@Service 注解相當于告訴 IoC 容器:這個類你需要幫我創建和管理;而 @Autowire 注解相當于告訴 IoC 容器:我需要依賴這個類,你需要幫我注入進來。
- 3、BeanDefinition(存儲 bean 的所有屬性方法定義)
理解了 bean,BeanDefinition 就好理解了。BeanDefinition 是 bean 的定義,用來存儲 bean 的所有屬性方法定義。
- 4、BeanFactory 和 ApplicationContext
BeanFactory:基礎類型 IoC 容器,提供完整的 IoC 服務支持。
ApplicationContext:BeanFactory 的子接口,在 BeanFactory 的基礎上構建,是相對比較高級的 IoC 容器實現。包含 BeanFactory 的所有功能,還提供了其他高級的特性,比如:事件發布、國際化信息支持、統一資源加載策略等。正常情況下,我們都是使用的 ApplicationContext。
以電話來舉例:
我們家里使用的 “座機” 就類似于 BeanFactory,可以進行電話通訊,滿足了最基本的需求。而現在非常普及的智能手機,iPhone、小米等,就類似于 ApplicationContext,除了能進行電話通訊,還有其他很多功能:拍照、地圖導航、聽歌等。
- 5、FactoryBean(自己實現 bean 的創建操作)
一般情況下,我們將 bean 的創建和管理都交給 Spring IoC 容器,Spring 會利用 bean 的 class 屬性指定的類來實例化 bean。但是如果我們想自己實現 bean 的創建操作,可以實現嗎?當然可以,FactoryBean 就可以實現這個需求。FactoryBean 是一種特殊的 bean,它是個工廠 bean,可以自己創建 bean 實例,如果一個類實現了 FactoryBean 接口,則該類可以自己定義創建實例對象的方法,只需要實現它的 getObject() 方法即可。FactoryBean 可能對于普通開發來說基本用不到也沒去注意過,但是它其實應用的非常廣,特別是在中間件中,如果你看過一些中間件的源碼,一定會看到 FactoryBean 的身影。
二、 Spring IoC 的核心流程。
容器構建啟動的入口有多種多樣,這邊以常用的 web.xml 配置的方式來說。首先,我們會在 web.xml 配置ContextLoaderListener 監聽器,當 Tomcat 啟動時,會觸發 ContextLoaderListener 的 contextInitialized 方法,從而開始 IOC 的構建流程。另一個常用的參數是 contextConfigLocation,用于指定 Spring 配置文件的路徑。
<?xml version="1.0" encoding="UTF-8"?> <web-app version="2.5" xmlns="Java EE: XML Schemas for Java EE Deployment Descriptors" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="Java EE: XML Schemas for Java EE Deployment Descriptorshttp://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"><display-name>open-joonwhee-service WAR</display-name><context-param><param-name>contextConfigLocation</param-name><param-value>classpath*:config/spring/appcontext-*.xml</param-value></context-param><listener><listener-class>org.springframework.web.context.ContextLoaderListener</listener-class></listener> </web-app>在正式進入容器的刷新前,會進行一些前置操作。
- 1、確認要使用的容器
通常使用的是XmlWebApplicationContext,如果是用 Spring Boot,一般是 AnnotationConfigApplicationContext,但其實都差別不大,最終都會繼承 AbstractApplicationContext,核心都在AbstractApplicationContext 中實現。 - 2、提供一個給開發者初始化 ApplicationContext 的機會。
具體的使用如下例子:ApplicationContextInitializer 擴展使用
1)創建一個 ApplicationContextInitializer 接口的實現類,例如下面的 SpringApplicationContextInitializer,并在 initialize 方法中進行自己的邏輯操作,例如:添加監聽器、添加 BeanFactoryPostProcessor。
package com.joonwhee.open.spring; import com.joonwhee.open.listener.EarlyListener; import com.joonwhee.open.processor.MyBeanFactoryPostProcessor; import org.springframework.context.ApplicationContextInitializer; import org.springframework.context.ConfigurableApplicationContext; /*** @author joonwhee* @date 2019/1/19*/ public class SpringApplicationContextInitializer implementsApplicationContextInitializer<ConfigurableApplicationContext> {@Overridepublic void initialize(ConfigurableApplicationContext applicationContext) {// 自己的邏輯實現// 例子1:通過硬編碼的方式添加監聽器EarlyListener earlyListener = new EarlyListener();applicationContext.addApplicationListener(earlyListener);// 例子2:通過硬編碼的方式添加BeanFactoryPostProcessorMyBeanFactoryPostProcessor myBeanFactoryPostProcessor = new MyBeanFactoryPostProcessor();applicationContext.addBeanFactoryPostProcessor(myBeanFactoryPostProcessor);} }2)在web.xml中,定義 contextInitializerClasses 或 globalInitializerClasses 參數,參數值為 SpringApplicationContextInitializer 的全路徑。
創建一個新的 BeanFactory,默認為 DefaultListableBeanFactory。
根據 web.xml 中 contextConfigLocation 配置的路徑,讀取 Spring 配置文件,并封裝成
Resource。
根據 Resource 加載 XML 配置文件,并解析成 Document 對象 。
從根節點開始,遍歷解析 Document 中的節點。
- 關于遍歷解析 Document 中的節點分為默認命名空間的節點和自定義命名空間的節點
1.對于默認命名空間的節點:先將 bean 節點內容解析封裝成 BeanDefinition,然后將 beanName、BeanDefinition 放到 BeanFactory 的緩存中,用于后續創建 bean 實例時使用。
2.對于自定義命名空間的節點:會拿到自定義命名空間對應的解析器,對節點進行解析處理。
例如:<context:component-scan base-package=“com.joonwhee” /> ,該節點對應的解析器會掃描 base-package 指定路徑下的所有類,將使用了 @Component(@Controller、@Service、@Repository)注解的類封裝成 BeanDefinition,然后將 beanName、BeanDefinition 放到 BeanFactory 的緩存中,用于后續創建 Bean 實例時使用。
注:默認命名空間為bean標簽,alias標簽和import標簽,而非默認空間主要為aop標簽 和管理事務的標簽。
實例化和調用所有 BeanFactoryPostProcessor,包括其子類 BeanDefinitionRegistryPostProcessor,BeanFactoryPostProcessor 接口是 Spring 初始化 BeanFactory 時對外暴露的擴展點,Spring IoC 容器允許 BeanFactoryPostProcessor 在容器實例化任何 bean 之前讀取 bean 的定義,并可以修改它
比BeanFactoryPostProcessor 具有更高的優先級,主要用來在常規的 BeanFactoryPostProcessor激活之前注冊一些 bean 定義。
來注冊一些常規的 BeanFactoryPostProcessor,因為此時所有常規的 BeanFactoryPostProcessor都還沒開始被處理。
注:這邊的 “常規 BeanFactoryPostProcessor” 主要用來跟 BeanDefinitionRegistryPostProcessor 區分。
例子:BeanFactoryPostProcessor 擴展使用
1)創建一個 BeanFactoryPostProcessor 接口的實現類,例如下面的 MyBeanFactoryPostProcessor,并在 postProcessBeanFactory 方法中進行自己的邏輯操作。例如:掃描某個包路徑,將該包路徑下使用了某個注解的類全部注冊到 Spring 中。
2)將該實現類注冊到 Spring 容器中,例如使用 @Component 注解
package com.joonwhee.open.demo.spring; import org.springframework.beans.BeansException; import org.springframework.beans.factory.config.BeanFactoryPostProcessor; import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; import org.springframework.stereotype.Component; /**1. @author joonwhee2. @date 2019/2/18*/ @Component public class MyBeanFactoryPostProcessor implements BeanFactoryPostProcessor {@Overridepublic void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {System.out.println("MyBeanFactoryPostProcessor#postProcessBeanFactory");// 自己的邏輯處理} }另外,Mybatis 中的 MapperScannerConfigurer 是一個典型的 BeanDefinitionRegistryPostProcessor 的擴展使用,有興趣的可以看看這個類的源碼。
注冊所有的 BeanPostProcessor,將所有實現了 BeanPostProcessor 接口的類加載到 BeanFactory 中
BeanPostProcessor 接口是 Spring 初始化 bean 時對外暴露的擴展點,Spring IOC 容器允許 BeanPostProcessor 在容器初始化 bean 的前后,添加自己的邏輯處理。在這邊只是注冊到 BeanFactory 中,具體調用是在 bean 初始化的時候。
例子:BeanPostProcessor 擴展使用
1)創建一個 BeanPostProcessor 接口的實現類,例如下面的 MyBeanPostProcessor,并在方法中進行自己的邏輯操作。
2)將該實現類注冊到 Spring 容器中,例如使用 @Component 注解。
遍歷所有被加載到緩存中的 beanName,觸發所有剩余的非懶加載單例 bean 的實例化。
首先通過 beanName 嘗試從緩存中獲取,如果存在則跳過實例化過程;否則,進行 bean 的實例化。
根據 BeanDefinition,使用構造函數創建 bean 實例。
根據 BeanDefinition,進行 bean 實例屬性填充。
執行 bean 實例的初始化。
5.1、觸發 Aware 方法。
5.2、觸發BeanPostProcessor,
postProcessBeforeInitialization 方法。
5.3、如果 bean 實現了 InitializingBean 接口,則觸發 afterPropertiesSet() 方法。
5.4、如果 bean 設置了 init-method 屬性,則觸發 init-method 指定的方法。
5.5、觸發 BeanPostProcessor 的 postProcessAfterInitialization 方法。
將創建好的 bean 實例放到緩存中,用于之后使用。
例子:監聽器擴展使用
1)創建一個自定義監聽器,實現 ApplicationListener 接口,監聽 ContextRefreshedEvent(上下文刷新完畢事件)。
2)將該監聽器注冊到 Spring IoC 容器即可。
以上為整個 IoC 的核心流程
本文轉自:https://zhuanlan.zhihu.com/p/79224941
總結
以上是生活随笔為你收集整理的Spring IOC 核心流程浓缩的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 好的产品经理都是这样绘制原型图的...
- 下一篇: 快速失败(fail-fast)和安全失败