對Spring源碼的學習:Bean實例化流程

目錄

SpringBean實例化流程

Spring的后處理器

Bean工廠后處理器

Bean后處理器


SpringBean實例化流程

Spring容器在進行初始化時,會將xml配置的<bean>的信息封裝成一個BeanDefinition對象,所有的BeanDefinition存儲到一個名為beanDefinitionMap的Map集合中去,Spring框架在對該Map進行遍歷,使用反射創建Bean實例對象,創建好的Bean對象存儲在一個名為sinaletonObiects的Map集合中,當調用getBean方法時則最終從該Map集合中取出Bean實例對象返回

對該方法進行斷點觀察

因此我們可以總結如下流程圖

Spring的后處理器

Spring的后處理器是Spring對外開發的重要擴展點,允許我們介入到Bean的整個實例化流程中來,以達到動態注冊BeanDefinition,動態修改BeanDefinition,以及動態修改Bean的作用。Spring主要有兩種后處理器:

  • BeanFactoryPostProcessor:Bean工廠后外理器,在BeanDefinitionMap填充完畢,Bean實例化之前執行。
  • BeanPostProcessor:Bean后處理器,一般在Bean實例化之后,填充到單例池singletonObjects之前執行。

Bean工廠后處理器

我們需要實現BeanFactoryPostProcessor接口,然后重寫其方法,該方法中的參數實際上就是Spring容器,我們可以通過該容器獲取BeanDefinition信息,然后對這些信息進行修改,在下面代碼中,我們修改了UserService的全路徑,這會導致在實例化Bean時通過反射拿到的實際上時UserDao類。

public class MyBeanFactoryProcessor implements BeanFactoryPostProcessor {@Override//在Map加載完成后執行public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {//beanFactory無法一次獲取全部的Map信息,只能通過key獲取BeanDefinition beanDefinition = beanFactory.getBeanDefinition("userService");System.out.println("當前的Bean的全路徑"+beanDefinition.getBeanClassName());beanDefinition.setBeanClassName("com.zmt.dao.impl.UserDaoImpl");}
}

配置完成不代表生效,我們需要將該類交給Spring管理,然后由Spring來回調該方法,因此需要修改xml文件

<beans><bean id="userService" class="com.zmt.service.impl.UserServiceImpl"></bean><bean id="userDao" class="com.zmt.dao.impl.UserDaoImpl"></bean><!-- 添加為Bean對象 --><bean class="com.zmt.processor.MyBeanFactoryProcessor"></bean>
</beans>

測試代碼如下

