Heim  >  Artikel  >  Java  >  Einführung in die Methode zum Lesen von .properties und zum Ersetzen von Quellcode durch Platzhalter ${...}

Einführung in die Methode zum Lesen von .properties und zum Ersetzen von Quellcode durch Platzhalter ${...}

Y2J
Y2JOriginal
2017-05-11 09:51:144677Durchsuche

In diesem Artikel werden hauptsächlich die relevanten Kenntnisse zum Lesen von .properties-Dateien und zur Analyse des Quellcodes zum Ersetzen von Platzhaltern ${...} vorgestellt, die einen sehr guten Referenzwert haben. Schauen wir uns das unten mit dem Editor an

Vorwort

Einige Parameter in Bean sind relativ fest Wird normalerweise zum Konfigurieren dieser Parameter in der .properties-Datei verwendet. Wenn die Bean dann instanziiert wird, werden die in der .properties-Datei konfigurierten Parameter eingelesen, indem der Platzhalter „${}“ durch Spring ersetzt und auf die entsprechenden Parameter gesetzt wird der Bohne.

Das typischste Beispiel für diesen Ansatz ist die JDBC-Konfiguration. In diesem Artikel wird der Quellcode zum Lesen von .properties-Dateien und zum Ersetzen der Platzhalter „${}“ untersucht simulieren Schauen wir uns die vier JDBC-Parameter an:

public class DataSource {

  /**
   * 驱动类
   */
  private String driveClass;

  /**
   * jdbc地址
   */
  private String url;

  /**
   * 用户名
   */
  private String userName;

  /**
   * 密码
   */
  private String password;

  public String getDriveClass() {
    return driveClass;
  }

  public void setDriveClass(String driveClass) {
    this.driveClass = driveClass;
  }

  public String getUrl() {
    return url;
  }

  public void setUrl(String url) {
    this.url = url;
  }

  public String getUserName() {
    return userName;
  }

  public void setUserName(String userName) {
    this.userName = userName;
  }

  public String getPassword() {
    return password;
  }

  public void setPassword(String password) {
    this.password = password;
  }

  @Override
  public String toString() {
    return "DataSource [driveClass=" + driveClass + ", url=" + url + ", userName=" + userName + ", password=" + password + "]";
  }
}

Definieren Sie eine db.properties-Datei:

 driveClass=0
 url=1
 userName=2
 password=3

Definieren Sie eine Properties.xml-Datei:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xmlns:aop="http://www.springframework.org/schema/aop"
  xmlns:tx="http://www.springframework.org/schema/tx"
  xsi:schemaLocation="http://www.springframework.org/schema/beans
    http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
    http://www.springframework.org/schema/aop
    http://www.springframework.org/schema/aop/spring-aop-3.0.xsd">

  <bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
    <property name="location" value="properties/db.properties"></property>
  </bean> 

  <bean id="dataSource" class="org.xrq.spring.action.properties.DataSource">
    <property name="driveClass" value="${driveClass}" />
    <property name="url" value="${url}" />
    <property name="userName" value="${userName}" />
    <property name="password" value="${password}" />
  </bean>
</beans>

Schreiben Sie einen Testcode:

public class TestProperties {

  @Test
  public void testProperties() {
    ApplicationContext ac = new ClassPathXmlApplicationContext("spring/properties.xml");

    DataSource dataSource = (DataSource)ac.getBean("dataSource");
    System.out.println(dataSource);
  }
}

Die laufenden Ergebnisse werden nicht veröffentlicht. Lassen Sie uns natürlich analysieren, wie Spring das Attribut in der Eigenschaftendatei liest und den Platzhalter „${}“ ersetzt.

PropertyPlaceholderConfigurer-Klassenanalyse

In der Dateiproperties.xml sehen wir eine Klasse PropertyPlaceholderConfigurer, wie der Name schon sagt, handelt es sich um einen Eigenschaftsplatzhalterkonfigurator. Schauen Sie sich diese Klasse an Vererbung Beziehungsdiagramm:

Anhand dieses Diagramms ist das Wichtigste, was wir analysieren können, dass PropertyPlaceholderConfigurer die Implementierungsklasse der Schnittstelle BeanFactoryPostProcessor ist , stellen Sie sich vor, dass Spring-Kontext den Platzhalter „${}“.

Analyse des Quellcodes zum Lesen der .properties-Datei

Werfen wir einen Blick auf die Implementierung der postProcessBeanFactory-Methode:

public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
  try {
    Properties mergedProps = mergeProperties();

    // Convert the merged properties, if necessary.
    convertProperties(mergedProps);
    // Let the subclass process the properties.
    processProperties(beanFactory, mergedProps);
  }
  catch (IOException ex) {
    throw new BeanInitializationException("Could not load properties", ex);
  }
}
Verfolgen Sie die Methode mergeProperties online 3:

protected Properties mergeProperties() throws IOException {
  Properties result = new Properties();

  if (this.localOverride) {
    // Load properties from file upfront, to let local properties override.
    loadProperties(result);
  }

  if (this.localProperties != null) {
    for (Properties localProp : this.localProperties) {
      CollectionUtils.mergePropertiesIntoMap(localProp, result);
    }
  }

  if (!this.localOverride) {
    // Load properties from file afterwards, to let those properties override.
    loadProperties(result);
  }
  return result;
}
Die Methode in Zeile 2 new erstellt ein Properties mit dem Namen result. Dieses Ergebnis wird mit dem nachfolgenden Code übergeben und die Daten in der .properties-Datei werden in das Ergebnis geschrieben.

OK, sehen wir uns an, wie der Code in Zeile 17 eingeht und die .properties-Datei über die Datei lädt:

protected void loadProperties(Properties props) throws IOException {
  if (this.locations != null) {
    for (Resource location : this.locations) {
      if (logger.isInfoEnabled()) {
        logger.info("Loading properties file from " + location);
      }
      InputStream is = null;
      try {
        is = location.getInputStream();

        String filename = null;
        try {
          filename = location.getFilename();
        } catch (IllegalStateException ex) {
          // resource is not file-based. See SPR-7552.
        }

        if (filename != null && filename.endsWith(XML_FILE_EXTENSION)) {
          this.propertiesPersister.loadFromXml(props, is);
        }
        else {
          if (this.fileEncoding != null) {
            this.propertiesPersister.load(props, new InputStreamReader(is, this.fileEncoding));
          }
          else {
            this.propertiesPersister.load(props, is);
          }
        }
      }
      catch (IOException ex) {
        if (this.ignoreResourceNotFound) {
          if (logger.isWarnEnabled()) {
            logger.warn("Could not load properties from " + location + ": " + ex.getMessage());
          }
        }
        else {
          throw ex;
        }
      }
      finally {
        if (is != null) {
          is.close();
        }
      }
    }
  }
}
Zeile 9, die Konfiguration von PropertyPlaceholderConfigurer kann in der Pfadliste übergeben werden ( Natürlich wird hier nur eine db.properties übergeben), Zeile 3 durchläuft die Liste, Zeile 9 erhält die Binärdaten, die .properties entsprechen, über einen Eingabebyte-Stream InputStream, und dann analysiert der Code in Zeile 23 die Binärdaten im InputStream und schreibt sie Unter den Parametern Properties ist Properties das native Tool des JDK zum Lesen von .properties-Dateien.

In einem so einfachen Prozess werden die Daten in .properties analysiert und in das Ergebnis geschrieben (Ergebnis ist ein neues Properties in der mergeProperties-Methode).

Platzhalter „${...}“ ersetzt das Parsen des Quellcodes

Der Lesevorgang der .properties-Datei wurde oben gesehen, und dann „${}“ sollte durch „Platzhalter“ ersetzt werden, kehren wir zur postProcessBeanFactory-Methode zurück:

public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
  try {
    Properties mergedProps = mergeProperties();

    // Convert the merged properties, if necessary.
    convertProperties(mergedProps);
    // Let the subclass process the properties.
    processProperties(beanFactory, mergedProps);
  }
  catch (IOException ex) {
    throw new BeanInitializationException("Could not load properties", ex);
  }
}
Zeile 3 führt die .properties-Dateien zusammen (dies wird Zusammenführen genannt, da mehrere .properties-Dateien möglicherweise denselben Schlüssel haben).

Zeile 6 konvertiert die zusammengeführten Eigenschaften bei Bedarf, aber ich sehe keinen Nutzen.

Zeile 9 beginnt, den Platzhalter „${...}“ zu ersetzen. Eine Sache muss vorab deklariert werden:

Die postProcessBeanFactory-Methode der BeanFactoryPostProcessor-Klasse wird aufgerufen, nachdem die Bean-Definition analysiert wurde , also Die aktuellen beanFactory-Parameter enthalten bereits alle Bean-Definitionen Freunde, die mit dem Bean-Parsing-Prozess vertraut sind, sollten dies sehr gut wissen. Folgen Sie der Methode „processProperties“ in Zeile 9:

protected void processProperties(ConfigurableListableBeanFactory beanFactoryToProcess, Properties props)
    throws BeansException {

  StringValueResolver valueResolver = new PlaceholderResolvingStringValueResolver(props);
  BeanDefinitionVisitor visitor = new BeanDefinitionVisitor(valueResolver);

  String[] beanNames = beanFactoryToProcess.getBeanDefinitionNames();
  for (String curName : beanNames) {
    // Check that we&#39;re not parsing our own bean definition,
    // to avoid failing on unresolvable placeholders in properties file locations.
    if (!(curName.equals(this.beanName) && beanFactoryToProcess.equals(this.beanFactory))) {
      BeanDefinition bd = beanFactoryToProcess.getBeanDefinition(curName);
      try {
        visitor.visitBeanDefinition(bd);
      }
      catch (Exception ex) {
        throw new BeanDefinitionStoreException(bd.getResourceDescription(), curName, ex.getMessage());
      }
    }
  }

  // New in Spring 2.5: resolve placeholders in alias target names and aliases as well.
  beanFactoryToProcess.resolveAliases(valueResolver);
  // New in Spring 3.0: resolve placeholders in embedded values such as annotation attributes.
  beanFactoryToProcess.addEmbeddedValueResolver(valueResolver);
}
Zeile 4 erstellt einen neuen PlaceholderResolvingStringValueResolver und übergibt Eigenschaften. Wie der Name schon sagt, ist dies ein

String -Wert, der die .properties-Datei enthält Konfiguration.

Zeile 5 BeanDefinitionVistor, übergeben Sie den obigen StringValueResolver. Wie der Name schon sagt, handelt es sich um ein Bean-Definitionszugriffstool, das einen String-Wert-Parser enthält. Stellen Sie sich vor, dass

über BeanDefinitionVistor auf Bean-Definitionen zugreifen kann. Wenn die zu analysierende Zeichenfolge verwendet wird, wird der vom -Konstruktor übergebene StringValueResolver zum Parsen der Zeichenfolge verwendet.

Zeile 7 ruft die Namen aller Bean-Definitionen über BeanFactory ab.

Zeile 8 beginnt, die Namen aller Bean-Definitionen zu durchlaufen. Achten Sie auf das erste Urteil in Zeile 11

"!(curName.equals(this.beanName)" bezieht sich this.beanName auf PropertyPlaceholderConfigurer, was bedeutet, dass PropertyPlaceholderConfigurer selbst den Platzhalter „${...}“ nicht analysiert.

着重跟14行的代码,BeanDefinitionVistor的visitBeanDefinition方法,传入BeanDefinition:

public void visitBeanDefinition(BeanDefinition beanDefinition) {
  visitParentName(beanDefinition);
  visitBeanClassName(beanDefinition);
  visitFactoryBeanName(beanDefinition);
  visitFactoryMethodName(beanDefinition);
  visitScope(beanDefinition);
  visitPropertyValues(beanDefinition.getPropertyValues());
  ConstructorArgumentValues cas = beanDefinition.getConstructorArgumentValues();
  visitIndexedArgumentValues(cas.getIndexedArgumentValues());
  visitGenericArgumentValues(cas.getGenericArgumentValues());
}

看到这个方法轮番访问60e23eb984d18edbb092da6b8f295aba定义中的parent、class、factory-bean、factory-method、scope、property、constructor-arg属性,但凡遇到需要"${...}"就进行解析。我们这里解析的是property标签中的"${...}",因此跟一下第7行的代码:

protected void visitPropertyValues(MutablePropertyValues pvs) {
  PropertyValue[] pvArray = pvs.getPropertyValues();
  for (PropertyValue pv : pvArray) {
    Object newVal = resolveValue(pv.getValue());
    if (!ObjectUtils.nullSafeEquals(newVal, pv.getValue())) {
      pvs.add(pv.getName(), newVal);
    }
  }
}

获取属性数组进行遍历,第4行的代码对属性值进行解析获取新属性值,第5行判断新属性值与原属性值不等,第6行的代码用新属性值替换原属性值。因此跟一下第4行的resolveValue方法

protected Object resolveValue(Object value) {
  if (value instanceof BeanDefinition) {
    visitBeanDefinition((BeanDefinition) value);
  }
  else if (value instanceof BeanDefinitionHolder) {
    visitBeanDefinition(((BeanDefinitionHolder) value).getBeanDefinition());
  }
  else if (value instanceof RuntimeBeanReference) {
    RuntimeBeanReference ref = (RuntimeBeanReference) value;
    String newBeanName = resolveStringValue(ref.getBeanName());
    if (!newBeanName.equals(ref.getBeanName())) {
      return new RuntimeBeanReference(newBeanName);
    }
  }
  else if (value instanceof RuntimeBeanNameReference) {
    RuntimeBeanNameReference ref = (RuntimeBeanNameReference) value;
    String newBeanName = resolveStringValue(ref.getBeanName());
    if (!newBeanName.equals(ref.getBeanName())) {
      return new RuntimeBeanNameReference(newBeanName);
    }
  }
  else if (value instanceof Object[]) {
    visitArray((Object[]) value);
  }
  else if (value instanceof List) {
    visitList((List) value);
  }
  else if (value instanceof Set) {
    visitSet((Set) value);
  }
  else if (value instanceof Map) {
    visitMap((Map) value);
  }
  else if (value instanceof TypedStringValue) {
    TypedStringValue typedStringValue = (TypedStringValue) value;
    String stringValue = typedStringValue.getValue();
    if (stringValue != null) {
      String visitedString = resolveStringValue(stringValue);
      typedStringValue.setValue(visitedString);
    }
  }
  else if (value instanceof String) {
    return resolveStringValue((String) value);
  }
  return value;
}

这里主要对value类型做一个判断,我们配置文件里面配置的是字符串,因此就看字符串相关代码,即34行的判断进去,其余的差不多,可以自己看一下源码是怎么做的。第35~第36行的代码就是获取属性值,第38行的代码resolveStringValue方法解析字符串:

protected String resolveStringValue(String strVal) {
  if (this.valueResolver == null) {
    throw new IllegalStateException("No StringValueResolver specified - pass a resolver " +
        "object into the constructor or override the &#39;resolveStringValue&#39; method");
  }
  String resolvedValue = this.valueResolver.resolveStringValue(strVal);
  // Return original String if not modified.
  return (strVal.equals(resolvedValue) ? strVal : resolvedValue);
}

继续跟第6行的方法,valueResolver前面说过了,是传入的一个PlaceholderResolvingStringValueResolver,看一下resolveStringValue方法实现:

 public String resolveStringValue(String strVal) throws BeansException {
   String value = this.helper.replacePlaceholders(strVal, this.resolver);
   return (value.equals(nullValue) ? null : value);
 }

