专业的JAVA编程教程与资源

网站首页 > java教程 正文

Spring源码分析 - 占位符解析器(springboot 占位符)

temp10 2025-04-09 20:31:23 java教程 5 ℃ 0 评论

1.PropertyPlaceholderHelper的作用

以下使用代码查看PropertyPlaceholderHelper类的作用

具体代码如下:

Spring源码分析 - 占位符解析器(springboot 占位符)

public class PropertyPlaceholderHelperSimpleApplication {
    public static void main(String[] args) throws IOException {
        //输入字符串
        String a = "${name}";
        String b = "${name${age}}";

        //加载配置文件
        InputStream is = PropertyPlaceholderHelperSimpleApplication.class.getClassLoader().getResourceAsStream("properties/simple.properties");
        Properties properties = new Properties();
        properties.load(is);
        //初始化PropertyPlaceholderHelper
        PropertyPlaceholderHelper helper = new PropertyPlaceholderHelper("${","}",":",false);

        //helper的replacePlaceholders方法处理字符串
        String result = helper.replacePlaceholders(a,properties);
        System.out.println(result);

        result = helper.replacePlaceholders(b,properties);
        System.out.println(result);
    }
}

其中配置文件simple.properties

name = poiuy
age = 31
name31 = jll

最后输出如下图,从输出中我们可以看到,PropertyPlaceholderHelper是将输入字符串中的文件进行替换处理,替换的逻辑是判断配置文件中是否有placeholderPrefix和placeholderSuffix包含的字符

2.PropertyPlaceholderHelper的使用

AbstractPropertyResolver的
resolveRequiredPlaceholders方法中,会用到PropertyPlaceholderHelper类来处理配置文件的路径

前三个参数分别和示例代码中一样

3.PropertyPlaceholderHelper源码解析

PropertyPlaceholderHelper的replacePlaceholders方法中,首先会创建一个
PropertyPlaceholderHelper.PlaceholderResolver的匿名内部类,然后调用replacePlaceholders方法

public class PropertyPlaceholderHelper {

    public String replacePlaceholders(String value, Properties properties) {
        Assert.notNull(properties, "'properties' must not be null");
        properties.getClass();
        return this.replacePlaceholders(value, properties::getProperty);
    }

    public String replacePlaceholders(String value, PropertyPlaceholderHelper.PlaceholderResolver placeholderResolver) {
        Assert.notNull(value, "'value' must not be null");
        return this.parseStringValue(value, placeholderResolver, (Set)null);
    }

	...
}

PlaceholderResolver是一个函数式接口,此处使用properties的getProperty方法来实现resolvePlaceholder的方法实现,

即调用resolvePlaceholder方法时实际上是获取Properties的属性

继续查看replacePlaceholders方法

public class PropertyPlaceholderHelper {
   protected String parseStringValue(String value, PropertyPlaceholderHelper.PlaceholderResolver placeholderResolver, @Nullable Set visitedPlaceholders) {
		//获取字符串中placeholderPrefix的下标
        int startIndex = value.indexOf(this.placeholderPrefix);
		//没有该前缀返回原字符串
        if (startIndex == -1) {
            return value;
        } else {
			//通过原字符串创建result对象
            StringBuilder result = new StringBuilder(value);
			//循环处理,知道找不到placeholderPrefix字符串
            while(startIndex != -1) {
				//从placeholderPrefix处开始查找placeholderSuffix的下标
                int endIndex = this.findPlaceholderEndIndex(result, startIndex);
                if (endIndex != -1) {
					//截取字符串中placeholderPrefix到placeholderSuffix的字符
                    String placeholder = result.substring(startIndex + this.placeholderPrefix.length(), endIndex);
                    String originalPlaceholder = placeholder;
                    if (visitedPlaceholders == null) {
                        visitedPlaceholders = new HashSet(4);
                    }
                    if (!((Set)visitedPlaceholders).add(placeholder)) {
                        throw new IllegalArgumentException("Circular placeholder reference '" + placeholder + "' in property definitions");
                    }
					//对截取的q字符串递归处理,解决字符串中还有需要替代的字符
                    placeholder = this.parseStringValue(placeholder, placeholderResolver, (Set)visitedPlaceholders);
					//从Properties中获取截取的字符串对应的value值,这里可以自定义实现类来处理
                    String propVal = placeholderResolver.resolvePlaceholder(placeholder);
					//从Properties中获取的值为空且不是valueSeparator不是空
                    if (propVal == null && this.valueSeparator != null) {
						//如果原字符串中有valueSeparator字符
                        int separatorIndex = placeholder.indexOf(this.valueSeparator);
                        if (separatorIndex != -1) {
							//则截取valueSeparator之前的字符串
                            String actualPlaceholder = placeholder.substring(0, separatorIndex);
							//valueSeparator后面的字符为默认值
                            String defaultValue = placeholder.substring(separatorIndex + this.valueSeparator.length());
							//从Proprties中获取actualPlaceholder对应的值,如果没有则返回默认值
                            propVal = placeholderResolver.resolvePlaceholder(actualPlaceholder);
                            if (propVal == null) {
                                propVal = defaultValue;
                            }
                        }
                    }
					//如果Properties中获取的值不为空
                    if (propVal != null) {
						//对Properties中的value值递归处理
                        propVal = this.parseStringValue(propVal, placeholderResolver, (Set)visitedPlaceholders);
						//原字符串中替换开始下标到结束下标的字符为获取到的value字符
                        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) {
                            throw new IllegalArgumentException("Could not resolve placeholder '" + placeholder + "' in value \"" + value + "\"");
                        }
						//重新定义开始下标对后面的字符进行处理
                        startIndex = result.indexOf(this.placeholderPrefix, endIndex + this.placeholderSuffix.length());
                    }
                    ((Set)visitedPlaceholders).remove(originalPlaceholder);
                } else {
                    startIndex = -1;
                }
            }
			//返回替换后的字符串
            return result.toString();
        }
    }
	...
}

