概述
我们在使用spring的时候可以通过占位符
获取系统变量,如:@Value(“${test.produce}”)
test.name=张三
test.age=28
test.produce=${test.name}:${test.age}
实例
配置文件
application.properties
my.home=地球
my.location=${my.home}
my.address=${my.location:我来自哪里}
代码
@EnableScheduling
@SpringBootApplication
public class Application {
public static void main(String[] args) {
ApplicationContext context = SpringApplication.run(Application.class, args);
PropertySourcesPlaceholdersResolver resolver = new PropertySourcesPlaceholdersResolver(context.getEnvironment());
val key = "${my.address}";
System.out.println(resolver.resolvePlaceholders(key));
}
}
结果输出
地球
如果环境变量my.location
不存在将输出:我来自哪里
原理分析
PropertySourcesPlaceholdersResolver构造函数,指定占位符的前缀
、后缀
、默认值分隔符
、未解析忽略
、环境变量容器
public PropertySourcesPlaceholdersResolver(Environment environment) {
this(getSources(environment), null);
}
public PropertySourcesPlaceholdersResolver(Iterable<PropertySource<?>> sources) {
this(sources, null);
}
/**
* PropertyPlaceholderHelper解析辅助类
* placeholderPrefix 占位符前缀
* placeholderSuffix 占位符后缀
* simplePrefix 前缀简写
* valueSeparator 默认值分隔符
* ignoreUnresolvablePlaceholders 是否忽略未解析成功
*/
public PropertySourcesPlaceholdersResolver(Iterable<PropertySource<?>> sources, PropertyPlaceholderHelper helper) {
this.sources = sources;
this.helper = (helper != null) ? helper : new PropertyPlaceholderHelper(SystemPropertyUtils.PLACEHOLDER_PREFIX,
SystemPropertyUtils.PLACEHOLDER_SUFFIX, SystemPropertyUtils.VALUE_SEPARATOR, true);
}
PropertyPlaceholderHelper#parseStringValue占位符的解析主要在这个方法里完成
protected String parseStringValue(
String value, PlaceholderResolver placeholderResolver, @Nullable Set<String> visitedPlaceholders) {
//获取占位符的前缀索引,如果索引为-1,则说明不存在占位符直接返回
int startIndex = value.indexOf(this.placeholderPrefix);
if (startIndex == -1) {
return value;
}
StringBuilder result = new StringBuilder(value);
while (startIndex != -1) {
//查找占位符的后缀索引,findPlaceholderEndIndex方法下面会有单独解析
int endIndex = findPlaceholderEndIndex(result, startIndex);
//如果解析到后缀索引,则继续处理
if (endIndex != -1) {
//获取占位符内需要解析的字段,如:${my.name}返回的结果为my.name
String placeholder = result.substring(startIndex +this.placeholderPrefix.length(), endIndex);
String originalPlaceholder = placeholder;
if (visitedPlaceholders == null) {
visitedPlaceholders = new HashSet<>(4);
}
//判断占位符是否存在循环引用的情况,如:
// your.name=${my.name}
// my.name=${your.name}
//如果则抛出异常
if (!visitedPlaceholders.add(originalPlaceholder)) {
throw new IllegalArgumentException(
"Circular placeholder reference '" + originalPlaceholder + "' in property definitions");
}
//递归解析占位符中包含占位符,如:
// enviroment.online=test1
// enviroment.offline=test2
// current.enviroment=online
// project.enviroment=${current.enviroment.${current.enviroment}}
placeholder = parseStringValue(placeholder, placeholderResolver, visitedPlaceholders);
//获取占位符对应的值,resolvePlaceholder方法下面会做具体解析
String propVal = placeholderResolver.resolvePlaceholder(placeholder);
//如果没有解析到占位符的值,则看是否有默认值配置,如:
// my.name=${your.name:June}
if (propVal == null && this.valueSeparator != null) {
//获取默认分隔符(如:“:”)索引
int separatorIndex = placeholder.indexOf(this.valueSeparator);
if (separatorIndex != -1) {
String actualPlaceholder = placeholder.substring(0, separatorIndex); //获取默认值
String defaultValue = placeholder.substring(separatorIndex + this.valueSeparator.length());
//解析占位符实际值
propVal = placeholderResolver.resolvePlaceholder(actualPlaceholder);
if (propVal == null) {
//如果占位符存在值则优先使用解析到的值
propVal = defaultValue;
}
}
}
if (propVal != null) {
//递归调用解析之前解析到的值中存在的占位符
propVal = parseStringValue(propVal, placeholderResolver, visitedPlaceholders);
//将字符串占位符替换为实际解析到的值
result.replace(startIndex, endIndex + this.placeholderSuffix.length(), propVal);
if (logger.isTraceEnabled()) {
logger.trace("Resolved placeholder '" + placeholder + "'");
}
//继续解析还未解析的占位符(一段字符串中可能包含多个占位符)
startIndex = result.indexOf(this.placeholderPrefix, startIndex + propVal.length());
}
//如果占位符未解析到内容,且可忽略未解析到内容
else if (this.ignoreUnresolvablePlaceholders) {
//继续解析未解析的占位符(一段字符串中可能包含多个占位符)
startIndex = result.indexOf(this.placeholderPrefix, endIndex + this.placeholderSuffix.length());
}
else {
//不可忽略未解析到内容则抛出异常
throw new IllegalArgumentException("Could not resolve placeholder '" +
placeholder + "'" + " in value \"" + value + "\"");
}
//解析完一个占位符则移除待解析队列
visitedPlaceholders.remove(originalPlaceholder);
}
else {
startIndex = -1;
}
}
return result.toString();
}
PropertyPlaceholderHelper#findPlaceholderEndIndex寻找带占位符结束索引
private int findPlaceholderEndIndex(CharSequence buf, int startIndex) {
int index = startIndex + this.placeholderPrefix.length();
int withinNestedPlaceholder = 0;
while (index < buf.length()) {
//判断字符串buf索引index是否为placeholderSuffix结束占位符
if (StringUtils.substringMatch(buf, index, this.placeholderSuffix)) {
//如果存在还未解析的且套占位符如:
//project.enviroment=${current.enviroment.${current.enviroment}}
//则需继续解析
if (withinNestedPlaceholder > 0) {
withinNestedPlaceholder--;
//已解析的索引继续移动placeholderSuffix.length()位置,继续解析
index = index + this.placeholderSuffix.length();
}
else {
return index;
}
}
//如果字符串buf的索引index的值为开始占位符的简写,说明存在嵌套占位符的情况
else if (StringUtils.substringMatch(buf, index, this.simplePrefix)) {
withinNestedPlaceholder++;
//索引移动simplePrefix.length()个字符继续解析
index = index + this.simplePrefix.length();
}
else {
index++;
}
}
return -1;
}
PropertySourcesPlaceholdersResolver#resolvePlaceholder从上下文环境变量容器中查找对应的变量值,后续会写spring上下文中有哪些PropertySource及加载实现方式
//这个soueces是各种环境变量的集合如:
// application.properties配置文件
// application-xx.yml配置文件
// systemProperties 系统环境变量
// systemEviroment 操作系统环境变量等
protected String resolvePlaceholder(String placeholder) {
if (this.sources != null) {
for (PropertySource<?> source : this.sources) {
Object value = source.getProperty(placeholder);
if (value != null) {
return String.valueOf(value);
}
}
}
return null;
}