Hadoop源碼解析之Configuration簡(jiǎn)介

???????對(duì)配置文件的配置及解析是每個(gè)框架的基本且必不可少的部分,本文主要對(duì)Hadoop中的配置文件的解析類(lèi)Configuration的基本結(jié)構(gòu)及主要方法進(jìn)行介紹。
??????Hadoop的配置文件的操作主要分為三個(gè)部分:配置的加載、屬性讀取和設(shè)置,本文將分別對(duì)其進(jìn)行介紹。如下是Configuration的主要的成員屬性:

/** 
  * 保存了所有的資源配置的來(lái)源信息,資源文件主要有以下幾種形式: URL、String、Path、InputStream和Properties。
  */
private ArrayList<Resource> resources = new ArrayList<Resource>();

/**
  * 記錄了配置文件中配置的final類(lèi)型的屬性名稱(chēng),標(biāo)記為final之后如果另外有同名的屬性,那么該屬性將不會(huì)被替換
  */
private Set<String> finalParameters = Collections.newSetFromMap(new ConcurrentHashMap<String, Boolean>());

/**
  * 記錄了配置初始化后更新過(guò)的屬性,其鍵為更新的屬性名,值為更新操作的來(lái)源
  */
private Map<String, String[]> updatingResource;

/**
  * 記錄了所有的屬性,包括系統(tǒng)初始化以及后續(xù)設(shè)置的屬性
  */
private Properties properties;

/**
  * 記錄了除了初始化之后手動(dòng)調(diào)用set方法設(shè)置的屬性
  */
private Properties overlay;

/**
  * 記錄了過(guò)期的屬性
  */
private static AtomicReference<DeprecationContext> deprecationContext = new AtomicReference<DeprecationContext>(new DeprecationContext(null, defaultDeprecations));

1.配置的加載

???????Configuration中的配置文件的信息主要保存在resources屬性中,該屬性是一個(gè)ArrayList<Resource>類(lèi)型,如下是Resource的結(jié)構(gòu):

private static class Resource {
    private final Object resource;
    private final String name;
    
    // 構(gòu)造方法,get和set方法略
  }

這里的name表示資源文件的名稱(chēng),resource則表示具體的資源,其用一個(gè)Object類(lèi)型,但具體的類(lèi)型如下:

  • URL: 通過(guò)一個(gè)URL鏈接來(lái)進(jìn)行讀取;
  • String: 從當(dāng)前classpath下該字符串所指定的文件讀?。?/li>
  • Path: 以絕對(duì)路徑指定的配置文件或者是使用url指定的配置;
  • InputStream: 以流的形式指定的配置文件;
  • Properties: 以屬性配置類(lèi)保存的配置信息。
    除了可以通過(guò)上述方式進(jìn)行配置的設(shè)置以外,Hadoop還設(shè)置了幾個(gè)默認(rèn)的配置文件:core-default.xml、core-site.xml和hadoop-site.xml,具體的讀取代碼如下:
  static {
    // Add default resources
    addDefaultResource("core-default.xml");
    addDefaultResource("core-site.xml");

    // print deprecation warning if hadoop-site.xml is found in classpath
    ClassLoader cL = Thread.currentThread().getContextClassLoader();
    if (cL == null) {
      cL = Configuration.class.getClassLoader();
    }
    if (cL.getResource("hadoop-site.xml") != null) {
      LOG.warn("DEPRECATED: hadoop-site.xml found in the classpath. " +
          "Usage of hadoop-site.xml is deprecated. Instead use core-site.xml, "
          + "mapred-site.xml and hdfs-site.xml to override properties of " +
          "core-default.xml, mapred-default.xml and hdfs-default.xml " +
          "respectively");
      addDefaultResource("hadoop-site.xml");
    }
  }

