主頁 > 後端開發 > 愛上原始碼,重學Spring IoC深入

愛上原始碼,重學Spring IoC深入

2022-10-27 06:25:24 後端開發

回答:
我們為什么要學習原始碼?
1、知其然知其所以然
2、站在巨人的肩膀上,提高自己的編碼水平
3、應付面試

1.1 Spring原始碼閱讀小技巧

1、類層次藏得太深,不要一個類一個類的去看,遇到方法該進就大膽的進

2、更不要一行一行的去看,看核心點,有些方法并不重要,不要跟它糾纏

3、看不懂的先不看,根據語意和回傳值能知道這個方法達到了啥目的即可

4、只看核心介面(下面標注了重點的地方)和核心代碼,有些地方也許你使用spring以來都沒觸發過

5、debug跟步走,原始碼中給大家標注好了,見到 ”===>“ 就進去

? 進去之前,下一行打個斷點,方便快速回到岔路口

? 進去之前,可以先點方法看原始碼,再debug跟進

6、廣度優先,而非深度優先,先沿著主流程走,了解大概,再細化某些方法

7、認命,spring里多少萬行的代碼,一部書都寫不完,只能學關鍵點

閱讀原始碼目的

加深理解spring的bean加載程序

面試吹牛x

江湖傳說,spring的類關系是這樣的……

file

1.2 IoC初始化流程與繼承關系

引言
在看原始碼之前需要掌握Spring的繼承關系和初始化

1) IoC容器初始化流程

目標:

1、IoC容器初始化程序中到底都做了哪些事情(宏觀目標)

2、IoC容器初始化是如何實體化Bean的(劃重點,最終目標)

//沒有Spring之前我們是這樣的
User user=new User();
user.xxx();

//有了Spring之后我們是這樣的
<bean id="userService" >
User user= context.getBean("xxx");
user.xxx();

IoC流程簡化圖:

tips:

下面的流轉記不住沒有關系

在剖析原始碼的整個程序中,我們一直會拿著這個圖和原始碼對照

file

初始化:

1、容器環境的初始化

2、Bean工廠的初始化(IoC容器啟動首先會銷毀舊工廠、舊Bean、創建新的工廠)

讀取與定義

讀取:通過BeanDefinitonReader讀取我們專案中的配置(application.xml)

定義:通過決議xml檔案內容,將里面的Bean決議成BeanDefinition(未實體化、未初始化)

實體化與銷毀

Bean實體化、初始化(注入)

銷毀快取等

擴展點

事件與多播、后置處理器

復雜的流程關鍵點:

file

重點總結:

1、工廠初始化程序

2、決議xml到BeanDefinition,放到map

3、呼叫后置處理器

4、從map取出進行實體化( ctor.newInstance)

5、實體化后放到一級快取(工廠)

2) 容器與工廠繼承關系

tips:

別緊張,下面的繼承記不住沒有關系

關注顏色標注的幾個就可以

目標:簡單理解ioC容器繼承關系

file

繼承關系理解:

1、ClassPathXmlApplicationContext最侄訓是到了 ApplicationContext 介面,同樣的,我們也可以使用綠顏色的 FileSystemXmlApplicationContext 和 AnnotationConfigApplicationContext 這兩個類完成容器初始化的作業

2、FileSystemXmlApplicationContext 的建構式需要一個 xml 組態檔在系統中的路徑,其他和 ClassPathXmlApplicationContext 基本上一樣

3、AnnotationConfigApplicationContext 的建構式掃描classpath中相關注解的類,主流程一樣

課程中我們以最經典的 classpathXml 為例,

Bean工廠繼承關系

目標:

ApplicationContext 和 BeanFactory 啥關系?

BeanFactory 和 FactoryBean呢?

file

總結:

別害怕,上面的繼承關系不用刻意去記住它

其實接觸到的就最下面這個!

1.3 開始搭建測驗專案

四步:

1、新建測驗module專案

首先我們在 Spring 原始碼專案中新增一個測驗專案,點擊 New -> Module... 創建一個 Gradle 的 Java 專案

file
2、詳細資訊

file
file

3、設定gradle

file
4、完善資訊

file

在 build.gradle 中添加對 Spring 原始碼的依賴:

compile(project(':spring-context'))

file

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" />
</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,這不是我們想要的模樣……

file

思考一下,為什么呢?

回顧 1.4中的第 12 小節,在bean工廠完成后,會對singleton的bean完成初始化,那么真正的初始化應該發生在那里!

那就需要找到:DefaultListableBeanFactory的第 809 行,那里的getBean

也可以從 1.4的第12小節的入口跟進去,斷點打在這里試試:

file

這也是我們在上面留下的尾巴,

