news 2026/9/13 3:42:44

source-code-hunter 源码解析:Spring MapPropertySource 与 PropertySource 环境抽象体系

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
source-code-hunter 源码解析:Spring MapPropertySource 与 PropertySource 环境抽象体系

source-code-hunter 源码解析:Spring MapPropertySource 与 PropertySource 环境抽象体系

【免费下载链接】source-code-hunter😱 从源码层面,剖析挖掘互联网行业主流技术的底层实现原理,为广大开发者 “提升技术深度” 提供便利。目前开放 Spring 全家桶,Mybatis、Netty、Dubbo 框架,及 Redis、Tomcat 中间件等项目地址: https://gitcode.com/GitHub_Trending/so/source-code-hunter

导读

org.springframework.core.env.MapPropertySource是 Spring 环境抽象(Environment/PropertySource)中最基础、使用最频繁的实现类之一——它以Map<String, Object>为内部数据结构,把"属性名到属性值"的映射直接暴露给 Spring 的占位符解析与属性注入机制。本文以本仓库 Spring-MapPropertySource.md 为核心,逐方法拆解其源码实现,并结合EnumerablePropertySourcePropertiesPropertySourceResourcePropertySource等兄弟/子类及 Spring Boot 启动加载链路,讲透这一"最不起眼却无处不在"的配置载体。读完本文,你将掌握 PropertySource 家族的继承体系、Map 型属性源的读写语义,以及它在 Spring/Spring Boot 配置解析中的真实调用场景。

图片说明:Spring 环境抽象中PropertySource及其相关类的整体类图,MapPropertySource位于该体系中。

一、类定位:MapPropertySource在 Spring 环境抽象中的角色

MapPropertySource的完整类路径为org.springframework.core.env.MapPropertySource,它继承自EnumerablePropertySource<Map<String, Object>>,而EnumerablePropertySource又继承自抽象基类PropertySource<T>。三者构成了 Springcore.env包中"属性源"最核心的一条继承链:

  • PropertySource<T>:抽象基类,持有name(属性源名称)与source(底层数据对象)两个 final 字段,定义抽象方法getProperty(String name)
  • EnumerablePropertySource<T>:在基类之上补充了可枚举性——新增抽象方法getPropertyNames(),要求实现类能够返回全部属性名(详见仓库 Spring-EnumerablePropertySource.md);
  • MapPropertySource:将底层数据对象固定为Map<String, Object>,所有属性操作都翻译为对 Map 的读写。

用一句话概括:MapPropertySource就是"给一个 Map 起个名字,然后把它包装成 Spring 能识别的属性源"。正是这一层轻量包装,让系统属性、环境变量、Properties文件内容、命令行参数等一切"键值对形态"的配置,都能统一进入 Spring 的配置体系。

二、源码逐方法解析:三个方法即全部

MapPropertySource的实现极其精简,只有构造函数加三个方法。完整源码如下(与仓库 Spring-MapPropertySource.md 一致):

public class MapPropertySource extends EnumerablePropertySource<Map<String, Object>> { public MapPropertySource(String name, Map<String, Object> source) { super(name, source); } @Override @Nullable public Object getProperty(String name) { // 从 map 中获取 name 对应的 value return this.source.get(name); } @Override public boolean containsProperty(String name) { // 判断是否存在 name 属性 return this.source.containsKey(name); } @Override public String[] getPropertyNames() { // 获取 map 的所有 key return StringUtils.toStringArray(this.source.keySet()); } }

2.1 构造函数:绑定名字与数据源

public MapPropertySource(String name, Map<String, Object> source) { super(name, source); }

构造函数仅做透传。在PropertySource基类中(见仓库 Spring-PropertySources.md),namesource都会被存储为 final 字段,并带有两项校验:

Assert.hasText(name, "Property source name must contain at least one character"); Assert.notNull(source, "Property source must not be null");

属性源名称不能为空字符串,底层 Map 不能为 nullname的作用非常关键:它是该属性源在MutablePropertySources列表中的唯一标识,后续通过PropertySource.named(name)进行查找、替换、排序都依赖它。

2.2 getProperty:Map 取值

@Override @Nullable public Object getProperty(String name) { return this.source.get(name); }

直接委托给Map#get,语义与 Map 完全一致:

  • 命中 key 时返回对应 value;
  • 未命中时返回null(方法标注@Nullable)。

需要注意的是,value 类型是Object而非String。虽然大部分配置值是字符串,但 Map 型属性源并不做类型限制,真正的类型转换发生在更上层的PropertyResolver/ConversionService阶段。此外,由于底层可能是HashMapget的时间复杂度为 O(1),这也使 MapPropertySource 成为所有属性源中查询效率最高的一类。

2.3 containsProperty:判断属性是否存在

@Override public boolean containsProperty(String name) { return this.source.containsKey(name); }

与基类PropertySource#containsProperty的默认实现(通过getProperty(name) != null判断)不同,MapPropertySourcecontainsKey直接判断,更精确:即使某个 key 对应的 value 为nullcontainsKey依然返回true,而基类的默认实现会返回false。这是 Map 型属性源在语义上的一个重要细节。

2.4 getPropertyNames:枚举全部属性名

@Override public String[] getPropertyNames() { return StringUtils.toStringArray(this.source.keySet()); }

Map的 key 集合转换为String[]返回,这是EnumerablePropertySource对"可枚举"这一能力的落地实现。它使得上层可以对属性源做全量遍历——例如AbstractEnvironment在打印配置信息、PropertySourcesPropertyResolver在生成占位符无法解析的报错信息时都会用到它。

2.5 父类 EnumerablePropertySource 的配合

MapPropertySource之所以能"只写三个方法",是因为父类EnumerablePropertySourcegetPropertyNames()反哺了containsProperty的通用实现(见仓库 Spring-EnumerablePropertySource.md):

@Override public boolean containsProperty(String name) { return ObjectUtils.containsElement(getPropertyNames(), name); } public abstract String[] getPropertyNames();

也就是说:凡是可枚举的属性源,即使不重写containsProperty,也能通过遍历属性名完成存在性判断MapPropertySource出于性能考虑选择直接用containsKey覆盖了该实现,而CommandLinePropertySourceServletContextPropertySource等兄弟类则各自实现了自己的语义(详见下文)。

三、家族图谱:从 MapPropertySource 出发看 PropertySource 体系

围绕MapPropertySource,Spring 在其上派生了一条完整的实现链,仓库中对应文档均已收录:

PropertySource<T> (抽象基类:name + source) └── EnumerablePropertySource<T> (抽象:getPropertyNames()) ├── MapPropertySource (本文主角:Map<String,Object> 包装) │ └── PropertiesPropertySource (Properties 包装) │ └── ResourcePropertySource (资源文件加载) ├── CommandLinePropertySource<T> (命令行参数) ├── ServletContextPropertySource (Web 应用初始化参数) └── SystemEnvironmentPropertySource (环境变量,Spring 5.1+ 内部实现)

3.1 PropertiesPropertySource:MapPropertySource 的第一个子类

仓库 Spring-PropertiesPropertySource.md 记录了这一层关系:java.util.Properties本身继承自Hashtable<Object,Object>,本质就是一个 Map 结构,因此PropertiesPropertySource直接继承MapPropertySource复用其全部逻辑:

public class PropertiesPropertySource extends MapPropertySource { @SuppressWarnings({"rawtypes", "unchecked"}) public PropertiesPropertySource(String name, Properties source) { super(name, (Map) source); } @Override public String[] getPropertyNames() { synchronized (this.source) { return super.getPropertyNames(); } } }

值得注意的差异点:PropertiesPropertySource重写了getPropertyNames(),用synchronized (this.source)包裹——因为Hashtable是线程安全容器,遍历其 keySet 时加锁可以避免并发修改导致ConcurrentModificationExceptiongetPropertycontainsProperty则原样继承MapPropertySourceHashtable#get/containsKey本身线程安全)。

3.2 ResourcePropertySource:加载配置文件到 Map

ResourcePropertySource继承PropertiesPropertySource,将"资源文件"转化为 Map 属性源(见仓库 Spring-ResourcePropertySource.md):

  • 通过PropertiesLoaderUtils.loadProperties(resource).properties文件读成Properties,最终落到底层的 Map 结构;
  • 支持classpath:前缀与ClassLoader参数,例如new ResourcePropertySource("myConfig", "classpath:app.properties", classLoader)
  • getNameForResource(Resource)resource.getDescription()作为属性源名称,描述为空时退化为短类名@identityHashCode
  • withName(String)可在不改动底层数据的前提下重新命名属性源,便于在MutablePropertySources中调整优先级。

application.properties/application.yml加载进 Spring 环境后,本质上就是ResourcePropertySource或其变体在发挥作用。

3.3 兄弟实现:命令行与 Servlet 上下文

