Spring 源碼學習 1:ApplicationContext

Spring 源碼學習 1:ApplicationContext

Bean 定義和 Bean 實例

AnnotationConfigApplicationContext

首先,創建一個最簡單的 Spring Boot 應用。

在入口類中接收SpringApplication.run的返回值:

@SpringBootApplication
public class DemoApplication {public static void main(String[] args) {ConfigurableApplicationContext context = SpringApplication.run(DemoApplication.class, args);System.out.println(context);}}

ConfigurableApplicationContext是一個接口,查看其繼承關系:

image-20250618101827464

比較重要的父接口有ApplicationContextBeanFactory

打上斷點,啟動調試模式,可以看到實際運行時使用的真實類型:

image-20250618102023491

查看AnnotationConfigApplicationContext的繼承關系比較復雜,最值得注意的:

image-20250618102843929

GenericApplicationContext實現了抽象類AbstractApplicationContext,可以看做是ApplicationContext接口的一個通用實現。其包含一個beanFactory屬性:

private final DefaultListableBeanFactory beanFactory;

DefaultListableBeanFactory

老版本的 Spring 會直接使用DefaultListableBeanFactory作為容器實現,新版本的 Spring 在外邊又包裝了一層。SpringBean 的注冊、獲取等操作都由DefaultListableBeanFactory實現,它包含一個屬性beanDefinitionMap

private final Map<String, BeanDefinition> beanDefinitionMap = new ConcurrentHashMap<>(256);

這個 Map 保存了 Spring Bean 的定義,key 則是 Spring Bean 的名稱。

可以用代碼的方式打印當前項目中已經注冊的 Bean 定義:

ConfigurableApplicationContext context = SpringApplication.run(DemoApplication.class, args);
System.out.println(context);
AnnotationConfigApplicationContext acaContext = (AnnotationConfigApplicationContext) context;
ConfigurableListableBeanFactory beanFactory = acaContext.getBeanFactory();
String[] beanDefinitionNames = beanFactory.getBeanDefinitionNames();
for (String beanDefinitionName : beanDefinitionNames) {System.out.println(beanFactory.getBean(beanDefinitionName));
}

image-20250618111633404

可以看到,即使沒有任何自定義的 Bean,默認情況下 Spring 框架也會注冊很多必要的 Bean,甚至包含了AnnotationConfigApplicationContext容器自己。

如果添加了自定義 Bean,在這里也會看到。

DefaultSingletonBeanRegistry

查看DefaultListableBeanFactory的繼承關系:

image-20250618112617884

它有一個基類DefaultSingletonBeanRegistry,這個類使用一個 Map 結構保存所有的單例 Bean:

private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);

同樣,可以用代碼的方式打印 Bean 對象:

DefaultListableBeanFactory beanFactory = (DefaultListableBeanFactory)acaContext.getBeanFactory();
DefaultSingletonBeanRegistry registry = beanFactory;
String[] singletonNames = registry.getSingletonNames();
for (String name : singletonNames) {System.out.println(registry.getSingleton(name));
}

Bean 對象的打印結果要多于 Bean 定義,這是顯而易見的,因為同一個 Bean 定義可以生成多個 Bean 對象:

image-20250618113755248

小結

從上面的分析不難看出,AnnotationConfigApplicationContext的設計采用了代理(委托)模式,它包含一個 DefaultListableBeanFactory 類型的 BeanFactory,具體的 Bean 定義和 Bean 實例都保存在其中,而AnnotationConfigApplicationContext 本身所有對 Bean 的注冊、獲取等操作都代理給DefaultListableBeanFactory 的對應方法實現。而DefaultListableBeanFactoryAnnotationConfigApplicationContext 都實現了基本的BeanFactory接口,這也正是代理模式的基礎。

其它功能

上面介紹了 Bean 工廠的核心功能——維護 Bean 定義和 Bean 實例。事實上 ApplicationContext 除了繼承 BeanFactory 的相關接口,還繼承了其它的幾個接口:

image-20250618114816873

EnvironmentCapable

可以利用這個接口獲取環境信息,比如系統環境變量和項目的配置信息:

private static void printEnvironment(EnvironmentCapable environmentCapable) {Environment environment = environmentCapable.getEnvironment();String property = environment.getProperty("spring.application.name");System.out.println(property);String javaHome = environment.getProperty("java_home");System.out.println(javaHome);
}

注意,在獲取系統環境變量時,是大小寫不敏感的,比如這里的 getProperty("java_home"),實際上系統中配置的環境變量是大寫的JAVA_HOME,但這里依然可以獲取到。

ApplicationEventPublisher

是 Spring 事件框架的一部分,可以用它來發布事件:

private static void testApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {applicationEventPublisher.publishEvent(new UserUpdatedEvent(applicationEventPublisher, 1L));
}

UserUpdatedEvent是用戶自定義事件,代表用戶數據被更新,提示事件處理程序進行相應處理(比如更新用戶緩存):

public class UserUpdatedEvent extends ApplicationEvent {@Getterprivate final Long userId;public UserUpdatedEvent(Object source, Long userId) {super(source);this.userId = userId;}
}

要處理這個事件,需要添加事件監聽:

@Component
public class UserListener {@EventListenerpublic void userUpdatedEventHandler(UserUpdatedEvent userUpdatedEvent){Long userId = userUpdatedEvent.getUserId();System.out.println("User updated: " + userId);}
}

resourcePatternResolver

可以用這個接口獲取項目的資源文件:

private static void printResource(ResourcePatternResolver resourcePatternResolver) throws IOException {Resource resource = resourcePatternResolver.getResource("classpath:application.properties");BufferedReader reader = FileUtil.getReader(resource.getFile(), StandardCharsets.UTF_8);do{String line = reader.readLine();System.out.println(line);}while(reader.ready());
}

這里使用了 hutool 依賴。

classpath:xxx只會返回在 classpath 下檢索到的第一個匹配的資源文件。如果要檢索所有的 classpath 路徑中匹配到的資源文件(比如包含其它 jar 包中的資源文件),需要使用classpath*:xxx

private static void printResources(ResourcePatternResolver resourcePatternResolver) throws IOException {Resource[] resources = resourcePatternResolver.getResources("classpath*:META-INF/spring.factories");for(Resource resource : resources){System.out.println(resource.getFilename());}
}

打印當前項目使用的 classpath:

private static void printClassPaths(){String classpath = System.getProperty("java.class.path");String[] classpathArr = classpath.split(";");for (String classpathStr : classpathArr) {System.out.println(classpathStr);}
}

image-20250618123542653

可以看到結果中包含了通過 Maven 導入的 jar 包。

MessageSource

MessageSource 可以實現國際化。

添加國際化相關資源文件:

image-20250619115108598

messages_en_US.properties

login.title=User Login
login.username=Username

messages_zh_CN.properties

login.title=用戶登錄
login.username=用戶名

在配置文件application.properties中添加配置信息:

spring.messages.basename=messages/messages
spring.messages.encoding=UTF-8

使用 MessageSource 按照語言地域輸出信息:

private static void testMessageResource(MessageSource messageSource) {String enTitle = messageSource.getMessage("login.title", null, Locale.US);String enUserName = messageSource.getMessage("login.username", null, Locale.US);String cnTitle = messageSource.getMessage("login.title", null, Locale.CHINA);String cnUserName = messageSource.getMessage("login.username", null, Locale.CHINA);System.out.println(String.format("%s %s", enTitle, enUserName));System.out.println(String.format("%s %s", cnTitle, cnUserName));
}

本文的完整示例代碼可以從這里獲取。

The End.

參考資料

  • 黑馬程序員Spring視頻教程,深度講解spring5底層原理

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

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

相關文章

CppCon 2017 學習:Design Patterns for Low-Level Real-Time Rendering

這段內容講的是離散顯卡&#xff08;Discrete GPU&#xff09;中的內存管理模型&#xff0c;重點是CPU和GPU各自獨立管理自己的物理內存&#xff0c;以及它們如何通過虛擬內存和DMA引擎實現高效通信。以下是詳細的理解和梳理&#xff1a; 1. 基本概念 CPU 和 GPU 是兩個獨立的…

