路径解析和占位符

配置文件路径解析

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

// 设置xml配置文件路径
public void setConfigLocations(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等信息。
PropertySourcesPropertyResolverPropertyResolver 的实现,对一组 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()
    //是否包含某个属性
	public boolean containsProperty(String name)
    //得到属性名对应的属性
    public abstract Object getProperty(String name)   
}

搜索过程是按照层次结构执行的。默认情况下,系统属性优先于环境变量。因此,在调用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
    String getProperty(String key);

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

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

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

    // 转换指定key的value为指定类型。如果没有则返回null。
    // 如果value不能转换成指定类型,则抛出ConversionException
    <T> Class<T> getPropertyAsClass(String key, Class<T> targetType);

    // 返回指定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(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();
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值