第2行的replacePlaceholders方法顾名思义,替换占位符,它位于PropertyPlaceholderHelper类中,跟一下这个方法:

 public String replacePlaceholders(String value, PlaceholderResolver placeholderResolver) {
   Assert.notNull(value, "Argument &#39;value&#39; must not be null.");
   return parseStringValue(value, placeholderResolver, new HashSet<String>());
}

继续跟第3行的parseStringValue方法,即追踪到了替换占位符的核心代码中:

protected String parseStringValue(
    String strVal, PlaceholderResolver placeholderResolver, Set<String> visitedPlaceholders) {

  StringBuilder buf = new StringBuilder(strVal);

  int startIndex = strVal.indexOf(this.placeholderPrefix);
  while (startIndex != -1) {
    int endIndex = findPlaceholderEndIndex(buf, startIndex);
    if (endIndex != -1) {
      String placeholder = buf.substring(startIndex + this.placeholderPrefix.length(), endIndex);
      if (!visitedPlaceholders.add(placeholder)) {
        throw new IllegalArgumentException(
            "Circular placeholder reference &#39;" + placeholder + "&#39; in property definitions");
      }
      // Recursive invocation, parsing placeholders contained in the placeholder key.
      placeholder = parseStringValue(placeholder, placeholderResolver, visitedPlaceholders);

      // Now obtain the value for the fully resolved key...
      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) {
        // Recursive invocation, parsing placeholders contained in the
        // previously resolved placeholder value.
        propVal = parseStringValue(propVal, placeholderResolver, visitedPlaceholders);
        buf.replace(startIndex, endIndex + this.placeholderSuffix.length(), propVal);
        if (logger.isTraceEnabled()) {
          logger.trace("Resolved placeholder &#39;" + placeholder + "&#39;");
        }
        startIndex = buf.indexOf(this.placeholderPrefix, startIndex + propVal.length());
      }
      else if (this.ignoreUnresolvablePlaceholders) {
        // Proceed with unprocessed value.
        startIndex = buf.indexOf(this.placeholderPrefix, endIndex + this.placeholderSuffix.length());
      }
      else {
        throw new IllegalArgumentException("Could not resolve placeholder &#39;" + placeholder + "&#39;");
      }

      visitedPlaceholders.remove(placeholder);
    }
    else {
      startIndex = -1;
    }
  }

  return buf.toString();
}

过一下此流程:

  1. 获取占位符前缀"${"的位置索引startIndex

  2. 占位符前缀"${"存在,从"${"后面开始获取占位符后缀"}"的位置索引endIndex

  3. 如果占位符前缀位置索引startIndex与占位符后缀的位置索引endIndex都存在,截取中间的部分placeHolder

  4. 从Properties中获取placeHolder对应的值propVal

  5. 如果propVal不存在,尝试对placeHolder使用":"进行一次分割,如果分割出来有结果,那么前面一部分命名为actualPlaceholder,后面一部分命名为defaultValue,尝试从Properties中获取actualPlaceholder对应的value,如果存在则取此value,如果不存在则取defaultValue,最终赋值给propVal

  6. 返回propVal,就是替换之后的值

流程很长,通过这样一整个的流程,将占位符"${...}"中的内容替换为了我们需要的值。

【相关推荐】

1. Java免费视频教程

2. JAVA教程手册

3. 全面解析Java注解

Das obige ist der detaillierte Inhalt vonEinführung in die Methode zum Lesen von .properties und zum Ersetzen von Quellcode durch Platzhalter ${...}. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!

Stellungnahme:
Der Inhalt dieses Artikels wird freiwillig von Internetnutzern beigesteuert und das Urheberrecht liegt beim ursprünglichen Autor. Diese Website übernimmt keine entsprechende rechtliche Verantwortung. Wenn Sie Inhalte finden, bei denen der Verdacht eines Plagiats oder einer Rechtsverletzung besteht, wenden Sie sich bitte an admin@php.cn