【單調隊列】-----【原理+模版】

單調隊列 一、什么是單調隊列&#xff1f; 單調隊列是一種在滑動窗口或區間查詢中維護候選元素單調性的數據結構&#xff0c;通常用于解決“滑動窗口最大值/最小值”等問題。 核心思想是&#xff1a;利用雙端隊列&#xff08;deque&#xff09;維護當前窗口內或候選范圍內元素…

CSS語法中的選擇器與屬性詳解

CSS:層疊樣式表&#xff0c;Cascading Style Sheets 層疊樣式表 內容和樣式分離解耦&#xff0c;便于修改樣式。 特殊說明&#xff1a; 最后一條聲明可以沒有分號&#xff0c;但是為了以后修改方便&#xff0c;一般也加上分號為了使用樣式更加容易閱讀&#xff0c;可以將每條代…

模擬設計的軟件工程項目

考核題目 論文論述題&#xff1a;結合你 參與開發、調研或模擬設計的軟件工程項目 &#xff0c;撰寫一篇論文 完成以下任務&#xff0c;論文題目為《面向微服務架構的軟件系統設計與建模分析》&#xff0c;總分&#xff1a; 100 分。 1. 考核內容&#xff1a; 一、系統論述…

個人理解redis中IO多路復用整個網絡處理流

文章目錄 1.redis網絡處理流2.理解通知機制 1.redis網絡處理流 10個客戶端通過TCP與Redis建立socket連接&#xff0c;發送GET name指令到服務器端。服務器端的網卡接收數據&#xff0c;數據進入內核態的網絡協議棧。Redis通過IO多路復用機制中的epoll向內核注冊監聽這些socket的…

【鄭州輕工業大學|數據庫】數據庫課設-酒店管理系統