這里需要注意的是,Hadoop對(duì)配置文件的讀取并不是在Configuration類(lèi)初始化時(shí)進(jìn)行的,而是在獲取配置信息時(shí)再進(jìn)行讀取,再此之前,配置相關(guān)的信息都保存在resources屬性中。如下是addDefaultResource(String)方法的代碼:

  public static synchronized void addDefaultResource(String name) {
    if(!defaultResources.contains(name)) {
      defaultResources.add(name);
      for(Configuration conf : REGISTRY.keySet()) {
        if(conf.loadDefaults) {
          conf.reloadConfiguration();
        }
      }
    }
  }

???????從上述代碼可以看出,在添加配置文件時(shí),其先檢查默認(rèn)配置文件中是否有該配置文件,如果不存在,則將其添加到defaultResources列表中,并且其會(huì)對(duì)Configuration配置項(xiàng)進(jìn)行檢查,如果其配置了加載默認(rèn)配置文件,那么其就會(huì)對(duì)該配置中保存配置信息的properties和finalParameters進(jìn)行清空,以此觸發(fā)屬性的重新加載。
???????在初始化時(shí)將相關(guān)配置信息添加到resources和defaultResources列表之后,配置文件中屬性的讀取是在客戶(hù)端具體調(diào)用get*方法時(shí)進(jìn)行的,以下是初始化配置信息的代碼:

  protected synchronized Properties getProps() {
    if (properties == null) {
      properties = new Properties();
      Map<String, String[]> backup =
          new ConcurrentHashMap<String, String[]>(updatingResource);
      loadResources(properties, resources, quietmode);

      if (overlay != null) {
        properties.putAll(overlay);
        for (Map.Entry<Object,Object> item: overlay.entrySet()) {
          String key = (String)item.getKey();
          String[] source = backup.get(key);
          if(source != null) {
            updatingResource.put(key, source);
          }
        }
      }
    }
    return properties;
  }

上述代碼中,首先判斷properties是否為空,為空則進(jìn)行初始化,否則直接返回,這也就是前面為什么將properties和finalParameters進(jìn)行清空之后能夠觸發(fā)屬性的重新加載的原因。在初始化配置信息的時(shí)候首先對(duì)更新的資源過(guò)的資源進(jìn)行備份,然后調(diào)用loadResources()方法加載配置文件的信息,加載完之后還會(huì)將用戶(hù)調(diào)用set*方法設(shè)置的屬性(保存在overlay中)添加到properties中,并且將用戶(hù)設(shè)置的信息添加到updatingResource中。如下是loadResources()方法的具體代碼:

  private void loadResources(Properties properties, ArrayList<Resource> resources, boolean quiet) {
    if(loadDefaults) {
      for (String resource : defaultResources) {
        loadResource(properties, new Resource(resource), quiet);
      }
    }
    
    for (int i = 0; i < resources.size(); i++) {
      Resource ret = loadResource(properties, resources.get(i), quiet);
      if (ret != null) {
        resources.set(i, ret);
      }
    }
  }

