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…