該數據課設是一個基于酒店管理系統的數據庫設計 建庫語句 create database hotel_room default charset utf8 collate utf8_general_ci;建表語句 use hotel_room;-- 房型表 create table room_type( id bigint primary key auto_increment comment 房型id, name varchar(50)…

TCP 三次握手與四次揮手詳解

前言 在當今互聯網時代&#xff0c;前端開發的工作范疇早已超越了簡單的頁面布局和交互設計。隨著前端應用復雜度的不斷提高&#xff0c;對網絡性能的優化已成為前端工程師不可忽視的重要職責。而要真正理解并優化網絡性能&#xff0c;就需要探究支撐整個互聯網的基礎協議——…

RTD2735TD/RTD2738 (HDMI,DP轉EDP 高分辨率高刷新率顯示器驅動芯片)

一、芯片概述 RTD2738是瑞昱半導體&#xff08;Realtek&#xff09;推出的一款高性能顯示驅動芯片&#xff0c;專為高端顯示器、便攜屏、專業顯示設備及多屏拼接系統設計。其核心優勢在于支持4K分辨率下240Hz高刷新率及8K30Hz顯示&#xff0c;通過集成DisplayPort 1.4a與HDMI …

C++實現手寫strlen函數

要實現求字符串長度的函數&#xff0c;核心思路是通過指針或索引遍歷字符串&#xff0c;直到遇到字符串結束標志 \0 。以下是兩種常見的實現方式&#xff1a; 指針遍歷版本 #include <iostream> using namespace std; // 指針方式實現strlen size_t myStrlen(const cha…

NVPL 函數庫介紹和使用

文章目錄 NVPL 函數庫介紹和使用什么是 NVPLNVPL 的主要組件NVPL 的優勢安裝 NVPL基本使用示例示例1&#xff1a;使用 NVPL RAND 生成隨機數示例2&#xff1a;使用 NVPL FFT 進行快速傅里葉變換 編譯 NVPL 程序性能優化建議總結 NVPL 函數庫介紹和使用 什么是 NVPL NVPL (NVI…

HTTP相關內容補充

目錄 一、URI 和 URL 二、使用 Cookie 的狀態管理 三、返回結果的 HTTP狀態碼 一、URI 和 URL URI &#xff1a;統一資源標識符 URL&#xff1a;統一資源定位符 URI 格式 登錄信息&#xff08;認證&#xff09;指定用戶名和密碼作為從服務器端獲取資源時必要的登錄信息&a…

MySQL: Invalid use of group function

https://stackoverflow.com/questions/2330840/mysql-invalid-use-of-group-function 出錯SQL: 錯誤原因&#xff1a; 1. 不能在 WHERE 子句中使用聚合&#xff08;或分組&#xff09;函數 2. HAVING 只能篩選分組后的聚合結果或分組字段 # Write your MySQL query statem…

C#財政票查驗接口集成-醫療發票查驗-非稅收入票據查驗接口

財政票據是企事業單位、醫療機構、金融機構等組織的重要報銷憑證&#xff0c;其真實性、完整性和合規性日益受到重視。現如今&#xff0c;為有效防范虛假票據報銷、入賬、資金流失等問題的發生&#xff0c;財政票據查驗接口&#xff0c;結合財政票據識別接口&#xff0c;旨在為…

瀏覽器基礎及緩存

目錄 瀏覽器概述 主流瀏覽器&#xff1a;IE、Chrome、Firefox、Safari Chrome Firefox IE Safari 瀏覽器內核 核心職責 主流瀏覽器內核 JavaScript引擎 主流的JavaScript引擎 瀏覽器兼容性 瀏覽器渲染 渲染引擎的基本流程 DOM和render樹構建 html解析 DOM 渲染…

Ubuntu 安裝Telnet服務

1. 安裝Telnet 客戶端 sudo apt-get install telnet 2. 安裝Telnet 服務器 &#xff08;這樣才能用A電腦的客戶端連接B電腦的Telnet服務&#xff09; sudo apt-get install telnetd 3. 這時候Telnet服務器是無法自我啟動的&#xff0c;需要網絡守護進程服務程序來管理…

AI+預測3D新模型百十個定位預測+膽碼預測+去和尾2025年6月19日第113彈

從今天開始&#xff0c;咱們還是暫時基于舊的模型進行預測&#xff0c;好了&#xff0c;廢話不多說&#xff0c;按照老辦法&#xff0c;重點8-9碼定位&#xff0c;配合三膽下1或下2&#xff0c;殺1-2個和尾&#xff0c;再殺4-5個和值&#xff0c;可以做到100-300注左右。 (1)定…

觀察者模式 vs 發布訂閱模式詳解教程

&#x1f31f;觀察者模式 vs 發布訂閱模式詳解教程 收藏 點贊 關注&#xff0c;持續更新高頻面試知識庫&#xff01;&#x1f680; 一、核心概念&#xff08;總&#xff09; 在軟件開發中&#xff0c;觀察者模式&#xff08;Observer&#xff09; 和 發布訂閱模式&#xff0…

【云馨AI-大模型】MD2Card:從Markdown到知識卡片的完美轉變

Markdown的魅力與挑戰MD2Card的核心功能使用體驗與案例分析總結 在當今這個信息快速傳播的時代&#xff0c;內容創作者們一直在尋找更有效的方式來呈現他們的想法和知識。無論是為了個人學習筆記、團隊內部的知識分享還是對外的內容發布&#xff0c;一個清晰、美觀的展示方式顯…

【實戰教程】OPEN API 雷池社區版自動拉黑IP

老版本使用雷池社區版的時候都需要在界面操作&#xff0c;但是網絡攻擊往往都是無規律的&#xff0c;每次都手動操作非常累 前一段時間雷池社區版剛好開放了OPEN API 功能&#xff0c;可以支持大家使用API的方式進行管理了 但是沒有相關文檔非常難受&#xff0c;一直沒有使用…

Hot100——鏈表專項

目錄 相交鏈表 反轉鏈表 回文鏈表 環形鏈表 合并兩個有序鏈表 相交鏈表 ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {if (headA nullptr || headB nullptr) {return nullptr;}ListNode *pA headA;ListNode *pB headB;while (pA ! pB) {pA (pA…