loadResources是對(duì)所有的配置文件信息進(jìn)行加載的方法,其主要包括兩個(gè)部分:加載默認(rèn)配置文件和加載資源文件。在加載配置文件時(shí)除了Properties和Resource屬性之外,還有一個(gè)boolean類(lèi)型的屬性quiet,該屬性表示是否以“安靜”模式加載配置文件,即加載時(shí)是否打印加載日志。如下是通過(guò)loadResource()加載配置文件的具體代碼:

  private Resource loadResource(Properties properties,
                                Resource wrapper, boolean quiet) {
    String name = UNKNOWN_RESOURCE;
    try {
      Object resource = wrapper.getResource();
      name = wrapper.getName();
      XMLStreamReader2 reader = null;
      boolean returnCachedProperties = false;

      if (resource instanceof URL) {                  // an URL resource
        reader = (XMLStreamReader2)parse((URL)resource);
      } else if (resource instanceof String) {        // a CLASSPATH resource
        URL url = getResource((String)resource);
        reader = (XMLStreamReader2)parse(url);
      } else if (resource instanceof Path) {          // a file resource
        // Can't use FileSystem API or we get an infinite loop
        // since FileSystem uses Configuration API.  Use java.io.File instead.
        File file = new File(((Path)resource).toUri().getPath())
          .getAbsoluteFile();
        if (file.exists()) {
          if (!quiet) {
            LOG.debug("parsing File " + file);
          }
          reader = (XMLStreamReader2)parse(new BufferedInputStream(
              new FileInputStream(file)), ((Path)resource).toString());
        }
      } else if (resource instanceof InputStream) {
        reader = (XMLStreamReader2)parse((InputStream)resource, null);
        returnCachedProperties = true;
      } else if (resource instanceof Properties) {
        overlay(properties, (Properties)resource);
      }

      if (reader == null) {
        if (quiet) {
          return null;
        }
        throw new RuntimeException(resource + " not found");
      }
      Properties toAddTo = properties;
      if(returnCachedProperties) {
        toAddTo = new Properties();
      }
      DeprecationContext deprecations = deprecationContext.get();

      StringBuilder token = new StringBuilder();
      String confName = null;
      String confValue = null;
      String confInclude = null;
      boolean confFinal = false;
      boolean fallbackAllowed = false;
      boolean fallbackEntered = false;
      boolean parseToken = false;
      LinkedList<String> confSource = new LinkedList<String>();

      while (reader.hasNext()) {
        switch (reader.next()) {
        case XMLStreamConstants.START_ELEMENT:
          switch (reader.getLocalName()) {
          case "property":
            confName = null;
            confValue = null;
            confFinal = false;
            confSource.clear();

            // First test for short format configuration
            int attrCount = reader.getAttributeCount();
            for (int i = 0; i < attrCount; i++) {
              String propertyAttr = reader.getAttributeLocalName(i);
              if ("name".equals(propertyAttr)) {
                confName = StringInterner.weakIntern(
                    reader.getAttributeValue(i));
              } else if ("value".equals(propertyAttr)) {
                confValue = StringInterner.weakIntern(
                    reader.getAttributeValue(i));
              } else if ("final".equals(propertyAttr)) {
                confFinal = "true".equals(reader.getAttributeValue(i));
              } else if ("source".equals(propertyAttr)) {
                confSource.add(StringInterner.weakIntern(
                    reader.getAttributeValue(i)));
              }
            }
            break;
          case "name":
          case "value":
          case "final":
          case "source":
            parseToken = true;
            token.setLength(0);
            break;
          case "include":
            // Determine href for xi:include
            confInclude = null;
            attrCount = reader.getAttributeCount();
            for (int i = 0; i < attrCount; i++) {
              String attrName = reader.getAttributeLocalName(i);
              if ("href".equals(attrName)) {
                confInclude = reader.getAttributeValue(i);
              }
            }
            if (confInclude == null) {
              break;
            }
            // Determine if the included resource is a classpath resource
            // otherwise fallback to a file resource
            // xi:include are treated as inline and retain current source
            URL include = getResource(confInclude);
            if (include != null) {
              Resource classpathResource = new Resource(include, name);
              loadResource(properties, classpathResource, quiet);
            } else {
              URL url;
              try {
                url = new URL(confInclude);
                url.openConnection().connect();
              } catch (IOException ioe) {
                File href = new File(confInclude);
                if (!href.isAbsolute()) {
                  // Included resources are relative to the current resource
                  File baseFile = new File(name).getParentFile();
                  href = new File(baseFile, href.getPath());
                }
                if (!href.exists()) {
                  // Resource errors are non-fatal iff there is 1 xi:fallback
                  fallbackAllowed = true;
                  break;
                }
                url = href.toURI().toURL();
              }
              Resource uriResource = new Resource(url, name);
              loadResource(properties, uriResource, quiet);
            }
            break;
          case "fallback":
            fallbackEntered = true;
            break;
          case "configuration":
            break;
          default:
            break;
          }
          break;

        case XMLStreamConstants.CHARACTERS:
          if (parseToken) {
            char[] text = reader.getTextCharacters();
            token.append(text, reader.getTextStart(), reader.getTextLength());
          }
          break;

        case XMLStreamConstants.END_ELEMENT:
          switch (reader.getLocalName()) {
          case "name":
            if (token.length() > 0) {
              confName = StringInterner.weakIntern(token.toString().trim());
            }
            break;
          case "value":
            if (token.length() > 0) {
              confValue = StringInterner.weakIntern(token.toString());
            }
            break;
          case "final":
            confFinal = "true".equals(token.toString());
            break;
          case "source":
            confSource.add(StringInterner.weakIntern(token.toString()));
            break;
          case "include":
            if (fallbackAllowed && !fallbackEntered) {
              throw new IOException("Fetch fail on include for '"
                  + confInclude + "' with no fallback while loading '"
                  + name + "'");
            }
            fallbackAllowed = false;
            fallbackEntered = false;
            break;
          case "property":
            if (confName == null || (!fallbackAllowed && fallbackEntered)) {
              break;
            }
            confSource.add(name);
            DeprecatedKeyInfo keyInfo =
                deprecations.getDeprecatedKeyMap().get(confName);
            if (keyInfo != null) {
              keyInfo.clearAccessed();
              for (String key : keyInfo.newKeys) {
                // update new keys with deprecated key's value
                loadProperty(toAddTo, name, key, confValue, confFinal,
                    confSource.toArray(new String[confSource.size()]));
              }
            } else {
              loadProperty(toAddTo, name, confName, confValue, confFinal,
                  confSource.toArray(new String[confSource.size()]));
            }
            break;
          default:
            break;
          }
        default:
          break;
        }
      }
      reader.close();

      if (returnCachedProperties) {
        overlay(properties, toAddTo);
        return new Resource(toAddTo, name);
      }
      return null;
    } catch (IOException e) {
      LOG.fatal("error parsing conf " + name, e);
      throw new RuntimeException(e);
    } catch (XMLStreamException e) {
      LOG.fatal("error parsing conf " + name, e);
      throw new RuntimeException(e);
    }
  }

