【Spring】PropertySource 的解读与示例(MapPropertySource CommandLinePropertySource)
本文已参与「新人创作礼」活动,一起开启掘金创作之路。
前言
之前写过一篇解读 PropertySource 源码的文章,本文做一个补充,重点解读下 CommandLinePropertySource,并添加部分示例
[【源码】Spring —— PropertySource 解读](【源码】Spring —— PropertySource 解读 - 掘金 (juejin.cn))
public abstract class PropertySource<T> {
protected final String name;
protected final T source;
// ...
}
顶层抽象类,以 name 维度管理资源 source,并提供如下方法
public boolean containsProperty(String name) {
return (getProperty(name) != null);
}
@Nullable
public abstract Object getProperty(String name);
EnumerablePropertySource
public abstract class EnumerablePropertySource<T> extends PropertySource<T> {
public EnumerablePropertySource(String name, T source) {
super(name, source);
}
protected EnumerablePropertySource(String name) {
super(name);
}
@Override
public boolean containsProperty(String name) {
return ObjectUtils.containsElement(getPropertyNames(), name);
}
public abstract String[] getPropertyNames();
}
在 PropertySource 的基础上拓展了 枚举 能力,提供 getPropertyNames 获取所有属性名称,因而 containsProperty 方法也基于此实现
EnumerablePropertySource 是个核心分支,其下:
DynamicValuesPropertySource,test模块下的非公开类,用途未知,维护的source是一个Map<String, Supplier<Object>> valueSuppliers,其中的Supplier用来生成属性k对应的值ServletConfigPropertySource和ServletContextPropertySource都是web模块下维护对应ServletConfig和ServletContext类型的source,用于web environmentMapPropertySource,最常用的分支,source类型为Map,下文重点解读CommandLinePropertySource,将命令行参数维护成对应的source,我们在启动SpringBoot项目时用到的命令行参数就被封装成CommandLinePropertySource,下文重点解读CompositePropertySource,组合模式,可以组合上述的EnumerablePropertySource
MapPropertySource
其维护的 source 类型为 Map<String, Object>,因而对应的方法基于 Map 实现,比如:
@Override
@Nullable
public Object getProperty(String name) {
return this.source.get(name);
}
@Override
public boolean containsProperty(String name) {
return this.source.containsKey(name);
}
@Override
public String[] getPropertyNames() {
return StringUtils.toStringArray(this.source.keySet());
}
展示一段示例 demo:
@Test
public void map() {
Map<String, Object> map = new HashMap<>() {
{
put("a", "1");
put("b", "2");
}
};
MapPropertySource mapPropertySource
= new MapPropertySource("source", map);
Assertions.assertEquals("1", mapPropertySource.getProperty("a"));
}
SystemEnvironmentPropertySource
SystemEnvironmentPropertySource 继承了 MapPropertySource,主要用来维护 系统环境参数,在 MapPropertySource 的基础上,SystemEnvironmentPropertySource 忽略属性 key 的大小写,同时会在不存在对应属性是,先后试图处理 . - 等特殊符号后再次获取
展示一段 SystemEnvironmentPropertySource 的示例 demo:
@Test
public void system() {
Map<String, Object> map = new HashMap<>() {
{
put("a_b", "1");
put("C_D", "2");
}
};
SystemEnvironmentPropertySource propertySource
= new SystemEnvironmentPropertySource("system", map);
Assertions.assertEquals("1", propertySource.getProperty("a.b"));
Assertions.assertEquals("2", propertySource.getProperty("c-d"));
}
PropertiesPropertySource
PropertiesPropertySource 继承 MapPropertySource,接受 Properties 类型的 source,毕竟 Properties 就是 Map 的子类
CommandLinePropertySource
我们在启动 SpringBoot 项目时,是可以指定 命令行参数 的,诸如:
java -jar xxx.jar --server.port=8080
通过 命令行参数 我们可以干预 SpringApplication 及其 Environment 的属性,实际上命令行参数被解析成了 CommandLinePropertySource(本文重点关注 SimpleCommandLinePropertySource),同时它拥有极高的优先权
命令行参数 分为两类:
option arguments:指定方式通常如下--k=vnon-option arguments:不以诸如--之类前缀修饰的属性,被解析为non-option arguments,诸如a b c会被解析为a,b,c,其key为nonOptionArgs,当然我们也可以通过setNonOptionArgsPropertyName方法修改该key值
Spring 针对 CommandLinePropertySource 提供了两个实现(当然我们也可以自己实现),分别为 SimpleCommandLinePropertySource 和 JOptCommandLinePropertySource,基本我们用到的也就是 SimpleCommandLinePropertySource 了
SimpleCommandLinePropertySource
在该类中,命令行参数 被解析为 CommandLineArgs,负责解析工作的类是 SimpleCommandLineArgsParser,见其构造方法:
public class SimpleCommandLinePropertySource extends CommandLinePropertySource<CommandLineArgs> {
public SimpleCommandLinePropertySource(String... args) {
super(new SimpleCommandLineArgsParser().parse(args));
}
// ...
}
对应的属性操作也直接委托给了 CommandLineArgs,CommandLineArgs 和 SimpleCommandLineArgsParser 的代码较为简单,不再深入
给出一段示例 demo:
@Test
public void command() {
String[] command = { "--o1=v1", "--o2", "a", "b", "c" };
SimpleCommandLinePropertySource propertySource
= new SimpleCommandLinePropertySource("command", command);
Assertions.assertEquals("v1", propertySource.getProperty("o1"));
// 可以不指定值,结果为 ""
Assertions.assertEquals("", propertySource.getProperty("o2"));
// 不指定 key,则值为 null
Assertions.assertNull(propertySource.getProperty("o3"));
// nonOptionArgs 会用 , 连接返回
Assertions.assertEquals("a,b,c", propertySource.getProperty("nonOptionArgs"));
}
PropertySources
public interface PropertySources extends Iterable<PropertySource<?>> {
default Stream<PropertySource<?>> stream() {
return StreamSupport.stream(spliterator(), false);
}
boolean contains(String name);
@Nullable
PropertySource<?> get(String name);
}
维护一组 PropertySource 集合,定义了 读 方法,唯一子类 MutablePropertySources 额外提供了 写 方法
MutablePropertySources
MutablePropertySources 基于 CopyOnWriteArrayList 维护一组 PropertySource
private final List<PropertySource<?>> propertySourceList = new CopyOnWriteArrayList<>();
它提供了 addFirst addLast addBefore addAfter 等方法,允许细粒度的控制 PropertySource 的优先级,Environment 就是借助它来维护对应的 属性集,并遵循严格的优先级
给出一段示例 demo
@Test
public void mutable() {
MutablePropertySources propertySources = new MutablePropertySources();
Map<String, Object> map = new HashMap<>() {
{
put("a", "1");
put("b", "2");
}
};
MapPropertySource mapPropertySource
= new MapPropertySource("map", map);
String command = "--a=3";
SimpleCommandLinePropertySource commandLinePropertySource
= new SimpleCommandLinePropertySource("command", command);
StandardEnvironment environment = new StandardEnvironment();
environment
.getPropertySources()
.addFirst(mapPropertySource);
Assertions.assertEquals("1", environment.getProperty("a"));
// 插到 mapPropertySource 前面
environment
.getPropertySources()
.addBefore(
"map"
, commandLinePropertySource
);
Assertions.assertEquals("3", environment.getProperty("a"));
}
这里我们借助 StandardEnvironment 来维护、获取属性,其中 environment.getPropertySources() 返回的就是一个 MutablePropertySources ,可以看到 addBefore 方法让后者覆盖了前者
总结
PropertySource 相关解读到此为止,Spring 广泛使用的类,希望自己在平时的开发中也可以使用到~