Spring路径解析和占位符

一,配置文件路径解析

当我们执行Spring应用程序的时候,首先遇见的就是路径解析问题。关键代码如下

// 设置xml配置文件路径	
public void setConfigLocations(@Nullable String... locations) {
		if (locations != null) {
			Assert.noNullElements(locations, "Config locations must not be null");
			this.configLocations = new String[locations.length];
			for (int i = 0; i < locations.length; i++) {
				this.configLocations[i] = resolvePath(locations[i]).trim();
			}
		}
		else {
			this.configLocations = null;
		}
	}


//占位符号解析和替换
protected String resolvePath(String path) {
	return getEnvironment().resolveRequiredPlaceholders(path);
}

在“占位符解析和替换”代码部分中,我们需要掌握的的知识就是Enviroment,我们可以看到getEnvironment()之后又调用了resolveRequiredPlaceholders函数,这个地方就是占位符解析和替换的工作。接下来我们首先要了解的知识就是“Spring环境、属性和占位符”。

二,环境和属性

Spring 环境和属性由四个部分组成:

  • PropertySource:属性源。key-value 属性对抽象,用于配置数据。

  • PropertyResolver:属性解析器。用于解析属性配置。

  • Profile:剖面。只有被激活的Profile才会将其中所对应的Bean注册到Spring容器中

  • Environment:环境。Profile 和 PropertyResolver 的组合。

类名

描述

PropertySource

提供了可配置属性源上的搜索操作。

PropertyResolver

属性解析器,用于解析任何基础源的属性的接口

ConfigurablePropertyResolver

提供属性类型转换的功能

AbstractPropertyResolver

解析属性文件的抽象基类。设置了解析属性文件所需要ConversionService、prefix、suffix、valueSeparator等信息。

PropertySourcesPropertyResolver

PropertyResolver 的实现,对一组 PropertySources 提供属性解析服务

ConversionService

用于在运行时执行类型转换

Environment

集成在容器中的抽象,它主要包含两个方面:Profiles和Properties

ConfigurableEnvironment

设置激活的 profile 和默认的 profile 的功能以及操作 Properties 的工具

PropertySource

PropertySource是在Spring Environment之上提供了可配置属性源上的搜索操作。

public abstract class PropertySource<T> {

    //属性源的名字
    public String getName()

    //属性源(比如来自Map,那就是一个Map对象)
	public T getSource()

    //是否包含某个属性
	@Override
	public boolean containsProperty(String name) {
		throw new UnsupportedOperationException(USAGE_ERROR);
	}

    //得到属性名对应的属性
    @Override
	@Nullable
	public String getProperty(String name) {
		throw new UnsupportedOperationException(USAGE_ERROR);
	}   
}

搜索过程是按照层次结构执行的。默认情况下,系统属性优先于环境变量。因此,在调用env.getProperty("foo")时,如果在系统属性和环境变量中都设置了foo属性,则系统变量将优先于环境变量。完整层次结构如下所示,优先级最高的条目位于顶部:

  • ServletConfig参数

  • ServletContext参数

  • JNDI环境变量(如:"java:comp/env/")

  • JVM system properties("-D"命令行参数,如-Dfoo="abcd")

  • JVM system environment(操作系统环境变量)

请注意,属性值不会被合并,而是会被前面的条目覆盖。

PropertyResolver

属性解析器,用于解析任何基础源的属性的接口

public interface PropertyResolver {

	// 检测是否包含指定的key
	boolean containsProperty(String key);

	// 返回指定key对应的value,如果没有则返回null
	@Nullable
	String getProperty(String key);

	// 返回指定key对应的value,如果没有则返回defaultValue
	String getProperty(String key, String defaultValue);

	// 返回指定key对应的value,并解析成指定类型。如果没有对应值则返回null
	@Nullable
	<T> T getProperty(String key, Class<T> targetType);

	// 返回指定key对应的value,并解析成指定类型。如果没有对应值则返回defaultValue
	<T> T getProperty(String key, Class<T> targetType, T defaultValue);

	
    // 返回指定key的value值,如果没有则抛出异常
	String getRequiredProperty(String key) throws IllegalStateException;

	// 转换指定key的value为指定类型,如果没有则抛出异常
	<T> T getRequiredProperty(String key, Class<T> targetType) throws IllegalStateException;

	
    // 解析${}占位符并替换为getProperty方法返回的结果,无法解析的占位符会被忽略
	String resolvePlaceholders(String text);


    // 解析${}占位符并替换为getProperty方法返回的结果,无法解析的占位符会抛异常
	String resolveRequiredPlaceholders(String text) throws IllegalArgumentException;

}

三,解析过程详解

第一步

AbstractRefreshableConfigApplicationContext.java

	public void setConfigLocations(@Nullable String... locations) {
		if (locations != null) {
			Assert.noNullElements(locations, "Config locations must not be null");
			this.configLocations = new String[locations.length];
			for (int i = 0; i < locations.length; i++) {
				this.configLocations[i] = resolvePath(locations[i]).trim();
			}
		}
		else {
			this.configLocations = null;
		}
	}

第二部

AbstractRefreshableConfigApplicationContext.java

// 路径解析
protected String resolvePath(String path) {
   // 从环境中解析path中的占位符
   return getEnvironment().resolveRequiredPlaceholders(path);
}

第三步

AbstractEnvironment.java

// 对占位符进行解析
public String resolveRequiredPlaceholders(String text) throws IllegalArgumentException {
   return this.propertyResolver.resolveRequiredPlaceholders(text);
}

第四步

AbstractPropertyResolver.java

// 创建占位符解析工具类
public String resolveRequiredPlaceholders(String text) throws IllegalArgumentException {
   if (this.strictHelper == null) {
      this.strictHelper = createPlaceholderHelper(false);
   }
   return doResolvePlaceholders(text, this.strictHelper);
}

第五步

PropertyPlaceholderHelper.java

// 调用占位符解析函数
public String replacePlaceholders(String value, PlaceholderResolver placeholderResolver) {
   Assert.notNull(value, "'value' must not be null");
   return parseStringValue(value, placeholderResolver, new HashSet<String>());
}

// 占位符解析和替换文本的函数
protected String parseStringValue(
      String value, PlaceholderResolver placeholderResolver, Set<String> visitedPlaceholders) {

   StringBuilder result = new StringBuilder(value);

   // 查找占位符前缀
   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");
         }
         // 递归调用,解析占位符中包含的占位符(嵌套占位符)
         placeholder = parseStringValue(placeholder, placeholderResolver, visitedPlaceholders);
         // 获取占位符对应的值
         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) {
                  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) {
            // Proceed with unprocessed value.
            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();
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值