  • CommandLinePropertySource(仓库 Spring-CommandLinePropertySource.md):同样继承EnumerablePropertySource,但底层是命令行参数解析器而非 Map。其getProperty支持多值参数——例如命令行传入--foo=bar --foo=baz,以foo查询会得到逗号分隔的bar,baz;同时通过nonOptionArgs特例处理无选项参数。这说明了EnumerablePropertySource的"可枚举"接口能适配完全不同的底层数据结构。
  • ServletContextPropertySource(仓库 Spring-ServletContextPropertySource.md):底层是ServletContext接口,getProperty委托给getInitParametergetPropertyNames委托给getInitParameterNames。Web 环境下的web.xml初始化参数正是经它进入 Spring 环境的。

四、实战印证:Spring Boot 启动链路中的 Map 型属性源

回到本仓库,Spring Boot 的启动加载文档 SpringBoot-application-load.md 中出现了MapPropertySource家族在生产环境中的真实用法:

propertySources.add(new OriginTrackedMapPropertySource(name + documentNumber, ...

OriginTrackedMapPropertySource是 Spring Boot 对MapPropertySource的扩展子类(位于org.springframework.boot.env包),它在继承 Map 型属性源全部能力的同时,额外记录了每个属性的来源出处(如配置文件的具体路径与行列),用于启动失败时的诊断信息定位。从源码调用链可以推断,Spring Boot 解析application.properties时,会为每个配置文档(document)创建一个命名的 Map 型属性源,通过addLast/addFirst按优先级顺序加入MutablePropertySources,从而支撑@Value("${...}")@ConfigurationProperties等注解的取值。

这也是 MapPropertySource 在整个框架中价值的缩影:它不直接参与业务,却是所有"键值对配置"进入 Spring 统一环境抽象的唯一通道。无论是系统属性(System.getProperties())、环境变量,还是*.properties文件、命令行参数,最终都会以 Map 形态被包装成某个MapPropertySource及其子类实例,挂载到Environment上。

五、小结

方法底层实现关键语义
getProperty(name)source.get(name)未命中返回null,O(1) 查询
containsProperty(name)source.containsKey(name)比基类默认实现更精确,value 为 null 也算存在
getPropertyNames()StringUtils.toStringArray(source.keySet())实现EnumerablePropertySource的可枚举契约
构造super(name, source)名称非空、Map 非空双重校验

MapPropertySource用最朴素的方式诠释了 Spring 的设计哲学:通过统一的PropertySource抽象 + 可枚举扩展点,将千差万别的配置来源收敛为同一种"名称 → 属性源 → 属性"的访问模型。理解它,是读懂 Spring 环境抽象(EnvironmentPropertyResolver、占位符解析)的第一步,也是排查"配置为何没生效""属性为何解析不到"这类问题的基础功。想继续深入,可以沿着本仓库 Spring-PropertySources.md 继续阅读MutablePropertySources的优先级排序机制,以及 PropertySource 家族其余文档 中的各实现类。

【免费下载链接】source-code-hunter😱 从源码层面,剖析挖掘互联网行业主流技术的底层实现原理,为广大开发者 “提升技术深度” 提供便利。目前开放 Spring 全家桶,Mybatis、Netty、Dubbo 框架,及 Redis、Tomcat 中间件等项目地址: https://gitcode.com/GitHub_Trending/so/source-code-hunter

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/13 3:38:56

MyBatis缓存机制详解:一级与二级缓存原理与实践

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/13 3:36:13

SpringBoot+Vue构建高校在线教育平台实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/13 3:32:50

Pixelle-Video 使用 uv 安装依赖失败时如何处理?

Pixelle-Video 使用 uv 安装依赖失败时如何处理&#xff1f; 【免费下载链接】Pixelle-Video &#x1f680; AI 全自动短视频引擎 | AI Fully Automated Short Video Engine 项目地址: https://gitcode.com/GitHub_Trending/pi/Pixelle-Video 在 Pixelle-Video 从源码安…

作者头像 李华
网站建设 2026/9/13 3:25:19

电子元器件视觉质检系统:YOLOv11与VLM工业落地实践

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华