4.Properties文件

了解了配置文件路径的解析过程,继续看下Spring中的Properties文件

AbstractPropertyResolver中Properties是通过getPropertyAsRawString方法获取,该方法在AbstractPropertyResolver中是抽象的,需要从子类中继续查看

在AbstractEnvironment的
resolveRequiredPlaceholders方法中会通过propertyResolver属性来调用
resolveRequiredPlaceholders方法

AbstractEnvironment的propertyResolver属性是
PropertySourcesPropertyResolver的实现类

public abstract class AbstractEnvironment implements ConfigurableEnvironment {
    private final ConfigurablePropertyResolver propertyResolver;

    public AbstractEnvironment() {
        this.propertyResolver = new PropertySourcesPropertyResolver(this.propertySources);
        this.customizePropertySources(this.propertySources);
    }

    public String resolveRequiredPlaceholders(String text) throws IllegalArgumentException {
        return this.propertyResolver.resolveRequiredPlaceholders(text);
    }

	...
}


PropertySourcesPropertyResolver是AbstractPropertyResolver的子类,在类中实现了getPropertyAsRawString方法

getPropertyAsRawString中调用getProperty方法,方法从PropertySources里面获取的

从上面可以看到,AbstractEnvironment创建
PropertySourcesPropertyResolver对象时,会传入PropertySources的引用,在AbstractEnvironment中,这个值通过customizePropertySources方法初始化

public class PropertySourcesPropertyResolver extends AbstractPropertyResolver {
    @Nullable
    private final PropertySources propertySources;

    @Nullable
    protected String getPropertyAsRawString(String key) {
        return (String)this.getProperty(key, String.class, false);
    }

    @Nullable
    protected  T getProperty(String key, Class targetValueType, boolean resolveNestedPlaceholders) {
        if (this.propertySources != null) {
            Iterator var4 = this.propertySources.iterator();
			//遍历propertySources
            while(var4.hasNext()) {
                PropertySource propertySource = (PropertySource)var4.next();
                if (this.logger.isTraceEnabled()) {
                    this.logger.trace("Searching for key '" + key + "' in PropertySource '" + propertySource.getName() + "'");
                }
				
                Object value = propertySource.getProperty(key);
                if (value != null) {
                    if (resolveNestedPlaceholders && value instanceof String) {
                        value = this.resolveNestedPlaceholders((String)value);
                    }

                    this.logKeyFound(key, propertySource, value);
                    return this.convertValueIfNecessary(value, targetValueType);
                }
            }
        }

        if (this.logger.isTraceEnabled()) {
            this.logger.trace("Could not find key '" + key + "' in any property source");
        }
        return null;
    }

	...
}

customizePropertySources方法在AbstractEnvironment的子类StandardEnvironment中实现

在方法中getSystemProperties和getSystemEnvironment方法分别添加系统属性和环境变量

public class StandardEnvironment extends AbstractEnvironment {
    public static final String SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME = "systemEnvironment";
    public static final String SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME = "systemProperties";

    public StandardEnvironment() {
    }

    protected void customizePropertySources(MutablePropertySources propertySources) {
		//System.getProperties();
        propertySources.addLast(new PropertiesPropertySource("systemProperties", this.getSystemProperties()));
		//System.getenv()
        propertySources.addLast(new SystemEnvironmentPropertySource("systemEnvironment", this.getSystemEnvironment()));	
    }
}

这样可以在Spring配置文件中使用系统属性及环境变量,例如将配置文件放入到JAVA_HOME目录下,然后在Spring中使用Properties名,并用${}符号修饰,Spring在解析时首先会从systemProperties和systemEnvironment中查找该变量,最后在systemEnvironment中找到,并进行替换,最终的文件路径是环境变量与配置路径拼接得到

Tags:

本文暂时没有评论,来添加一个吧(●'◡'●)

欢迎 发表评论:

最近发表
标签列表