public class ApplicationContextTest {public static void main(String[] args) {ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");Object userService =  context.getBean("userService");System.out.println(userService);}
}

運行結果如下

當前的Bean的全路徑com.zmt.service.impl.UserServiceImpl

com.zmt.dao.impl.UserDaoImpl@5649fd9b

如果我們需要向Map中添加BeanDefinition數據,在創建完BeanDefinition之后無法將其注冊到Map中,需要實現該接口的子接口BeanDefinitionRegistryPostProcessor。下面我們創建一個PersonDao接口以及實現類,但是不在xml文件中定義,通過Bean工廠后處理器添加在Map當中,測試能否getBean時獲取到對應類

<beans><bean id="userService" class="com.zmt.service.impl.UserServiceImpl"></bean><bean id="userDao" class="com.zmt.dao.impl.UserDaoImpl"></bean><bean class="com.zmt.processor.MyBeanFactoryRegistryProcessor"></bean>
</beans>
public class MyBeanFactoryRegistryProcessor implements BeanDefinitionRegistryPostProcessor {@Overridepublic void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry beanDefinitionRegistry) throws BeansException {RootBeanDefinition beanDefinition = new RootBeanDefinition();//設置全路徑beanDefinition.setBeanClassName("com.zmt.dao.impl.PersonDaoImpl");//添加到Map中beanDefinitionRegistry.registerBeanDefinition("personDao",beanDefinition);}//該方式是父接口中的方法,我們可以不編寫業務代碼@Overridepublic void postProcessBeanFactory(ConfigurableListableBeanFactory configurableListableBeanFactory) throws BeansException {}
}

測試代碼

public class ApplicationContextTest {public static void main(String[] args) {ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");PersonDaoImpl personDao = context.getBean(PersonDaoImpl.class);System.out.println(personDao);}
}
/**
* 運行結果如下
* com.zmt.dao.impl.PersonDaoImpl@64bfbc86
*/

此時,我們可能存在一個疑惑,那就是如果配置了多個Bean工廠后處理器,那么執行順序應該是什么?接下來我們對其進行測試

public class MyBeanFactoryProcessor implements BeanFactoryPostProcessor {@Override//在Map加載完成后執行public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {//beanFactory無法一次獲取全部的Map信息,只能通過key獲取System.out.println("執行MyBeanFactoryProcessor中的postProcessBeanFactory方法");}
}public class MyBeanFactoryRegistryProcessor implements BeanDefinitionRegistryPostProcessor {@Overridepublic void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry beanDefinitionRegistry) throws BeansException {System.out.println("執行MyBeanFactoryRegistryProcessor中的postProcessBeanDefinitionRegistry方法");}@Overridepublic void postProcessBeanFactory(ConfigurableListableBeanFactory configurableListableBeanFactory) throws BeansException {System.out.println("執行MyBeanFactoryRegistryProcessor中的postProcessBeanFactory方法");}
}

執行結果如下

執行MyBeanFactoryRegistryProcessor中的postProcessBeanDefinitionRegistry方法

執行MyBeanFactoryRegistryProcessor中的postProcessBeanFactory方法

執行MyBeanFactoryProcessor中的postProcessBeanFactory方法

由此我們可以得出結論,優先執行BeanFactoryProcessor的子類獨有方法,再執行子類中繼承來的父類方法,最后執行BeanFactory中的方法。

因此,我們可以總結Bean工廠后處理器的執行時機在Spring啟動流程中如下

Bean后處理器

Bean被實例化后,到最終緩存到名為singletonObjects單例池之前,中間會經過Bean的初始化過程,例如: 屬性的填充、初始方法init的執行等,其中有一個對外進行擴展的點BeanPostProcessor,我們稱為Bean后處理。跟Bean工廠后處理器相似,它也是一個接口,實現了該接口并被容器管理的BeanPostProcessor,會在流程節點上被Spring自動調用。

Bean后處理器中有兩個方法分別為

要實現Bean后處理器,需要實現BeanPostProcessor接口,重寫需要要實現的方法。接下來編寫Bean后處理器來測試Bean后處理器兩個方法的執行時機

public class MyBeanPostProcessor implements BeanPostProcessor {@Overridepublic Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {System.out.println(beanName+":Bean后處理器初始化前方法");return bean;}@Overridepublic Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {System.out.println(beanName+":Bean后處理器初始化后方法");return bean;}
}
public class UserServiceImpl implements UserService , InitializingBean {public UserServiceImpl() {System.out.println("userService調用無參構造器");}private void init(){System.out.println("userService執行init方法");}//該方法由beanFactory來調用,set注入public void setUserDao(UserDao userDao){System.out.println("由bean工廠調用該set方法");}@Overridepublic void afterPropertiesSet() throws Exception {System.out.println("userService執行屬性注入后方法");}
}
<beans><bean id="userService" class="com.zmt.service.impl.UserServiceImpl" init-method="init"><property name="userDao" ref="userDao"></property></bean><bean id="userDao" class="com.zmt.dao.impl.UserDaoImpl"></bean><bean class="com.zmt.processor.MyBeanFactoryProcessor"></bean><bean class="com.zmt.processor.MyBeanPostProcessor"></bean></beans>

運行結果如下

執行MyBeanFactoryProcessor中的postProcessBeanFactory方法
userService調用無參構造器
userDao:Bean后處理器初始化前方法
userDao:Bean后處理器初始化后方法
由bean工廠調用該set方法
userService:Bean后處理器初始化前方法
userService執行屬性注入后方法
userService執行init方法
userService:Bean后處理器初始化后方法

在Spring中,大量采用Bean后處理器對Bean對象進行加強處理,通常會對bean對象做代理,下面就是一個對userService類對象進行進行加強,在執行方法時統計該方法執行時間

public class UserServiceImpl implements UserService {private String name;public void setName(String name) {this.name = name;}@Overridepublic void print() {try {Thread.sleep(3000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("方法print開始執行");System.out.println("name:"+name);}
}
public class MyTimeLogBeanPostProcessor implements BeanPostProcessor {/*** 后處理器前初始化方法。作用時間為bean實例化完成之后,可以對bean對象初始化賦值*/@Overridepublic Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {if (bean instanceof UserService){((UserSerivce) bean).setName("張三");}return bean;}/*** 后處理器后處理方法。作用時間為bean添加到單例池之前,主要作用是對bean對象做增強*/@Overridepublic Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {Object proxyInstance = Proxy.newProxyInstance(bean.getClass().getClassLoader(),bean.getClass().getInterfaces(),(proxy, method, args) -> {System.out.println("記錄時間" + new Date());Object invoke = method.invoke(bean, args);System.out.println("記錄停止" + new Date());return invoke;});return proxyInstance;}
}

執行結果如下

方法print開始執行Fri Dec 08 17:52:46 CST 2023
方法print開始執行
name:張三
方法print執行結束Fri Dec 08 17:52:49 CST 2023

被加強后的bean會被加載到單例池中,并且無論去執行哪些方法都會執行時間日志輸出。

總結Bean處理器執行流程如下

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/213127.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/213127.shtml
英文地址,請注明出處:http://en.pswp.cn/news/213127.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

Docker容器的可視化管理工具—DockerUI本地部署與遠程訪問

文章目錄 前言1. 安裝部署DockerUI2. 安裝cpolar內網穿透3. 配置DockerUI公網訪問地址4. 公網遠程訪問DockerUI5. 固定DockerUI公網地址 前言 DockerUI是一個docker容器鏡像的可視化圖形化管理工具。DockerUI可以用來輕松構建、管理和維護docker環境。它是完全開源且免費的。基…

【GlobalMapper精品教程】066:shp轉JSON(GeoJson)案例實現

文章目錄 一、JSON與GeoJson的區別二、globalmapper實現shp轉JSON1. 加載shp數據2. shp轉json一、JSON與GeoJson的區別 JSON(JavaScript Object Notation)是一種輕量級的數據交換格式,它基于JavaScript的語法,可以將JavaScript對象中表示的一組數據轉換為字符串,在函數之…

ElasticSearch之cat recovery API

命令樣例如下&#xff1a; curl -X GET "https://localhost:9200/_cat/recovery?vtrue&pretty" --cacert $ES_HOME/config/certs/http_ca.crt -u "elastic:ohCxPHQBEs5*lo7F9"執行結果輸出如下&#xff1a; index shard time type sta…

2023 CCF中國軟件大會(CCF ChinaSoft) “程序語義深度理解前沿進展”論壇成功召開...

2023年12月2日&#xff0c;2023年度CCF中國軟件大會軟件程序語義深度理解前沿進展論壇成功召開。 本次論壇由南京大學卜磊老師和國防科技大學陳振邦老師主持&#xff0c;計算機研究與發展期刊代表侯麗珊老師致辭&#xff0c;旨在反映程序語義理解及其應用相關研究前沿進展與實踐…

Vue之模板語法

模板語法有兩大類&#xff1a; 1.插值語法 2.指令語法 讓我為大家介紹一下吧&#xff01; 一、插值語法 功能:用于解析標簽體內容。 寫法: {{xxx}}&#xff0c;xxx是js表達式&#xff0c;且可以直接讀取到data中的所有屬性。 舉個例子&#xff1a; <!DOCTYPE html> &l…

探索未來新趨勢:鴻蒙系統的嶄新時代

探索未來新趨勢&#xff1a;鴻蒙系統的嶄新時代 隨著科技的不斷發展&#xff0c;操作系統作為計算機和移動設備的核心&#xff0c;扮演著至關重要的角色。近年來&#xff0c;一種備受矚目的操作系統——鴻蒙系統&#xff08;HarmonyOS&#xff09;嶄露頭角&#xff0c;正引領著…

uniapp 微信小程序請求攔截器 接口封裝

前言&#xff1a; 請求攔截器可以在我們需要傳遞請求頭的時候使用&#xff0c;例如&#xff1a;token 也會在當token發生變化的時候給予響應&#xff0c;所以我們做好對應的判斷即可 話不多說&#xff0c;直接進入正題&#xff1a; 1.首先在根目錄創建common文件夾&#xff0c…

Selenium 中并行測試的重要性!

隨著技術的進步&#xff0c;測試解決方案變得更具可擴展性&#xff0c;加速了團隊從手動測試到Selenium測試自動化的轉型。但是成年人的世界&#xff0c;沒有什么是容易的。對于許多團隊來說&#xff0c;并行運行多個測試仍然是不可擴展的。他們傾向于遵循傳統的順序執行測試方…

MIT6.5840-2023-Lab2A: Raft-leader election

前置知識 什么是一致性算法&#xff1f; 安全性保證&#xff0c;絕對不會返回一個錯誤的結果&#xff1b;可用性&#xff0c;容忍集群部分節點失敗&#xff1b;不依賴時序來保證一致性&#xff1b;一條指令可以盡可能快的在集群中大多數節點響應一輪遠程過程調用時完成。小部分…

uniapp實戰 —— 可滾動區域 scroll-view (自適配高度,下拉刷新)

自適配高度 自定義的頂部導航欄&#xff0c;可參考博文 https://blog.csdn.net/weixin_41192489/article/details/134852124 如圖可見&#xff0c;在頁面滾動過程中&#xff0c;頂部導航欄和底欄未動&#xff0c;僅中間的內容區域可滾動。 整個頁面的高度設置為 100%&#xf…

鴻蒙開發—學習聲明式UI

基本UI描述 ArkTS通過裝飾器Component和Entry裝飾struct關鍵字聲明的數據結構&#xff0c;構成一個自定義組件。自定義組件中提供了一個build函數&#xff0c;開發者需在該函數內以鏈式調用的方式進行基本的UI描述&#xff0c;UI描述的方法請參考UI描述規范。 基本概念 stru…

GZ029 智能電子產品設計與開發賽題第4套

2023年全國職業院校技能大賽高職組 “GZ029智能電子產品設計與開發”賽項賽卷四 題目&#xff1a;模擬工業傳送帶物品檢測系統的設計與開發 1 競賽任務 在智能電視機上播放工業傳送帶傳輸物品視頻&#xff0c;模擬工業傳送帶物品檢測系統&#xff08;以下簡稱物品檢測系統&…

DALI1.0學習——BIT解碼

最近在學習DALI調光相關知識并下載了Microchip提供的基于ATMega88PA的軟件工程及硬件設計參考方案。寫這些文章的目的就是把自己對知識的理解作一些梳理。 芯片廠果然專業&#xff0c;考慮得相當周到&#xff0c;為了芯片銷量連軟件和硬件方案全都提供了。芯片廠關于DALI1.0實…

【unity小技巧】實現槍武器隨鏡頭手臂搖擺效果

文章目錄 前言方法一、改變武器位置方法二、改變武器旋轉結語完結 前言 如果我們視角移動轉向&#xff0c;武器如果不跟著進行搖擺&#xff0c;會感覺我們的動作很生硬&#xff0c;特別是射擊類游戲&#xff0c;如下 實現武器搖擺這里主要分享兩種實現方法&#xff0c;一種是…

xtu oj 1271 color

題目描述 Alice在玩一個游戲&#xff0c;她在一個mn的格子里&#xff0c;隨機涂黑k個格子。然后她每次可以把一行或者一列的格子染成紅色&#xff0c;但是這一行中不能有黑色的格子。 請問她最多能把多少個格子涂成紅色&#xff1f; 輸入 第一行是一個整數T(T≤100)&#xf…

華為OD機試 - 數的分解(Java JS Python C)

題目描述 給定一個正整數 n,如果能夠分解為 m(m > 1)個連續正整數之和,請輸出所有分解中,m最小的分解。 如果給定整數無法分解為連續正整數,則輸出字符串"N"。 輸入描述 輸入數據為一整數,范圍為 (1, 2^30] 輸出描述 比如輸入為: 21 輸出: 21=10+11 …

SSD數據在寫入NAND之前為何要隨機化?-Part1

SSD的存儲介質是什么&#xff0c;它就是NAND閃存。那你知道NAND閃存是怎么工作的嗎&#xff1f;其實&#xff0c;它就是由很多個晶體管組成的。這些晶體管里面存儲著電荷&#xff0c;代表著我們的二進制數據&#xff0c;要么是“0”&#xff0c;要么是“1”。NAND閃存原理上是一…

唯創知音WT588F02B語音芯片在電子針療儀中的聲音播放提示應用

在醫療技術領域&#xff0c;電子針療儀作為一種非侵入性的治療設備&#xff0c;被廣泛應用于各種疼痛管理和康復治療。然而&#xff0c;操作電子針療儀需要一定的專業知識和經驗&#xff0c;以確保安全有效的治療。為了解決這一難題&#xff0c;唯創知音WT588F02B語音芯片被應用…

0基礎學java-day14-(集合)

一、集合 前面我們保存多個數據使用的是數組&#xff0c;那么數組有不足的地方&#xff0c;我們分析一下 1.數組 2 集合 數據類型也可以不一樣 3.集合的框架體系 Java 的集合類很多&#xff0c;主要分為兩大類&#xff0c;如圖 &#xff1a;[背下來] package com.hspedu.c…

設計模式之GoF23介紹

深入探討設計模式&#xff1a;構建可維護、可擴展的軟件架構 一、設計模式的背景1.1 什么是設計模式1.2 設計模式的歷史 二、設計模式的分類2.1 創建型模式2.2 結構型模式2.3 行為型模式 三、七大設計原則四、設計模式關系結論 :rocket: :rocket: :rocket: 在軟件開發領域&…