java xml 占位符_spring是如何解析xml配置文件中的占位符

前言

我们在配置Spring Xml配置文件的时候,可以在文件路径字符串中加入 ${} 占位符,Spring会自动帮我们解析占位符,这么神奇的操作Spring是怎么帮我们完成的呢?这篇文章我们就来一步步揭秘。

1.示例

ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext();

applicationContext.setConfigLocation("${java.version}.xml");

applicationContext.refresh();

String[] beanNames = applicationContext.getBeanDefinitionNames();

for (String beanName : beanNames) {

System.out.println(beanName);

}

这段代码在我工程里是会报错的,如下:

Caused by: java.io.FileNotFoundException: class path resource [1.8.0_144.xml] cannot be opened because it does not exist

at org.springframework.core.io.ClassPathResource.getInputStream(ClassPathResource.java:190)

at org.springframework.beans.factory.xml.XmlBeanDefinitionReader.loadBeanDefinitions(XmlBeanDefinitionReader.java:336)

... 11 more

可以看到报错里面的文件路径变成了1.8.0_144.xml,也就是说Spring帮我们把${java.version}解析成了实际值。

2.原理

AbstractRefreshableConfigApplicationContext

我们在之前的文章里提到过这个类的resolve方法,我们再来瞧一眼:

/**

* Resolve the given path, replacing placeholders with corresponding

* environment property values if necessary. Applied to config locations.

* @param path the original file path

* @return the resolved file path

* @see org.springframework.core.env.Environment#resolveRequiredPlaceholders(String)

*/

protected String resolvePath(String path) {

//通过当前环境去 解析 必要的占位符

return getEnvironment().resolveRequiredPlaceholders(path);

}

获取当前环境,这个环境在示例代码中就是 StandardEnvironment ,并且根据当前环境去解析占位符,这个占位符解析不到还会报错。

resolveRequiredPlaceHolders由StandardEnvironment的父类AbstractEnvironment实现。

AbstractEnvironment

//把propertySources放入 Resolver中

private final ConfigurablePropertyResolver propertyResolver =

new PropertySourcesPropertyResolver(this.propertySources);

@Override

public String resolveRequiredPlaceholders(String text) throws IllegalArgumentException {

return this.propertyResolver.resolveRequiredPlaceholders(text);

}

这里的propertySources很重要了,从命名也可以看出我们解析占位符的来源就是从这个集合中来的。这个集合是在我们StandardEnvironment实例化的时候去自定义的。

StandardEnvironment

/**

* Create a new {@code Environment} instance, calling back to

* {@link #customizePropertySources(MutablePropertySources)} during construction to

* allow subclasses to contribute or manipulate(操作) {@link PropertySource} instances as

* appropriate.

* @see #customizePropertySources(MutablePropertySources)

*/

//StandardEnvironment 实例化调用

public AbstractEnvironment() {

customizePropertySources(this.propertySources);

}

@Override

protected void customizePropertySources(MutablePropertySources propertySources) {

//todo Java提供了System类的静态方法getenv()和getProperty()用于返回系统相关的变量与属性,

//todo getenv方法返回的变量大多于系统相关,

//todo getProperty方法返回的变量大多与java程序有关。

//https://www.cnblogs.com/Baronboy/p/6030443.html

propertySources.addLast(new MapPropertySource(SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME, getSystemProperties()));

//SystemEnvironmentPropertySource 是System.getenv()

propertySources.addLast(new SystemEnvironmentPropertySource(SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME, getSystemEnvironment()));

}

最重要的肯定是我们的 propertyResolver.resolveRequiredPlaceholders 方法了,propertyResolver.resolveRequiredPlaceholders其实是PropertySourcesPropertyResolver的父类AbstractPropertyResolver来实现。

AbstractPropertyResolver

//创建一个占位符的helper去解析

@Override

public String resolveRequiredPlaceholders(String text) throws IllegalArgumentException {

if (this.strictHelper == null) {

//不忽略

this.strictHelper = createPlaceholderHelper(false);

}

return doResolvePlaceholders(text, this.strictHelper);

}

//私有方法

//是否忽略 无法解决的占位符

private PropertyPlaceholderHelper createPlaceholderHelper(boolean ignoreUnresolvablePlaceholders) {

//默认使用${ placeholderPrefix

return new PropertyPlaceholderHelper(this.placeholderPrefix, this.placeholderSuffix,

this.valueSeparator, ignoreUnresolvablePlaceholders);

}

private String doResolvePlaceholders(String text, PropertyPlaceholderHelper helper) {

//PlaceholderResolver function interface

//todo important 重要的是这个getPropertyAsRawString

return helper.replacePlaceholders(text, this::getPropertyAsRawString);

}