從代碼中可以看出,在加載每個(gè)配置文件的時(shí)候,首先通過(guò)instanceof判斷wrapper對(duì)象中Object類(lèi)型的屬性resource是什么類(lèi)型,然后根據(jù)具體不同的類(lèi)型將其轉(zhuǎn)換為一個(gè)XMLStreamReader2類(lèi)型的reader。對(duì)于轉(zhuǎn)換之后的reader,其會(huì)依次讀取xml配置文件中的具體標(biāo)簽信息,如:name、value、final、source等等,并且將讀取后的信息保存在properties中,這里需要注意的是,在代碼中的case "include"處可以看出,配置文件中如果使用了<include></include>標(biāo)簽引入其他的配置文件,那么其會(huì)遞歸的調(diào)用loadResource()方法對(duì)其進(jìn)行讀取。
???????在加載完配置

2.屬性的獲取

???????Configuration類(lèi)主要是通過(guò)get方法獲取相關(guān)屬性的,其get方法的種類(lèi)有三十多個(gè),如:get(String)、get(String, String)、getBoolean(String, Boolean)、getClass(String, Class<?>)等等,但是最終調(diào)用的還是get(String, String)方法,該方法的作用是獲取一個(gè)某個(gè)名稱(chēng)對(duì)應(yīng)的屬性值,具體代碼如下:

  public String get(String name, String defaultValue) {
    // 處理過(guò)期的屬性
    String[] names = handleDeprecation(deprecationContext.get(), name);
    String result = null;
    // 對(duì)屬性值中形如${foo.bar}引入的其他屬性進(jìn)行替換
    for(String n : names) {
      result = substituteVars(getProps().getProperty(n, defaultValue));
    }
    return result;
  }

