回答: 我們為什麼要學習源碼? 1、知其然知其所以然 2、站在巨人的肩膀上,提高自己的編碼水平 3、應付面試 1.1 Spring源碼閱讀小技巧 1、類層次藏得太深,不要一個類一個類的去看,遇到方法該進就大膽的進 2、更不要一行一行的去看,看核心點,有些方法並不重要,不要跟它糾纏 3、看不懂的先不看 ...
回答:
我們為什麼要學習源碼?
1、知其然知其所以然
2、站在巨人的肩膀上,提高自己的編碼水平
3、應付面試
1.1 Spring源碼閱讀小技巧
1、類層次藏得太深,不要一個類一個類的去看,遇到方法該進就大膽的進
2、更不要一行一行的去看,看核心點,有些方法並不重要,不要跟它糾纏
3、看不懂的先不看,根據語義和返回值能知道這個方法達到了啥目的即可
4、只看核心介面(下麵標註了重點的地方)和核心代碼,有些地方也許你使用spring以來都沒觸發過
5、debug跟步走,源碼中給大家標註好了,見到 ”===>“ 就進去
進去之前,下一行打個斷點,方便快速回到岔路口
進去之前,可以先點方法看源碼,再debug跟進
6、廣度優先,而非深度優先。先沿著主流程走,瞭解大概,再細化某些方法
7、認命。spring里多少萬行的代碼,一部書都寫不完。只能學關鍵點
閱讀源碼目的
加深理解spring的bean載入過程
面試吹牛x
江湖傳說,spring的類關係是這樣的……
1.2 IoC初始化流程與繼承關係
引言
在看源碼之前需要掌握Spring的繼承關係和初始化
1) IoC容器初始化流程
目標:
1、IoC容器初始化過程中到底都做了哪些事情(巨集觀目標)
2、IoC容器初始化是如何實例化Bean的(劃重點,最終目標)
//沒有Spring之前我們是這樣的
User user=new User();
user.xxx();
//有了Spring之後我們是這樣的
<bean id="userService" class="com.spring.test.impl.UserServiceImpl">
User user= context.getBean("xxx");
user.xxx();
IoC流程簡化圖:
tips:
下麵的流轉記不住沒有關係
在剖析源碼的整個過程中,我們一直會拿著這個圖和源碼對照
初始化:
1、容器環境的初始化
2、Bean工廠的初始化(IoC容器啟動首先會銷毀舊工廠、舊Bean、創建新的工廠)
讀取與定義
讀取:通過BeanDefinitonReader讀取我們項目中的配置(application.xml)
定義:通過解析xml文件內容,將裡面的Bean解析成BeanDefinition(未實例化、未初始化)
實例化與銷毀
Bean實例化、初始化(註入)
銷毀緩存等
擴展點
事件與多播、後置處理器
複雜的流程關鍵點:
重點總結:
1、工廠初始化過程
2、解析xml到BeanDefinition,放到map
3、調用後置處理器
4、從map取出進行實例化( ctor.newInstance)
5、實例化後放到一級緩存(工廠)
2) 容器與工廠繼承關係
tips:
別緊張,下麵的繼承記不住沒有關係
關註顏色標註的幾個就可以
目標:簡單理解ioC容器繼承關係
繼承關係理解:
1、ClassPathXmlApplicationContext最終還是到了 ApplicationContext 介面,同樣的,我們也可以使用綠顏色的 FileSystemXmlApplicationContext 和 AnnotationConfigApplicationContext 這兩個類完成容器初始化的工作
2、FileSystemXmlApplicationContext 的構造函數需要一個 xml 配置文件在系統中的路徑,其他和 ClassPathXmlApplicationContext 基本上一樣
3、AnnotationConfigApplicationContext 的構造函數掃描classpath中相關註解的類,主流程一樣
課程中我們以最經典的 classpathXml 為例。
Bean工廠繼承關係
目標:
ApplicationContext 和 BeanFactory 啥關係?
BeanFactory 和 FactoryBean呢?
總結:
別害怕,上面的繼承關係不用刻意去記住它
其實接觸到的就最下麵這個!
1.3 開始搭建測試項目
四步:
1、新建測試module項目
首先我們在 Spring 源碼項目中新增一個測試項目,點擊 New -> Module... 創建一個 Gradle 的 Java 項目
2、詳細信息
3、設置gradle
4、完善信息
在 build.gradle 中添加對 Spring 源碼的依賴:
compile(project(':spring-context'))
spring-context 會自動將 spring-core、spring-beans、spring-aop、spring-expression 這幾個基礎 jar 包帶進來。
接著,我們需要在項目中創建一個 bean 和配置文件(application.xml)及啟動文件(Main.java)
介面如下:
package com.spring.test.service;
public interface UserService {
public String getName();
}
實現類
package com.spring.test.impl;
import com.spring.test.service.UserService;
public class UserServiceImpl implements UserService {
@Override
public String getName() {
return "Hello World";
}
}
Main代碼如下
public class Test {
public static void main(String[] args) {
ApplicationContext context =
new ClassPathXmlApplicationContext("classpath*:application.xml");
UserService userService = context.getBean(UserService.class);
System.out.println(userService);
// 這句將輸出: hello world
System.out.println(userService.getName());
}
}
配置文件 application.xml(在 resources 中)配置如下:
<?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">
<bean id="userService" class="com.spring.test.impl.UserServiceImpl"/>
</beans>
運行
輸出如下
com.spring.test.impl.UserServiceImpl@2aa5fe93
Hello World
1.4 工廠的構建
引言:
接下來,我們就正式講解Spring ioC容器的源碼
我們的目的:看一下ioC如何幫我們生成對象的
生命周期
1)ApplicationContext入口
參考 IocTest.java
測試代碼:spring支持多種bean定義方式,為方便大家理解結構,以xml為案例,後面的解析流程一致
ApplicationContext context = new ClassPathXmlApplicationContext("classpath*:${xmlName}.xml");
// (c)從容器中取出Bean的實例,call:AbstractApplicationContext.getBean(java.lang.Class<T>)
//工廠模式(simple)
UserService userService = (UserService) context.getBean("userServiceBeanId");
// 這句將輸出: hello world
System.out.println(userService.getName());
進入到ClassPathXmlApplicationContext的有參構造器
org.springframework.context.support.ClassPathXmlApplicationContext#ClassPathXmlApplicationContext(java.lang.String[], boolean, org.springframework.context.ApplicationContext)
public ClassPathXmlApplicationContext(
String[] configLocations, boolean refresh, @Nullable ApplicationContext parent)
throws BeansException {
//繼承結構圖
//1、返回一個classloader
//2、返回一個解析器
super(parent);
// 1、獲取環境(系統環境、jvm環境)
// 2、設置Placeholder占位符解析器
// 2、將xml的路徑解析完存儲到數組
setConfigLocations(configLocations);
//預設為true
if (refresh) {
//核心方法(模板)
refresh();
}
}
重點步驟解析(斷點跟蹤講解)
super方法做了哪些事情
1、super方法:通過點查看父容器與子容器概念
2、super方法:調用到頂端,一共5層,每一層都要與講義中的【ioC與Bean工廠類關係繼承】進行對照
3、super方法:在什麼地方初始化的類載入器和解析器
setConfigLocations方法做了哪些事情:
1、如何返回的系統環境和jvm環境
2、路徑的解析
3、設置占位符解析器
進入核心方法refresh
2)預刷新
prepareRefresh()【準備刷新】
// synchronized塊鎖(monitorenter --monitorexit),不然 refresh() 還沒結束,又來個啟動或銷毀容器的操作
synchronized (this.startupShutdownMonitor) {
//1、【準備刷新】【Did four things】
prepareRefresh();
......。略
講解重點(斷點跟蹤、類繼承關係、架構圖講解)
prepareRefresh幹了哪些事情
//1、記錄啟動時間/設置開始標誌
//2、子類屬性擴展(模板方法)
//3、校驗xml配置文件
//4、初始化早期發佈的應用程式事件對象(不重要,僅僅是創建setg對象)
3)創建bean工廠【重點】
【獲得新的bean工廠】obtainFreshBeanFactory()
最終目的就是解析xml,註冊bean定義
關鍵步驟
//1、關閉舊的 BeanFactory
//2、創建新的 BeanFactory(DefaluListbaleBeanFactory)
//3、解析xml/載入 Bean 定義、註冊 Bean定義到beanFactory(未初始化)
//4、返回全新的工廠
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
4)bean工廠前置操作
【準備bean工廠】prepareBeanFactory(beanFactory);
//1、設置 BeanFactory 的類載入器
//2、設置 BeanFactory 的表達式解析器
//3、設置 BeanFactory 的屬性編輯器
//4、智能註冊
tips
當前代碼邏輯簡單、且非核心
5)bean工廠後置操作
【後置處理器Bean工廠】postProcessBeanFactory(beanFactory) 空方法
tips:子類實現
空方法,跳過
6)工廠後置處理器【重點】
【調用bean工廠後置處理器】invokeBeanFactoryPostProcessors(beanFactory);
//調用順序一:bean定義註冊後置處理器
//調用順序二:bean工廠後置處理器
PostProcessorRegistrationDelegate 類里有詳細註解
tips
invoke方法近200行
關註兩類後置處理器的方法執行步驟和順序
7)bean後置處理器
【註冊bean後置處理器】registerBeanPostProcessors(beanFactory)
//6、【註冊bean後置處理器】只是註冊,但是不會反射調用
//功能:找出所有實現BeanPostProcessor介面的類,分類、排序、註冊
registerBeanPostProcessors(beanFactory);
// 核心:查看重要的3步;最終目的都是實現bean後置處理器的註冊
// 第一步: implement PriorityOrdered
// 第二步: implement Ordered.
// 第三步: Register all internal BeanPostProcessors.
8)國際化
【初始化消息源】國際化問題i18n initMessageSource();
tips:
就加了個bean進去,非核心步驟,跳過
9)初始化事件廣播器
【初始化應用程式事件多路廣播】initApplicationEventMulticaster();
tips:
需要講解觀察者設計模式
重點:就放了個bean進去, 到下麵的 listener再聯調。
10)刷新
【刷新】 onRefresh();
空的,交給子類實現:預設情況下不執行任何操作
// 具體的子類可以在這裡初始化一些特殊的 Bean(在初始化 singleton beans 之前)
onRefresh();
不重要,跳過
11)註冊監聽器【重點】
【註冊所有監聽器】registerListeners();
測試代碼參考:MulticastTest
//獲取所有實現了ApplicationListener,然後進行註冊
//1、集合applicationListeners查找
//2、bean工廠找到實現ApplicationListener介面的bean
//3、this.earlyApplicationEvents;
tips:
需要講解觀察者設計模式
重點:演示多播和容器發佈
12)完成bean工廠【重點】
【完成bean工廠初始化操作】finishBeanFactoryInitialization(beanFactory);
//【完成bean工廠初始化操作】負責初始化所有的 singleton beans
//此處開始調用Bean的前置處理器和後置處理器
finishBeanFactoryInitialization(beanFactory);
講解重點(斷點跟蹤、類繼承關係、架構圖講解)
//1、設置輔助器:例如:解析器、轉換器、類裝載器
//2、實例化
//3、填充
//4、調用前置、後置處理器
//核心代碼在 getBean() , 下麵單獨講解
13)完成刷新
【完成刷新】
protected void finishRefresh() {
// 1、清除上下文級資源緩存
clearResourceCaches();
// 2、LifecycleProcessor介面初始化
// ps:當ApplicationContext啟動或停止時,它會通過LifecycleProcessor來與所有聲明的bean的周期做狀態更新
// 而在LifecycleProcessor的使用前首先需要初始化
initLifecycleProcessor();
// 3、啟動所有實現了LifecycleProcessor介面的bean
//DefaultLifecycleProcessor,預設實現
getLifecycleProcessor().onRefresh();
// 4、發佈上下文刷新完畢事件到相應的監聽器
//ps:當完成容器初始化的時候,
// 要通過Spring中的事件發佈機制來發出ContextRefreshedEvent事件,以保證對應的監聽器可以做進一步的邏輯處理
publishEvent(new ContextRefreshedEvent(this));
// 5、把當前容器註冊到到MBeanServer,用於jmx使用
LiveBeansView.registerApplicationContext(this);
}
tips:
非核心步驟
2 singleton bean 創建【重點】
下麵拎出來,重點講 getBean方法。
參考代碼:
先看沒有迴圈依賴的情況,普通單例bean的初始化 SinigleTest.java
後面再講迴圈依賴
1)調用入口
大家都知道是getBean()方法,但是這個方法要註意,有很多調用時機
如果你把斷點打在了這裡,再點進去getBean,你將會直接從singleton集合中拿到一個實例化好的bean
無法看到它的實例化過程。
可以debug試一下。會發現直接從getSingleTon返回了bean,這不是我們想要的模樣……
思考一下,為什麼呢?
回顧 1.4中的第 12 小節,在bean工廠完成後,會對singleton的bean完成初始化,那麼真正的初始化應該發生在那裡!
那就需要找到:DefaultListableBeanFactory的第 809 行,那裡的getBean
也可以從 1.4的第12小節的入口跟進去。斷點打在這裡試試:
這也是我們在上面留下的尾巴。
本小節我們從這裡繼續……
2)主流程
小tip:先搞清除3級緩存的事
關於bean的三級緩存:DefaultSingletonBeanRegistry代碼
/**
* 一級緩存:單例(對象)池,這裡面的對象都是確保初始化完成,可以被正常使用的
* 它可能來自3級,或者2級
*/
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);
/**
* 三級緩存:單例工廠池,這裡面不是bean本身,是它的一個工廠,未來調getObject來獲取真正的bean
* 一旦獲取,就從這裡刪掉,進入2級(發生閉環的話)或1級(沒有閉環)
*/
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);
/**
* 二級緩存:早期(對象)單例池,這裡面都是半成品,只是有人用它提前從3級get出來,把引用暴露出去
* 它裡面的屬性可能是null,所以叫早期對象,early!半成品
* 未來在getBean付完屬性後,會調addSingleton清掉2級,正式進入1級
*/
private final Map<String, Object> earlySingletonObjects = new HashMap<>(16);
傳統叫三級緩存里拿bean,其實就是仨map
嚴格意義上,只有single一級緩存,其他倆根本算不上是緩存
他們只是在生成bean的過程中,暫存過bean的半成品。
就那麼稱呼,不必較真
主流程圖很重要!後面的debug會帶著這張圖走
getBean :
入口
doGetBean :
調getSingleton查一下緩存看看有沒有,有就返回,沒有給singleton一個lambda表達式,函數式編程里調下麵的createBean拿到新的bean,然後清除3級緩存,放入1級緩存
createBean :
調這裡。一堆檢查後,進入下麵
doCreateBean :
真正創建bean的地方: 調構造函數初始化 - 放入3級緩存 - 解析屬性賦值 - bean後置處理器
3)getSingleton
在DefaultSingletonBeanRegistry里,有三個,作用完全不一樣
//啥也沒乾,調下麵傳了個true
public Object getSingleton(String beanName)
//從1級緩存拿,1級沒有再看情況
//後面的參數如果true,就使用3級升2級返回,否則直接返回null
protected Object getSingleton(String beanName, boolean allowEarlyReference)
//1級沒有,通過給的factory創建並放入1級里,清除2、3
public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory)
4)bean實例化
org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#createBeanInstance
5)放入三級緩存
迴圈依賴和aop
org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#addSingletonFactory
4)註入屬性
org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#populateBean
真正給bean設置屬性的地方!
7)bean前後置
還記得上面我們自定義的 Bean後置處理器嗎
org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#initializeBean
前、後置的調用,在這裡
詳細見下圖第3步,很多,瞭解即可,需要時查一下在相關地方擴展
8)小結
偽代碼,無迴圈依賴時,生成bean流程一覽
getBean("A"){
doGetBean("A"){
a = getSingleton("A"){
a = singletonObjects(); //查1級緩存,null
if("創建過3級緩存"){ //不成立
//忽略
}
return a;
}; // null
if(a == null){
a = getSingleton("A" , ObjectFactory of){
a = of.getObject() -> { //lambda表達式
createBean("A"){
doCreateBean("A"){
createBeanInstance("A"); // A 實例化
addSingletonFactory("A"); // A 放入3級緩存
populateBean("A"); // A 註入屬性
initializeBean("A"); // A 後置處理器
} //end doCreateBean("A")
} //end crateBean("A")
} // end lambda A
addSingleton("A" , a) // 清除2、3級,放入1級
} // end getSingleton("A",factory)
} // end if(a == null)
return a;
} //end doGetBean("A")
}//end getBean("A")
3 Spring的迴圈依賴
引言
在上面,我們剖析了bean實例化的整個過程
也就是我們的Bean他是單獨存在的,和其他Bean沒有交集和引用
而我們在業務開發中,肯定會有多個Bean相互引用的情況
也就是所謂的迴圈依賴
3.1 什麼是迴圈依賴
簡單回顧下
通俗的講就是N個Bean互相引用對方,最終形成閉環。
項目代碼介紹如下(測試類入口: CircleTest.java)
配置文件
<!--迴圈依賴BeanA依賴BeanB -->
<bean id="userServiceImplA" class="com.spring.test.impl.UserServiceImplA">
<property name="userServiceImplB" ref="userServiceImplB"/>
</bean>
<!--迴圈依賴BeanB依賴BeanA -->
<bean id="userServiceImplB" class="com.spring.test.impl.UserServiceImplB">
<property name="userServiceImplA" ref="userServiceImplA"/>
</bean>
userServiceImplA代碼如下
public class UserServiceImplA implements UserService {
private UserServiceImplB userServiceImplB;
public void setUserServiceImplB(UserServiceImplB userServiceImplB) {
this.userServiceImplB = userServiceImplB;
}
@Override
public String getName() {
return "在UserServiceImplA的Bean中" +
"userServiceImplB註入成功>>>>>>>>>"+userServiceImplB;
}
}
userServiceImplB代碼如下
//實現類
public class UserServiceImplB implements UserService {
private UserServiceImplA userServiceImplA;
public void setUserServiceImplA(UserServiceImplA userServiceImplA) {
this.userServiceImplA = userServiceImplA;
}
@Override
public String getName() {
return "在UserServiceImplB的Bean中" +
"userServiceImplA註入成功>>>>>>>>>"+userServiceImplA;
}
入口Main
ApplicationContext context = new ClassPathXmlApplicationContext("classpath*:application.xml");
UserService userService = context.getBean("userServiceImplA",UserService.class);
System.out.println(userService.getName());
輸出如下
3.2 Spring如何解決迴圈依賴
假如無法解決迴圈依賴
1、Bean無法成功註入,導致業務無法進行
2、產生死迴圈(一種假設情景)
1)三級緩存變化過程
目標:
只有明白三級緩存變化過程,才能知道是如何解決迴圈依賴的
略去其他步驟,只看緩存變化
變化過程3-1:如下圖:
步驟:
A :... - 走到doCreateBean: 初始化 - 進3級緩存 - 註入屬性,發現需要B
B :... - 走到doCreateBean: 初始化 - 進3級緩存
1、BeanA經歷gdcd四個方法,走到doCreatebean里在實例化後、註入前放到三級緩存
2、放到三級緩存後;BeanA在正式的註入的時候,發現有迴圈依賴,重覆上【1】的步驟
3、最終:BeanA和BeanB都放到了三級緩存
變化過程3-2:如下圖:
步驟:
1、BeanB放到三級緩存後,這個時候BeanB要開始註入了;
於是,BeanB找到了迴圈依賴BeanA後,再從頭執行A的getBean和doGetBean方法;
此處在getSingleton裡面(這貨第一次是必經的,但第二次來行為不一樣了)將BeanA設置到了二級緩存,並且把BeanA從三級緩存移除走了
2、BeanB如願以償的拿到了A,註入,此時,完成了註入過程;一直到DefaultSingletonBeanRegistry#addSingleton方法後;BeanB從三級緩存直接進入一級緩存,完成它的使命
3、目前,一級緩存有BeanB(裡面的BeanA屬性還是空)、二級緩存有BeanA 三級緩存為空
效果如下
走到這一步,B裡面有A,它已完成。
但是很不幸,A裡面的B還是null,我們第三步會繼續完成這個設置
思考一下:
如果不用三級,我們直接用2級也能實現,但是3級我們說它是一個Factory,裡面可以在創建的前後嵌入我們的代碼,和前後置處理器,Aop之類的操作就發生在這裡
而2級存放的是bean實例,沒這麼多擴展的可能性,如果僅僅用於bean迴圈創建,倒是可以
總結:
1、如果不調用後置,返回的bean和三級緩存一樣
2、如果調用後置,返回的就是代理對象
3、這就是三級緩存設計的巧妙之處!!!!Map<String, ObjectFactory<?>>
變化過程3-3:如下圖:
步驟:
此時, BeanB裡面已經註入了BeanA,它自己完成併進入了一級緩存
要註意,它的完成是被動的結果,也就是A需要它,臨時先騰出時間創建了它
接下來,BeanA 還要繼續自己的流程,然後populateBean方法將BeanB註入到自己里
最後,BeanA 進一級緩存,刪除之前的二級
整個流程完成!
大功告成:雙方相互持有對方效果如下:
2)三級緩存解決方案總結
簡化版
序列圖
三級緩存解決迴圈依賴過程(回顧)
1、BeanA經過gdcd方法、放入到3級緩存、如果有迴圈依賴BeanB,重覆執行gdcd方法
2、直到發現了它也需要A,而A前面經歷了一次get操作,將3級緩存的BeanA放到2級緩存
3、然後2級緩存的A註入進BeanB, BeanB完事進一級緩存,此時BeanB持有BeanA
3、接下來,繼續完成BeanA剩下的操作,取BeanB填充進BeanA,將BeanA放到一級緩存,完成!
偽代碼,迴圈依賴流程一覽,都是關鍵步驟,不能再簡化了
建議粘貼到vscode等編輯器里查看,因為……它層級太tmd深了!
getBean("A"){
doGetBean("A"){
a = getSingleton("A"){
a = singletonObjects(); //查1級緩存,null
if("創建過3級緩存"){ //不成立
//忽略
}
return a;
}; // A第一次,null
if(a == null){
a = getSingleton("A" , ObjectFactory of){
a = of.getObject() -> { //lambda表達式
createBean("A"){
doCreateBean("A"){
createBeanInstance("A"); // A 實例化
addSingletonFactory("A"); // A 放入3級緩存
populateBean("A"){
//A 需要B,進入B的getBean
b = getBean("B"){
doGetBean("B"){
b = getSingleton("B"); // B第一次,null
if(b == null){
b = getSingleton("B", ObjectFactory of){
b = of.getObject() -> {
createBean("B"){
doCreateBean("B"){
createBeanInstance("B"); // B 實例化
addSingletonFactory("B"); // B 放入3級緩存
populateBean("B"){
//B 需要A,2次進入A的getBean
a = getBean("A"){
doGetBean("A"){
a = getSingleton("A"){
a = singletonObjects(); //查1級緩存,null
if("創建過3級緩存"){ //成立!
a = singletonFactory.getObject("A"); //取3級緩存,生成a
earlySingletonObjects.put("A", a); //放入2級緩存
singletonFactories.remove("A"); //移除3級緩存
return a;
}
}; // A第二次,不是null,但是半成品,還待在2級緩存里
} // end doGetBean("A")
} // end getBean("A")
} // end populate B
initializeBean("B",b); // B後置處理器
} // end doCreateBean B
} // end createBean B
} // end lambda B
// B 創建完成,並且是完整的,雖然它裡面的A還是半成品,但不影響它進入1級
addSingleton("B",b) ; // 清除3級緩存,進入1級
); // end getSingleton("B",factory)
} // end if(b==null);
return b;
} // end doGetBean("B")
} // end getBean("B")
} // end populateBean("A")
initializeBean("A"); // A 後置處理器
} //end doCreateBean("A")
} //end crateBean("A")
} // end lambda A
addSingleton("A" , a) // 清除2、3級,放入1級
} // end getSingleton("A",factory)
} // end if(a == null)
return a;
} //end doGetBean("A")
}//end getBean("A")
總結
可以發現,通過spring的三級緩存完美解決了迴圈依賴
Spring處理機制很聰明;它先掃描一遍Bean,先放到一個容器(3級緩存待命)
此時也不知道是否存在迴圈依賴,先放到三級緩存再說
等到設置屬性的時候,取對應的屬性bean去(此時才發現有了迴圈依賴) ,在放到第二個容器(2級緩存,半成品)
繼續,然後從二級緩存拿出進行填充(註入)
填充完畢,將自己放到一級緩存(這個bean是被動創建出來的,因為別人需要它,結果它先完成了)
然後不斷迴圈外層,處理最原始要創建的那個bean
為什麼設計三級?二級緩存能否解決迴圈依賴?
可以解決。別說2級,1級都行
雖然二級緩存能解決迴圈依賴,但是aop時會可能會引發問題,三級是一個factory,在裡面配備了對應的後置處理器,其中就有我們的aop (後面會講到),如果有人要用它,會在調用factory的getObject時生效,生成代理bean而不是原始bean。
如果不這麼做,直接創建原始對象註入,可能引發aop失效。
所以spring的3級各有意義:
1級:最終成品
2級:半成品
3級:工廠,備用
在上面的方法getEarlyBeanReference(提前暴露的引用)
回顧下
AbstractAutowireCapableBeanFactory.getEarlyBeanReference
if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {
//迴圈所有Bean後置處理器
for (BeanPostProcessor bp : getBeanPostProcessors()) {
if (bp instanceof SmartInstantiationAwareBeanPostProcessor) {
SmartInstantiationAwareBeanPostProcessor ibp = (SmartInstantiationAwareBeanPostProcessor) bp;
//重點:開始創建AOP代理
exposedObject = ibp.getEarlyBeanReference(exposedObject, beanName);
}
}
}
總結下:
1、如果不調用後置處理器,返回的Bean和三級緩存一樣,都是實例化、普通的Bean
2、如果調用後置,返回的就是代理對象,不是普通的Bean了
其實;這就是三級緩存設計的巧妙之處
那為什麼要2級呢? 不能直接放入1級嗎?
不能!
A-B-A中,第二次A的時候,A還是個半成品,不能放入1級
以上面為例,A在進入2級緩存的時候,它裡面的B還是個null !
如果放入1級,被其他使用的地方取走,會引發問題,比如空指針
4 IoC用到的那些設計模式
引言:
Spring中使用了大量的設計模式(面試)
4.1 工廠
工廠模式(Factory Pattern)提供了一種創建對象的最佳方式。
工廠模式(Factory Pattern)分為三種
1、簡單工廠
2、工廠方法
3、抽象工廠
1. 簡單工廠模式
ApplicationContext context =
new ClassPathXmlApplicationContext("classpath*:application.xml");\
UserService userService = context.getBean(UserService.class);
簡單工廠模式對對象創建管理方式最為簡單,因為其僅僅簡單的對不同類對象的創建進行了一層簡單的封裝
定義介面IPhone
public interface Phone {
void make();
}
實現類
public class IPhone implements Phone {
public IPhone() {
this.make();
}
@Override
public void make() {
// TODO Auto-generated method stub
System.out.println("生產蘋果手機!");
}
}
實現類
public class MiPhone implements Phone {
public MiPhone() {
this.make();
}
@Override
public void make() {
// TODO Auto-generated method stub
System.out.println("生產小米手機!");
}
}
定義工廠類並且測試
public class PhoneFactory {
public Phone makePhone(String phoneType) {
if (phoneType.equalsIgnoreCase("MiPhone")) {
return new MiPhone();
} else if (phoneType.equalsIgnoreCase("iPhone")) {
return new IPhone();
}
return null;
}
//測試簡單工廠
public static void main(String[] arg) {
PhoneFactory factory = new PhoneFactory();
Phone miPhone = factory.makePhone("MiPhone");
IPhone iPhone = (IPhone) factory.makePhone("iPhone");
}
}
4.2 模板
模板模式(Template Pattern):基於抽象類的,核心是封裝演算法
Spring核心方法refresh就是典型的模板方法
org.springframework.context.support.AbstractApplicationContext#refresh
模板設計模式—
模板方法定義了一個演算法的步驟,並允許子類為一個或多個步驟提供具體實現
//模板模式
public abstract class TemplatePattern {
protected abstract void step1();
protected abstract void step2();
protected abstract void step3();
protected abstract void step4();
//模板方法
public final void refresh() {
//此處也可加入當前類的一個方法實現,例如init()
step1();
step2();
step3();
step4();
}
}
定義子類
//模板模式
public class SubTemplatePattern extends TemplatePattern {
@Override
public void step1() {
System.out.println(">>>>>>>>>>>>>>1");
}
@Override
public void step2() {
System.out.println(">>>>>>>>>>>>>>2");
}
@Override
public void step3() {
System.out.println(">>>>>>>>>>>>>>3");
}
@Override
public void step4() {
System.out.println(">>>>>>>>>>>>>>4");
}
//測試
public static void main(String[] args) {
TemplatePattern tp = new SubTemplatePattern();
tp.refresh();
}
}
輸出
4.3 觀察者
什麼是觀察者模式
觀察者模式(Observer Pattern):當對象間存在一對多關係時,則使用觀察者模式(Observer Pattern)。比如,當一個對象被修改時,則會自動通知依賴它的對象。
Spring 的事件機制就是具體的觀察者模式的實現
spring中的多播與事件
AbstractApplicationContext#initApplicationEventMulticaster
AbstractApplicationContext#registerListeners
觀察者模式有哪些角色?
事件 ApplicationEvent
是所有事件對象的父類,繼承JDK的EventObject
事件監聽 ApplicationListener
,也就是觀察者對象,繼承自 JDK 的 EventListener
,可以監聽到事件;該類中只有一個方法 onApplicationEvent
。當監聽的事件發生後該方法會被執行。
事件發佈ApplicationContext
, 實現事件的發佈。
(發佈事件)
or=========
Spring中的多播
事件發佈 ApplicationEventMulticaster
,用於事件監聽器的註冊和事件的廣播。
自定義一個事件MessageSourceEvent並且實現ApplicationEvent介面
//在Spring 中使用事件監聽機制(事件、監聽、發佈)
//定義事件
//執行順序
//1、進入到事件源的有參數構造器
//2、發佈事件
//3、進入到監聽器類---one
//4、進入到事件源的方法
//5、進入到監聽器類---two
//6、進入到事件源的方法
public class MessageSourceEvent extends ApplicationEvent {
public MessageSourceEvent(Object source) {
super(source);
System.out.println("進入到事件源的有參數構造器");
}
public void print() {
System.out.println("進入到事件源的方法");
}
}
有了事件之後還需要自定義一個監聽用來接收監聽到事件,自定義ApplicationContextListener監聽 需要交給Spring容器管理, 實現ApplicationListener介面並且重寫onApplicationEvent方法,
監聽一
//在Spring 中使用事件監聽機制(事件、監聽、發佈)
//監聽類,在spring配置文件中,註冊事件類和監聽類
public class ApplicationContextListener implements ApplicationListener {
@Override
public void onApplicationEvent(ApplicationEvent event) {
if (event instanceof MessageSourceEvent) {
System.out.println("進入到監聽器類---one");
MessageSourceEvent myEvent = (MessageSourceEvent) event;
myEvent.print();
}
}
}
監聽二
//在Spring 中使用事件監聽機制(事件、監聽、發佈)
//監聽類,在spring配置文件中,註冊事件類和監聽類
public class ApplicationContextListenerTwo implements ApplicationListener {
@Override
public void onApplicationEvent(ApplicationEvent event) {
if(event instanceof MessageSourceEvent){
System.out.println("進入到監聽器類---two");
MessageSourceEvent myEvent=(MessageSourceEvent)event;
myEvent.print();
}
}
}
發佈事件
//在Spring 中使用事件監聽機制(事件、監聽、發佈)
//該類實現ApplicationContextAware介面,得到ApplicationContext對象
// 使用該對象的publishEvent方法發佈事件
public class ApplicationContextListenerPubisher implements ApplicationContextAware {
private ApplicationContext applicationContext;
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
public void publishEvent(ApplicationEvent event) {
System.out.println("發佈事件");
applicationContext.publishEvent(event);
}
}
配置文件
<!-- Spirng中的事件>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> -->
<!--<bean id="messageSourceEvent" class="com.spring.test.pattern.observer.MessageSourceEvent" />-->
<bean id="applicationContextListener" class="com.spring.test.pattern.observer.ApplicationContextListener"/>
<bean id="applicationContextListenerTwo" class="com.spring.test.pattern.observer.ApplicationContextListenerTwo"/>
<bean id="applicationContextListenerPubisher" class="com.spring.test.pattern.observer.ApplicationContextListenerPubisher"/>
<!-- Spirng中的事件>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> -->
測試
//總結 :使用bean工廠發佈和使用多播器效果是一樣的
public class Test {
public static void main(String[] args) {
ApplicationContext context =
new ClassPathXmlApplicationContext("classpath*:application.xml");
//***************使用spring的多播器發佈**********************
ApplicationEventMulticaster applicationEventMulticaster = (ApplicationEventMulticaster) context.getBean("applicationEventMulticaster");
applicationEventMulticaster.multicastEvent(new MessageSourceEvent("測試..."));
//***************使用BeanFactory的publishEvent發佈*********************
// ApplicationContextListenerPubisher myPubisher = (ApplicationContextListenerPubisher)
//context.getBean("applicationContextListenerPubisher");
//myPubisher.publishEvent(new MessageSourceEvent("測試..."));
}
}
多播發佈
工廠發佈
總結:
1、spring的事件驅動模型使用的是 觀察者模式
2、通過ApplicationEvent抽象類和ApplicationListener介面,可以實現事件處理
3、ApplicationEventMulticaster事件廣播器實現了監聽器的註冊,一般不需要我們實現,只需要顯示的調用 applicationcontext.publisherEvent方法即可
4、使用bean工廠發佈和使用多播器效果是一樣的
本文由
傳智教育博學谷
教研團隊發佈。如果本文對您有幫助,歡迎
關註
和點贊
;如果您有任何建議也可留言評論
或私信
,您的支持是我堅持創作的動力。轉載請註明出處!