目錄
1. spring整體脈絡
2 描述BeanFactory
3. BeanFactory和ApplicationContext的區別
4. 簡述SpringIoC的加載過程
5. 簡述Bean的生命周期
6. Spring中有哪些擴展接口及調用機制
一. spring源碼整體脈絡介紹及源碼編譯
1.1. 什么是IOC
ioc是控制反轉, 這是一種設計理念, 用來解決的是層和層之間, 類和類之間的耦合問題.
比如,現在有A, B兩個類, 在A類中引用了B類. 那么如果有一天, B類要被替換掉, 我們會怎么辦呢?如果B類被引用了100次, 我們要替換100次?
現在呢, A是直接調用B, 如果我們間接的調用B, 將B包裝起來, 如果以后將B換成C, 只需要在包裝類里面替換就可以了. 我們不需要修改A類. 這就是控制反轉.
Spring使用了ioc, Spring.ioc(A, B) 將A和B的引用都存在ioc中, spring會幫我們維護好, 完全不用擔心.
當我們在A中要使用B的時候, 使用B對應的接口, 然后使用@Autowired注解
A {
@Autowired
private IB b; }
什么時候把B換掉了, 不痛不癢的, 只需要把新的類放到IoC中就可以了.
1.2. Spring源碼的整體脈絡梳理
Spring IoC是一個容器, 在Spring Ioc中維護了許多Bean
那這些bean是如何被注冊到IoC中的呢? 換句話說, 我們自定義的類, 是如何作為一個bean交給IoC容器去管理的呢?
先來回憶,我們在開發spring的時候的步驟:
第一步: 配置類. 配置類可以使用的方式通常由
1) xml配置 2) 注解配置 3) javaconfig方式配置 第二步: 加載spring上下文 1) 如果是xml, 則new ClassPathXmlApplicationContext("xml"); 2) 如果是注解配置: 則new AnnotationConfigApplicationContext(config.class) 第三步: getBean() 我們會講自定義的類, 通過xml或者注解的方式注入到ioc容器中.
在這一步, 會將xml或注解中指定的類注入到IoC容器中.
1.2.1 那么, 到底是如何將一個類注入到ioc中的呢?
下面就來梳理一下整個過程.
第一問: 一個類要生產成一個Bean, 最重要最核心的類是什么?
是BeanFactory
第二問: BeanFactory是什么呢?
BeanFactory是Spring頂層的核心接口--使用了簡單工廠模式. 通常都是根據一個名字生產一個實例, 根據傳入的唯一的標志來獲得bean對象, 但具體是穿入參數后創建, 還是穿入參數前創建, 這個要根據 具體情況而定, 根據名字或類型生產不同的bean.
一句話總結: BeanFactory的責任就是生產Bean
來看下面這段代碼:
public static void main( String[] args ) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(); context.getBean("***"); }
這段代碼實現的功能是, 讀取當前文件所在目錄及其子目錄中的文件, 然后獲取指定名稱的bean, 整個流程如下圖所示:
首先, 通過ClassPathXmlApplicationContext或者AnnotationConfigApplicationContext去讀取配置, 然后將其交給BeanFactory. 第三. BeanFactory調用getBean()方法, 將Bean注入到IoC容器中
我們發現, 配置的讀取, 可能是xml方式, 也可能是annotation的方式, 不同的方式讀取應該使用的是不同的工具. 那么這些工具讀取的結果應該是統一的, 然后才能交給BeanFactory去處理.
因為在BeanFactory中是不會對這些異同點進行處理的. BeanFactory的作用只有一個, 就是個生產Bean.
1.2.2 那么, 不同的工具讀取配置是如何統一的呢?
我們知道,讀取配置這一塊, 應該會有一個不同的實現. 將xml和注解方式讀取成統一的東西, 放入到beanFactory中. 這個東西是誰呢?就是BeanDefinition(Bean定義)
什么意思呢? 如下圖:
看綠色框框住的部分. 這個含義是: 通過不同的工具, 可能是xmlApplicationContext, 可能是annotationApplicationContext工具 讀取的配置, 最后都會構造成BeanDefinition對象. 然后將BeanDefinition傳遞給BeanFactory, BeanFactory統一處理BeanDefinition對象, 調用getBean()方法, 將其放入IoC容器中.
1.2.3 那么又是是如何讀取配置統一構造成BeanDefinition的呢?
我們來舉個例子, 現在有一個人, 比如說我剛買了一個房子, 我要裝修. 需要一個衣櫃, 這時候, 我會找到一個衣櫃店. 然后告訴他我的需求, 櫃子的顏色, 款式格式什么樣. 然后衣櫃店記錄我的需求, 這個時候, 他不會自己生產, 他會通知工廠, 讓工廠來生產. 工廠按照什么生產呢, 衣櫃店有一個設計師, 他們的設計師. 會按照我的需求設計出一張圖紙. 然后將圖紙交給工廠. 工廠按照圖紙要求生產Bean.
整個過程如下圖:
入口是"我"
1. 我有一個需求, 打一個櫃子, 找到衣櫃店
2. 我告訴衣櫃店我的需求, 櫃子的顏色, 款式, 然后衣櫃店的設計師按照我的要求 ,設計出一張圖紙
3. 衣櫃店將圖紙給到工廠, 工廠按照圖紙生產櫃子
這是制造衣櫃的過程. 其中在畫圖紙的時候, 畫一張就給工廠給一張, 這樣效率太低了. 我們可以畫了n張, 一起給工廠. 所以, 在設計圖紙這塊是一個容器, 存放多張圖紙
后面,如果我還想定制一個櫥櫃店. 那么, 就告訴設計師我的櫥櫃的顏色,款式, 就可以了. 流程和上面都是一樣的.
整個這個過程, 就類似於我們的bean生產的過程
1. 定義了一個帶有@Component注解的類, 我找到衣櫃店, 衣櫃店就類似於ApplicationContext.
2. 我告訴ApplicationContext我的需求, 我要懶加載@Lazy, 設置單例模式還是多例模式@Scope. 對應的就是定制櫃子的顏色,款式. 然后衣櫃店里的設計師BeanDefinitionRegistry根據我的需求設計出圖紙, 也就是構造成BeanDefinition. 不同的BeanDefinitionRegistry設計出不同的BeanDefinition, 然后將他們都放在容器中.
3. 衣櫃店ApplicationContext統一將一摞圖紙BeanDefinitionMap交給工廠, 然后工廠按照要求生產Bean, 然后將生成的bean放入到IoC容器中.
這是一個帶有@Component的類被加載的過程.
衣櫃店要要想生意好, 那么他要去拉活呀, 所以還需要好的銷售. 銷售要去掃樓盤, 去聯系, 哪些人有裝修的需求. 挨個詢問.
可是問了100個人, 可能只有10個人有裝修的需求. 於是還要有一個接待, 這個接待要聯系客戶, 看看哪些是有意向的客戶, 將其篩選出來. 然后定制家具.
這里多了兩類人: 銷售和接待. 具體工作如下.
銷售就相當於我們的BeanDefinitionReader, 他的作用是去掃樓盤, 找到潛在客戶. 對應的就是BeanDefinitionReader去讀取xml配置或者Annotation注解.
xml中的配置有很多, 注解也有很多, 並不都是我們的目標. 於是有了接待
接待要去掃描所有潛在客戶. 將有意向的客戶掃描出來. 這就類似於我們的BeanDefinitionScanner, 去掃描潛在客戶, 最后將帶有@Component注解的類篩選出來
這就是后面需要定制家具的客戶了
BeanDefinitionReader對應的就去讀取配置類, 看看有哪些需求需要搞裝修.
它本身也是一個抽象類, 可以看到他有AnnotationBeanDefinitionReader和XmlBeanDefinitionReader
我們配置了配置包, 去掃描這個包下所有的類, 然后將掃描到的所有的類交給BeanDefinitionScanner, 它會去過濾帶有@Component的類.
在和上面的流程連接起來, 就是整個配置文件被加載到IoC的過程了.
1.3. ApplicationContext和FactoryBean的區別
1. FactoryBean的功能就是生產bean. 他生產bean是根據BeanDefinition來生產的. 所以, 一次只能生產一個
2. ApplicationContext有兩種. 一種是xmlApplicationContext, 另一種是annotationApplicationContext, 他傳入的參數是一個配置文件. 也就是可以加載某個目錄下所有帶有@Component的類
他們兩個都各有使用場景. 使用ApplicationContext的居多.
另一個區別: 就是后面會說到的, ApplicationContext有兩個擴展接口, 可以用來和外部集成. 比如和MyBatis集成.
1.4. Bean的生命周期
如上圖, beanFactory拿到BeanDefinition, 直接調用getBean()就生產Bean了么?
不是的, 生產Bean是有一個流程的. 下面我們來看看Bean的生命周期
第一步: 實例化. bean實例化的時候從BeanDefinition中得到Bean的名字, 然后通過反射機制, 將Bean實例化. 實例化以后, 這是還只是個殼子, 里面什么都沒有.
第二步: 填充屬性. 經過初始化以后, bean的殼子就有了, bean里面有哪些屬性呢? 在這一步填充 第三步: 初始化. 初始化的時候, 會調用initMethod()初始化方法, destory()初始化結束方法 這個時候, 類就被構造好了. 第四步: 構造好了的類, 會被放到IoC的一個Map中. Map的key是beanName, value是bean實例. 這個Map是一個單例池, 也就是我們說的一級緩存 第五步: 我們就可以通過getBean("user"), 從單例池中獲取雷鳴是user的類了.
在構造bean的過程中, 還會有很多細節的問題, 比如循環依賴.
A類里面調用了B類, 所以BeanFactory在構造A的時候, 會去構造B. 然后在構造B的時候, 發現, B還依賴了A. 這樣, 就是循環依賴. 這是不可以的.
Spring是如何解決循環依賴的問題的呢?
設置出口. 比如A在構造的過程中, 那么設置一個標記, 正在構造中. 然后構造B, B在構造的過程中應用了A, 這時候, 有趣構造A, 然后發現A正在構造中, 那么, 就不會再次構造A了.
后面還會詳細講解Spring是如何解決循環引用的. 這里我們需要知道的是: Spring使用的是三級緩存來解決循環引用的問題
其實, bean是存在一級緩存里面, 循環引用使用的是三級緩存來解決的. 其實, 一、二、三級緩存就是Map。
1.5. Spring中的擴展接口
有兩個非常重要的擴展接口. BeanFactoryPostProcessor(Bean工廠的后置處理器) 和 BeanDefinitionRegistryPostProcessor
這兩個接口是干什么的呢?
我們在這個圖里面, 看到了設計師要設計出圖紙, 然后把圖紙交給工廠去生產. 那么設計師設計出來的圖紙, 有沒有可能被修改呢?
當然是可以被修改的. 只要還沒有交給工廠, 就可以修改.
BeanFactoryPostProcessor(Bean工廠的后置處理器)的作用就是修改BeanDefinition.
1. BeanFactoryPostProcessor: 修改BeanDefinition.
是一個接口, 我們的類可以實現這個接口, 然后重寫里面的方法
public class DefinedPost implements BeanFactoryPostProcessor {
/**
* 重寫Bean工廠的后置處理器
* @param beanFactory
* @throws BeansException
*/ @Override public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException { // beanFactory 拿到工廠了, 就可以獲取某一個Bean定義了 GenericBeanDefinition car = (GenericBeanDefinition) beanFactory.getBeanDefinition("Car"); // 拿到了car, 然后修改了Car的類名為com.example.tulingcourse.Tank. 那么后面在獲取的Bean里面, 將其轉換為Car, 就會報錯了 car.setBeanClassName("com.example.tulingcourse.Tank"); } }
第一步: 實現了BeanFactoryPostProcessor接口, 然后需要重寫里面的方法
第二步: 我們發現重寫方法直接給我們了beanFactory, bean工廠
第三步: 拿到bean工廠, 我們就可以根據名稱獲取BeanDefinition, 也就是bean定義了.
第四步: 我們修改了bean定義中的類名為Tank.
這時候會發生什么呢? 從bean工廠中構建的car, 取出來以后轉換成Car對象, 會報錯,
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(Application.class); Car car = context.getBean("car", Car.class); // 這里會報錯, 因為已經被修改 System.out.println(car.getName()); }
執行流程: 當spring啟動的時候, 就會去執行AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(TulingCourseApplication.class);
然后ApplicationContext回去掃描所有實現了BeanFactoryPostProcessor對象的類, 然后執行postProcessBeanFactory方法.
BeanFactoryPostProcessor被使用到的場景非常多, 在集成其他組件的時候, 比如集成mybatis
2. BeanDefinitionRegistryPostProcessor 注冊BeanDefinition
這是一個Bean定義注冊的后置處理器.BeanDefinitionRegistryPostProcessor本事是實現了BeanFactoryPostProcessor 接口
我們來看個demo
public class DefinedPost implements BeanDefinitionRegistryPostProcessor {
/**
* 重寫Bean工廠的后置處理器
* @param beanFactory
* @throws BeansException
*/ @Override public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException { // beanFactory 拿到工廠了, 就可以獲取某一個Bean定義了 GenericBeanDefinition car = (GenericBeanDefinition) beanFactory.getBeanDefinition("Car"); // 拿到了car, 然后修改了Car的類名為com.example.tulingcourse.Tank. 那么后面在獲取的Bean里面, 將其轉換為Car, 就會報錯了 car.setBeanClassName("com.example.tulingcourse.Tank"); } @Override public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry beanDefinitionRegistry) throws BeansException { } }
一個類實現了BeanDefinitionRegistryPostProcessor, 需要重寫postProcessBeanDefinitionRegistry方法, 這個方法直接將BeanDefinitionRegistry就給我們了.
然后使用beanDefinitionRegistry.registerBeanDefinition(); 就可以添加圖紙了
在這里可以注冊新的bean, 也可以刪除注冊的bean. 多注冊一個, bean工廠就要多構建一個.
總結:
BeanFactoryPostProcessor和BeanDefinitionRegistryPostProcessor這兩個擴展類是很重要的類, 這對於向外部擴展起到了很大的的作用, 比如: 集成mybatis
BeanFactoryPostProcessor和BeanDefinitionRegistryPostProcessor是在ApplicationContext中的兩個擴展接口. 這也是ApplicationContext和BeanFactory的區別之一, 因為有了這兩個擴展節點, 就可以和外部做集成. 比如Mybatis集成. 比如: 掃描配置類, 就是通過 這兩個擴展點的方式實現的.
這個擴展點的作用:
1. 除了IoC, 其他的擴展,比如AOP, 和MyBatis集成, 都要用到這兩個擴展點. 之所以Spring能夠有序不亂的和很多外部組件整合, 都是這兩個擴展點的功能
1.6 Bean的擴展點
除了ApplicationContext有擴展點, 在Spring IoC中的bean也有擴展點. BeanPostProcessor(Bean的后置處理器). 如果使用在getBean()之前, 那么可以阻止構建Bean, 還可以自定義構建Bean.
BeanPostProcessor使用的場景有很多. 在Bean實例化之前和之后會被調用. 在填充屬性之前和之后也會被調用, 初始化之前和之后也會調用. 有些過程不只調用一次. 整個過程一共會調用9次. 在每一個過程都可以擴展Bean.
思考: Spring加入AOP是如何實現呢?
集成AOP肯定不會和IoC糅合在一塊了. AOP就是通過BeanPostProcessor(Bean后置處理器)整合進來的.
AOP的實現方式有兩種: 一種是CGLIB, 另一種是JDK.
假如說要進行集成, 會在那個步驟繼承呢? 比如要加日志, 使用AOP的方式加. 我們通常是在初始化之后加AOP. 在這里將AOP集成進來.
如上圖: 當面試的時候面試官問你, Bean的生命周期, 我們不能只說實例化-->填充屬性-->初始化. 還需要說初始化的時候, 還有一些列的aware.
1.7. Spring IOC的加載過程
對照上圖, 我們來簡述ioc的加載過程
我們將一個類加載成Bean, 不是一步到位的,需要經歷一下的過程.
1. 首先, 我們要將類加載成BeanDefinition(Bean定義)
加載成bean定義, 有以下幾個步驟:
1) 使用BeanDefinitionReader加載配置類, 此時是掃描所有的xml文件或者項目中的注解. 這里面有些使我們的目標類, 有些不是
2) 使用BeanDefinitionScanner掃描出我們的目標類.
3) 使用BeanDefinitionRegistry注冊bean到BeanDefinitionMap中.
2. 然后, ApplicationContext可以調用BeanFactoryPostProcessor修改bean定義, 還可以調用BeanDefinitionRegistryPostProcessor注冊bean定義
3. 將BeanDefinition交給BeanFactory處理, BeanFactory調用getBean()生成Bean或者調用Bean(getBean()有兩個功能).
4. 成產bean的時候, 首先會實例化, 然后填充屬性(主要是讀取@Autowire, @Value等注解). 在初始化Bean, 這里會調用initMethod()方法和初始化銷毀方法destroy(). 初始化的時候還會調用一堆的Aware, 而且在bean生成的過程中 會有很多擴展點, 供我們去擴展.
5. 將生產出的Bean放入到Map中, map是一個一級緩存池. 后面, 我們可以通過getBean("user")從緩存池中獲取bean