通過(guò)上述代碼可以看出,get(String, String)方法主要做了兩件事:處理過(guò)期鍵和對(duì)屬性值進(jìn)行標(biāo)簽替換處理。根據(jù)前面的介紹我們知道deprecationContext是一個(gè)AtomicReference<DeprecationContext>類(lèi)型的變量,其保存有過(guò)期鍵的相關(guān)信息,并且這里使用AtomicReference包裝,也就是說(shuō)其獲取和更新都是原子化的。這里我們首先講解一下DeprecationContext具體實(shí)現(xiàn)方式,如下是該類(lèi)的代碼:

  private static class DeprecationContext {
    /**
     * 保存有過(guò)期鍵信息,其值為主要是該過(guò)期鍵更新后的鍵的信息
     */
    private final Map<String, DeprecatedKeyInfo> deprecatedKeyMap;

    /**
     * 對(duì)于DeprecatedKeyInfo,其有一個(gè)String[] newKeys屬性,即更新后的鍵信息,這里reverseDeprecatedKeyMap的
     * 鍵為newKeys中的各個(gè)字符串,而值則對(duì)應(yīng)于newKeys所在的DeprecatedKeyInfo對(duì)象在deprecatedKeyMap所屬的鍵
     */
    private final Map<String, String> reverseDeprecatedKeyMap;

    /**
     * 根據(jù)另一個(gè)DeprecationContext對(duì)象實(shí)例化一個(gè)DeprecationContext對(duì)象,這里DeprecationDelta指的是除了DeprecationContext對(duì)象以外新增加的屬性
     */
    @SuppressWarnings("unchecked")
    DeprecationContext(DeprecationContext other, DeprecationDelta[] deltas) {
      HashMap<String, DeprecatedKeyInfo> newDeprecatedKeyMap = new HashMap<String, DeprecatedKeyInfo>();
      HashMap<String, String> newReverseDeprecatedKeyMap = new HashMap<String, String>();
      if (other != null) {
        for (Entry<String, DeprecatedKeyInfo> entry : other.deprecatedKeyMap.entrySet()) {
          newDeprecatedKeyMap.put(entry.getKey(), entry.getValue());
        }
        for (Entry<String, String> entry : other.reverseDeprecatedKeyMap.entrySet()) {
          newReverseDeprecatedKeyMap.put(entry.getKey(), entry.getValue());
        }
      }
      for (DeprecationDelta delta : deltas) {
        if (!newDeprecatedKeyMap.containsKey(delta.getKey())) {
          DeprecatedKeyInfo newKeyInfo = new DeprecatedKeyInfo(delta.getNewKeys(), delta.getCustomMessage());
          newDeprecatedKeyMap.put(delta.key, newKeyInfo);
          // 從這里可以看出,reverseDeprecatedKeyMap中的鍵為更新之后的過(guò)期鍵信息,而值為最初的過(guò)期鍵
          for (String newKey : delta.getNewKeys()) {
            newReverseDeprecatedKeyMap.put(newKey, delta.key);
          }
        }
      }
      this.deprecatedKeyMap = UnmodifiableMap.decorate(newDeprecatedKeyMap);
      this.reverseDeprecatedKeyMap = UnmodifiableMap.decorate(newReverseDeprecatedKeyMap);
    }

    Map<String, DeprecatedKeyInfo> getDeprecatedKeyMap() {
      return deprecatedKeyMap;
    }

    Map<String, String> getReverseDeprecatedKeyMap() {
      return reverseDeprecatedKeyMap;
    }
  }

