【Spring】PropertySource 的解读与示例(MapPropertySource CommandLinePropertySource)
前言
之前写过一篇解读 PropertySource
源码的文章,本文做一个补充,重点解读下 CommandLinePropertySource
,并添加部分示例
【源码】Spring —— PropertySource 解读
PropertySource
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 environment
MapPropertySource
,最常用的分支,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=v
non-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
广泛使用的类,希望自己在平时的开发中也可以使用到~