本小節我們從這里繼續……

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會帶著這張圖走

file

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步,很多,了解即可,需要時查一下在相關地方擴展

file

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互相參考對方,最終形成倍訓

file
專案代碼介紹如下(測驗類入口: CircleTest.java)

組態檔

	<!--回圈依賴BeanA依賴BeanB -->
	<bean id="userServiceImplA" >
		<property name="userServiceImplB" ref="userServiceImplB"/>
	</bean>

	<!--回圈依賴BeanB依賴BeanA -->
	<bean id="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());

輸出如下

file

3.2 Spring如何解決回圈依賴

假如無法解決回圈依賴

1、Bean無法成功注入,導致業務無法進行

2、產生死回圈(一種假設情景)

1)三級快取變化程序

目標:

只有明白三級快取變化程序,才能知道是如何解決回圈依賴的

略去其他步驟,只看快取變化

file

變化程序3-1:如下圖:

file

步驟:

A :... - 走到doCreateBean: 初始化 - 進3級快取 - 注入屬性,發現需要B

B :... - 走到doCreateBean: 初始化 - 進3級快取

1、BeanA經歷gdcd四個方法,走到doCreatebean里在實體化后、注入前放到三級快取

2、放到三級快取后;BeanA在正式的注入的時候,發現有回圈依賴,重復上【1】的步驟

3、最終:BeanA和BeanB都放到了三級快取

變化程序3-2:如下圖:

file

步驟:

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,我們第三步會繼續完成這個設定

file

思考一下:

如果不用三級,我們直接用2級也能實作,但是3級我們說它是一個Factory,里面可以在創建的前后嵌入我們的代碼,和前后置處理器,Aop之類的操作就發生在這里

而2級存放的是bean實體,沒這么多擴展的可能性,如果僅僅用于bean回圈創建,倒是可以

總結:

1、如果不呼叫后置,回傳的bean和三級快取一樣

2、如果呼叫后置,回傳的就是代理物件

3、這就是三級快取設計的巧妙之處!!!!Map<String, ObjectFactory<?>>

變化程序3-3:如下圖:

file

步驟:

此時, BeanB里面已經注入了BeanA,它自己完成并進入了一級快取

要注意,它的完成是被動的結果,也就是A需要它,臨時先騰出時間創建了它

接下來,BeanA 還要繼續自己的流程,然后populateBean方法將BeanB注入到自己里

最后,BeanA 進一級快取,洗掉之前的二級

整個流程完成!

大功告成:雙方相互持有對方效果如下:

file

2)三級快取解決方案總結

簡化版

file

序列圖

file
三級快取解決回圈依賴程序(回顧)

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");     
	}
}

file

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();
	}
}

輸出

file

4.3 觀察者

什么是觀察者模式

觀察者模式(Observer Pattern):當物件間存在一對多關系時,則使用觀察者模式(Observer Pattern),比如,當一個物件被修改時,則會自動通知依賴它的物件,

Spring 的事件機制就是具體的觀察者模式的實作

spring中的多播與事件
AbstractApplicationContext#initApplicationEventMulticaster
    
AbstractApplicationContext#registerListeners

觀察者模式有哪些角色?

事件 ApplicationEvent 是所有事件物件的父類,繼承JDK的EventObject

事件監聽 ApplicationListener,也就是觀察者物件,繼承自 JDK 的 EventListener,可以監聽到事件;該類中只有一個方法 onApplicationEvent,當監聽的事件發生后該方法會被執行,

事件發布ApplicationContext, 實作事件的發布,

(發布事件)

or=========

Spring中的多播

事件發布 ApplicationEventMulticaster,用于事件監聽器的注冊和事件的廣播,

file

自定義一個事件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"   />-->
	<bean id="applicationContextListener" />
	<bean id="applicationContextListenerTwo" />

	<bean id="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("測驗..."));
	}

}

多播發布

file
工廠發布

file

總結:

? 1、spring的事件驅動模型使用的是 觀察者模式

  2、通過ApplicationEvent抽象類和ApplicationListener介面,可以實作事件處理

  3、ApplicationEventMulticaster事件廣播器實作了監聽器的注冊,一般不需要我們實作,只需要顯示的呼叫 applicationcontext.publisherEvent方法即可

? 4、使用bean工廠發布和使用多播器效果是一樣的

本文由傳智教育博學谷教研團隊發布,

如果本文對您有幫助,歡迎關注點贊;如果您有任何建議也可留言評論私信,您的支持是我堅持創作的動力,

轉載請注明出處!

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

標籤:Java

上一篇:對redis的實戰理解

下一篇:聊一聊作為高并發系統基石之一的快取,會用很簡單,用好才是技識訓

標籤雲
其他(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