??????? DeprecationContext中主要有兩個(gè)屬性:deprecatedKeyMap和reverseDeprecatedKeyMap。對(duì)于deprecatedKeyMap,其鍵是過(guò)期的鍵,而值則主要保存該鍵被替換之后的新鍵的信息;對(duì)于reverseDeprecatedKeyMap,其鍵為某個(gè)過(guò)期鍵更新之后的鍵,而值則了被更新的過(guò)期鍵。
??????? 在介紹了DeprecationContext的具體結(jié)構(gòu)之后,我們繼續(xù)來(lái)看get(String, String)方法中handleDeprecation()方法的具體處理方式,以下是該方法的具體代碼:

  private String[] handleDeprecation(DeprecationContext deprecations, String name) {
    if (null != name) {
      name = name.trim();
    }
    // 默認(rèn)使用目標(biāo)name作為返回值
    String[] names = new String[]{name};
    // 查詢(xún)當(dāng)前name所對(duì)應(yīng)的過(guò)期鍵信息,并且獲取其更新后的信息
    DeprecatedKeyInfo keyInfo = deprecations.getDeprecatedKeyMap().get(name);
    if (keyInfo != null) {
      if (!keyInfo.getAndSetAccessed()) {
        logDeprecation(keyInfo.getWarningMessage(name));
      }
      // 將當(dāng)前過(guò)期鍵更新后的名稱(chēng)返回
      names = keyInfo.newKeys;
    }
    // 如果沒(méi)用通過(guò)set*方法設(shè)置的屬性,那么直接返回
    Properties overlayProperties = getOverlay();
    if (overlayProperties.isEmpty()) {
      return names;
    }

    // 查找當(dāng)前name在DeprecatedKeyInfo中對(duì)應(yīng)的最新的keys,如果某個(gè)key的屬性在overlay(用戶(hù)設(shè)置的key-value)中存在,那么就將該key對(duì)應(yīng)的
    // 值更新該key的值為所更新的過(guò)期鍵的值
    for (String n : names) {
      String deprecatedKey = deprecations.getReverseDeprecatedKeyMap().get(n);  // 查找當(dāng)前name是否在更新后的key中存在
      if (deprecatedKey != null && overlayProperties.containsKey(n)) {  // 當(dāng)前name在DeprecatedKeyInfo更新后的名字中存在,且在overlayProperties中也存在當(dāng)前name的鍵
        String deprecatedValue = overlayProperties.getProperty(deprecatedKey);
        if (deprecatedValue != null) {
          getProps().setProperty(n, deprecatedValue); // 將DeprecatedKeyInfo中新的鍵與舊的鍵所對(duì)應(yīng)的值關(guān)聯(lián)起來(lái),保存在properties和overlay中
          overlayProperties.setProperty(n, deprecatedValue);
        }
      }
    }
    return names;
  }