这里的 this::getPropertyAsRawString 很重要,利用了java8的函数式接口来实现。它的定义在AbstractPropertyResolver里

/**

* Retrieve the specified property as a raw String,

* i.e. without resolution of nested placeholders.

* @param key the property name to resolve

* @return the property value or {@code null} if none found

*/

@Nullable

protected abstract String getPropertyAsRawString(String key);

但是我们在doResolvePlaceholders里指向的this,所以还得看PropertySourcesPropertyResolver类。

PropertySourcesPropertyResolver

//提供给函数接口 PlaceholderResolver

//todo 解析 xml配置文件路径占位符的时候调用的是这个 2020-09-11

@Override

@Nullable

protected String getPropertyAsRawString(String key) {

return getProperty(key, String.class, false);

}

@Nullable

protected T getProperty(String key, Class targetValueType, boolean resolveNestedPlaceholders) {

if (this.propertySources != null) {

//例如遍历的是MutablePropertySources 的propertySourceList

for (PropertySource> propertySource : this.propertySources) {

if (logger.isTraceEnabled()) {

logger.trace("Searching for key '" + key + "' in PropertySource '" +

propertySource.getName() + "'");

}

Object value = propertySource.getProperty(key);

if (value != null) {

//todo 解析 profile变量的时候 会去 解析 变量中的占位符 2020-09-11

//TODO 解析xml配置文件路径字符串的时候 如果占位符 变量 的值 包含占位符 在这里 不会去解析 通过Helper 去解析 PropertyPlaceholderHelper

if (resolveNestedPlaceholders && value instanceof String) {

value = resolveNestedPlaceholders((String) value);

}

logKeyFound(key, propertySource, value);

//跳出for 循环

return convertValueIfNecessary(value, targetValueType);

}

}

}

if (logger.isTraceEnabled()) {

logger.trace("Could not find key '" + key + "' in any property source");

}

return null;

}

看到没有,我们是遍历this.propertySources集合,然后根据key调用它的getProperty方法获取value。我们从上面的StandardEnvrionment中看到我们定义的是 MapPropertySource 和 SystemEnvironmentPropertySource .

MapPropertySource

//从source中取得属性

@Override

@Nullable

public Object getProperty(String name) {

return this.source.get(name);

}

这里的source就是getSystemProperties(),也就是 AbstractEnvironment中的方法:

@Override

@SuppressWarnings({"unchecked", "rawtypes"})

public Map getSystemProperties() {

try {

//Hashtable

return (Map) System.getProperties();

}

catch (AccessControlException ex) {

return (Map) new ReadOnlySystemAttributesMap() {

@Override

@Nullable

protected String getSystemAttribute(String attributeName) {

try {

return System.getProperty(attributeName);

}

catch (AccessControlException ex) {

if (logger.isInfoEnabled()) {

logger.info("Caught AccessControlException when accessing system property '" +

attributeName + "'; its value will be returned [null]. Reason: " + ex.getMessage());

}

return null;

}

}

};

}

}

我们还忘了很重要的一步,就是PropertyPlaceholderHelper的replacePlaceholders方法。

PropertyPlaceholderHelper

//protected 范围

protected String parseStringValue(

String value, PlaceholderResolver placeholderResolver, Set visitedPlaceholders) {

StringBuilder result = new StringBuilder(value);

//如果value中没有占位符前缀 那直接返回result

int startIndex = value.indexOf(this.placeholderPrefix);

while (startIndex != -1) {

//找到占位符的最后一个索引

int endIndex = findPlaceholderEndIndex(result, startIndex);

if (endIndex != -1) {

String placeholder = result.substring(startIndex + this.placeholderPrefix.length(), endIndex);

String originalPlaceholder = placeholder;

if (!visitedPlaceholders.add(originalPlaceholder)) {

throw new IllegalArgumentException(

"Circular placeholder reference '" + originalPlaceholder + "' in property definitions");

}

//1. todo 2020-09-01 解析出来占位符,比如java.version

//解析内嵌占位符

// Recursive invocation, parsing placeholders contained in the placeholder key.

placeholder = parseStringValue(placeholder, placeholderResolver, visitedPlaceholders);

// Now obtain the value for the fully resolved key...

//2.todo 2020-09-01 获取实际值

String propVal = placeholderResolver.resolvePlaceholder(placeholder);

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) {

//从占位符里获取的值也有可能包含占位符 这里可能会报 Circular placeholder reference

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 {

startIndex = -1;

}

}

return result.toString();

}

到这里我们就可以看到Spring在处理一个小小的占位符就做了这么多设计。可见这个架构是如此严谨。下篇文章我们就来探讨下Spring是如何加载这个Xml文件的。

以上就是spring是如何解析xml配置文件中的占位符的详细内容,更多关于spring解析xml 占位符的资料请关注脚本之家其它相关文章!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值