愛上源碼,重學Spring IoC深入

来源:https://www.cnblogs.com/jiagooushi/archive/2022/10/26/16828457.html
-Advertisement-
Play Games

回答: 我們為什麼要學習源碼? 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的類關係是這樣的……

file

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:

下麵的流轉記不住沒有關係

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

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

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

輸出如下

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" 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("測試..."));
	}

}

多播發佈

file
工廠發佈

file

總結:

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

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

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

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

本文由傳智教育博學谷教研團隊發佈。

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

轉載請註明出處!


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 這裡我們以QQ郵箱為例。 一、導入依賴: <dependencies> <!-- https://mvnrepository.com/artifact/javax.activation/activation --> <dependency> <groupId>javax.activation</gr ...
  • 泛型 泛型定義 Scala的泛型和Java中的泛型表達的含義都是一樣的,對處理的數據類型進行約束,但是Scala提供了更加強大的功能 scala中的泛型採用中括弧 scala中的泛型是不可變的 泛型和類型不是一個層面的東西 所以scala中泛型和類型無法聯合使用 泛型語法 如果能將類型和泛型當成一個 ...
  • 核心功能點 【1】服務註冊:Nacos Client會通過發送REST請求的方式向Nacos Server註冊自己的服務,提供自身的元數據,比如ip地址、埠等信息。Nacos Server接收到註冊請求後,就會把這些元數據信息存儲在一個雙層的記憶體Map中。 【2】服務心跳:在服務註冊後,Nacos ...
  • 1、項目模塊介紹 2、 父項目 主要依賴 spring-cloud 的 版本控制 <properties> <!-- springCloud 版本 --> <scd.version>Dalston.SR4</scd.version> </properties> <dependencyManageme ...
  • RabbitMQ安裝說明文檔(超詳細版本) 1. 安裝依賴環境 線上安裝依賴環境: yum install build-essential openssl openssl-devel unixODBC unixODBC-devel make gcc gcc-c++ kernel-devel m4 n ...
  • 在服務端開發中,緩存常常被當做系統性能扛壓的不二之選。在實施方案上,緩存使用策略雖有一定普適性,卻也並非完全絕對,需要結合實際的項目訴求與場景進行綜合權衡與考量,進而得出符合自己項目的最佳實踐。 ...
  • 折騰好 Wordpress,開始安裝插件了,結果直接報錯 Installation failed: Could not create directory 當時站點健康工具(Site Health)里的文件系統許可權(Filesystem Permissions) 全是 Not Writable 搜了一 ...
  • 隱式轉換 精度小的類型可以自動轉換為精度大的類型,這個轉換過程無需開發人員參與,由編譯器自動完成,這個轉換操作我們稱之為隱式轉換。 如果程式編譯出錯,編譯器會嘗試在整個的作用域中查找能夠讓程式編譯通過的方式 如果找到,那麼編譯器會嘗試二次編譯,讓之前編譯出現錯誤的代碼經過轉換後能夠編譯通過。 這個轉 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...