???????總結(jié)來(lái)說(shuō),在handleDeprecation()方法中,其會(huì)查詢(xún)當(dāng)前的name是否是過(guò)期鍵,如果不是,則將當(dāng)前name組裝為一個(gè)數(shù)組返回,如果是,則通過(guò)該過(guò)期鍵獲取其所對(duì)應(yīng)的更新之后的鍵,并將其作為返回值。除此之外,其還會(huì)在調(diào)用者設(shè)置的屬性(overlayProperties)中查詢(xún)其是否保存有當(dāng)前過(guò)期鍵所更新的鍵名的信息,如果有,則將其更新為該過(guò)期鍵所對(duì)應(yīng)的值。
???????接下來(lái)我們回頭看看get(String, String)方法,在通過(guò)handleDeprecation()方法獲取到該name的相關(guān)信息之后,這里主要調(diào)用了substituteVars()方法,該方法的主要作用為對(duì)獲取到的屬性值中的占位符如${foo.bar}進(jìn)行替換,這里getProps()方法即為前面所講解的通過(guò)配置文件初始化相關(guān)屬性的方法。以下是substituteVars()的具體實(shí)現(xiàn):

  private String substituteVars(String expr) {
    if (expr == null) {
      return null;
    }
    String eval = expr;
    for(int s = 0; s < MAX_SUBST; s++) {
      // 返回值為長(zhǎng)度為2的數(shù)組,該方法獲取屬性值中最內(nèi)層占位符的起始和終止索引,如${prefix${foo.bar}suffix}將返回foo.bar的起始和終止索引
      final int[] varBounds = findSubVariable(eval);
      if (varBounds[SUB_START_IDX] == -1) {
        return eval;
      }
      final String var = eval.substring(varBounds[SUB_START_IDX], varBounds[SUB_END_IDX]);
      String val = null;
      try {
        // 判斷是否為系統(tǒng)屬性,系統(tǒng)屬性以env.開(kāi)頭
        if (var.startsWith("env.") && 4 < var.length()) {
          String v = var.substring(4);
          int i = 0;
          for (; i < v.length(); i++) {
            char c = v.charAt(i);
            if (c == ':' && i < v.length() - 1 && v.charAt(i + 1) == '-') {
              val = getenv(v.substring(0, i));
              if (val == null || val.length() == 0) {
                val = v.substring(i + 2);
              }
              break;
            } else if (c == '-') {
              val = getenv(v.substring(0, i));
              if (val == null) {
                val = v.substring(i + 1);
              }
              break;
            }
          }
          if (i == v.length()) {
            // i == v.length()說(shuō)明為系統(tǒng)屬性,那么返回值為系統(tǒng)屬性
            val = getenv(v);
          }
        } else {
          // 如果不為系統(tǒng)屬性,則從當(dāng)前上下文環(huán)境中獲取該屬性
          val = getProperty(var);
        }
      } catch(SecurityException se) {
        LOG.warn("Unexpected SecurityException in Configuration", se);
      }
      if (val == null) {
        // 如果既不是系統(tǒng)屬性也不是環(huán)境屬性,則在配置文件或者是用戶(hù)設(shè)置的屬性中查找
        val = getRaw(var);
      }
      if (val == null) {
        // val為null,說(shuō)明該屬性沒(méi)有定義,則直接返回原始值
        return eval;
      }

      final int dollar = varBounds[SUB_START_IDX] - "${".length();
      final int afterRightBrace = varBounds[SUB_END_IDX] + "}".length();  // 因?yàn)檫@里SUB_END_INDEX實(shí)際上指向的就是"{"
      final String refVar = eval.substring(dollar, afterRightBrace);

      // 這里refVar即為要替換的占位符,如${foo.bar},走到這一步說(shuō)明foo.bar有對(duì)應(yīng)的屬性值,即val,這里判斷val中是否繼續(xù)包含
      // 有${foo.bar},如果有,則說(shuō)明發(fā)生了嵌套占位,這種情況直接返回原始字符串,否則會(huì)發(fā)生無(wú)限循環(huán)
      if (val.contains(refVar)) {
        return expr;
      }

      eval = eval.substring(0, dollar) + val + eval.substring(afterRightBrace);
    }
    throw new IllegalStateException("Variable substitution depth too large: " + MAX_SUBST + " " + expr);
  }

???????總結(jié)而言,substituteVars()方法主要對(duì)屬性值中的占位符進(jìn)行替換,對(duì)于屬性值的獲取,其可以通過(guò)三個(gè)途徑進(jìn)行:系統(tǒng)變量、當(dāng)前環(huán)境變量和配置文件及用戶(hù)設(shè)置的屬性值,并且其優(yōu)先級(jí)是:系統(tǒng)變量 > 當(dāng)前環(huán)境變量 > 配置文件及用戶(hù)設(shè)置的屬性值。除此之外,該方法還會(huì)處理嵌套占位符,如${prefix${foo.bar}suffix},其是由內(nèi)而外進(jìn)行解析,直到不存在占位符,或者是進(jìn)行解析的層數(shù)超過(guò)了20層(最外層的for循環(huán)控制,這里MAX_SUBST為20)。

3.屬性設(shè)置

???????相對(duì)而言,屬性的設(shè)置要簡(jiǎn)單一些,和get方法類(lèi)似,雖然set方法也有很多,但其最終還是調(diào)用的set(String, String, String)方法,其第一個(gè)和第二個(gè)參數(shù)為要設(shè)置的鍵值對(duì),第三個(gè)參數(shù)則為當(dāng)前屬性值的來(lái)源。如下是該方法的具體實(shí)現(xiàn):

  public void set(String name, String value, String source) {
    Preconditions.checkArgument(name != null, "Property name must not be null");
    Preconditions.checkArgument(value != null, "The value of property %s must not be null", name);
    name = name.trim();
    DeprecationContext deprecations = deprecationContext.get();
    if (deprecations.getDeprecatedKeyMap().isEmpty()) {
      // 初始化配置文件信息
      getProps();
    }
    getOverlay().setProperty(name, value);
    getProps().setProperty(name, value);
    String newSource = (source == null ? "programmatically" : source);

    if (!isDeprecated(name)) {
      // 這里該name不是過(guò)期鍵分為兩種情況,一種是在存儲(chǔ)過(guò)期鍵的map(即deprecatedKeyMap)中沒(méi)有相應(yīng)數(shù)據(jù),
      // 而在更新的map(即reverseDeprecatedKeyMap)中有數(shù)據(jù),第二種是在這兩個(gè)map中都沒(méi)有數(shù)據(jù)
      updatingResource.put(name, new String[] {newSource});
      // 判斷該鍵是否為更新某一個(gè)過(guò)期鍵之后的鍵,如果是,則獲取所有更新了該過(guò)期鍵的鍵
      String[] altNames = getAlternativeNames(name);
      if(altNames != null) {
        // 更新所有該鍵所對(duì)應(yīng)的過(guò)期鍵更新之后的鍵值
        for(String n: altNames) {
          if(!n.equals(name)) {
            getOverlay().setProperty(n, value);
            getProps().setProperty(n, value);
            updatingResource.put(n, new String[] {newSource});
          }
        }
      }
    } else {
      // 如果該鍵為過(guò)期鍵,則將該過(guò)期鍵更新之后的鍵的值都設(shè)置為新的值
      String[] names = handleDeprecation(deprecationContext.get(), name);
      String altSource = "because " + name + " is deprecated";
      for(String n : names) {
        getOverlay().setProperty(n, value);
        getProps().setProperty(n, value);
        updatingResource.put(n, new String[] {altSource});
      }
    }
  }

???????對(duì)于set(String, String, String)方法,其不僅更新了當(dāng)前鍵值對(duì)的屬性,而且還判斷了該鍵是否為過(guò)期鍵或者是更新過(guò)期鍵之后的鍵,如果是則將更新之后的鍵所對(duì)應(yīng)的值都設(shè)置為新值。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • Spring Cloud為開(kāi)發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見(jiàn)模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn),斷路器,智...
    卡卡羅2017閱讀 136,568評(píng)論 19 139
  • 國(guó)家電網(wǎng)公司企業(yè)標(biāo)準(zhǔn)(Q/GDW)- 面向?qū)ο蟮挠秒娦畔?shù)據(jù)交換協(xié)議 - 報(bào)批稿:20170802 前言: 排版 ...
    庭說(shuō)閱讀 12,420評(píng)論 6 13
  • Android 自定義View的各種姿勢(shì)1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 179,052評(píng)論 25 709
  • 剛剛和好友談起金錢(qián)的事情,很多人為了錢(qián)怎么地怎么地的,我想給大家講個(gè)故事,看看我們古人的金錢(qián)觀,古人的高風(fēng)亮節(jié)。 ...
    右邊的雨閱讀 1,355評(píng)論 0 0
  • 什么是向往的生活?這個(gè)問(wèn)題一拋出是想從其他人的口中得到答案,其實(shí)真正的答案在自己的內(nèi)心深處。因?yàn)橛辛讼蛲?,你才?huì)對(duì)...
    靜苜蓿閱讀 475評(píng)論 0 0

友情鏈接更多精彩內(nèi)容