.propertiesを読み込み、ソースコードをプレースホルダーに置き換える方法の紹介 ${...}

Y2J
リリース: 2017-05-11 09:51:14
オリジナル
4670 人が閲覧しました

この記事では、.properties ファイルの読み取りとプレースホルダー ${...} 置換ソース コード解析に関する関連知識を主に紹介しており、非常に参考価値があります。以下のエディターで見てみましょう

前書き

Bean の一部のパラメーターは、通常、.プロパティ ファイルを作成し、Bean がインスタンス化されるときに、Spring はプレースホルダー "${}" 置換を使用してこれらの .properties ファイルに設定されたパラメータを読み取り、それらを Bean の対応するパラメータに設定します。

このアプローチの最も典型的な例は、JDBC の構成です。この記事では、.properties ファイルを読み取り、プレースホルダー「${}」を置き換えるソース コードを学習します。まず、コードから始めて、DataSource を定義します。 4 つの JDBC パラメータをシミュレートします:

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 + "]";
  }
}
ログイン後にコピー

db.properties ファイルを定義します:

 driveClass=0
 url=1
 userName=2
 password=3
ログイン後にコピー

property.xml ファイルを定義します:

<?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>
ログイン後にコピー

テスト コードを作成します:

public class TestProperties {

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

    DataSource dataSource = (DataSource)ac.getBean("dataSource");
    System.out.println(dataSource);
  }
}
ログイン後にコピー

実行結果はポストされないことは明らかです。 Spring は、プロパティ ファイル内の property を読み取り、「${}」プレースホルダーを置き換える方法です。

PropertyPlaceholderConfigurer クラスの分析

property.xml ファイルには、PropertyPlaceholderConfigurer クラスが含まれています。このクラスの継承関係図を見てください。

見てください、この図から分析できる最も重要な点は、PropertyPlaceholderConfigurer が BeanFactoryPostProcessorinterface

の実装クラスであるということです。

Spring コンテキストは、すべての Bean 定義がロードされた後、Bean の前に postProcessBeanFactory メソッドを 1 回渡す必要があることが予想されます。プレースホルダー「${}」 がインスタンス化されました。 .properties ファイルのソース コード分析を読み取る

postProcessBeanFactory メソッドの実装を見てみましょう:

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);
  }
}
ログイン後にコピー
ログイン後にコピー

3 行目の mergeProperties メソッドに従います:

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;
}
ログイン後にコピー

2 行目のメソッドは、result という名前の新しい Properties を作成します。結果は後続のコードとともに渡され、.properties ファイル内のデータが結果に書き込まれます。

OK、コードが 17 行目に入り、ファイルを通じて .properties ファイルをロードする様子を見てみましょう:

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();
        }
      }
    }
  }
}
ログイン後にコピー

9 行目、PropertyPlaceholderConfigurer の構成をパス リストで渡すことができます (もちろん、渡される db.properties は 1 つだけです)ここで)、3 行目はリストを走査し、9 行目は入力バイト ストリーム InputStream を通じて .properties に対応するバイナリ データを取得します。次に、23 行目のコードは、InputStream 内のバイナリを解析し、最初のパラメータ Properties に書き込みます。 JDK にネイティブな .properties ファイルを読み取るためのツール。

このような単純なプロセスにより、.properties 内のデータが解析され、結果に書き込まれます (結果は、mergeProperties メソッドで生成された新しいプロパティです)。

プレースホルダー "${...}" でソース コード分析を置き換えます

上記の .properties ファイルの読み取りプロセスを見てきましたが、その後、"${}" プレースホルダーを置き換えるか、postProcessBeanFactory メソッドに戻る必要があります。 :

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);
  }
}
ログイン後にコピー
ログイン後にコピー

3 行目は .properties ファイルをマージします (複数の .properties ファイルが同じキーを持つ可能性があるため、マージと呼ばれます)。

6 行目は必要に応じてマージされたプロパティを変換しますが、使い道はありません。

9 行目では、プレースホルダー "${...}" の置き換えが始まります。事前に宣言する必要があることが 1 つあります。

BeanFactoryPostProcessor クラスの postProcessBeanFactory メソッド呼び出しは、Bean 定義が解析された後であるため、現在の beanFactory パラメーターはすでに存在しています。すべての Bean 定義

、Bean 解析プロセスに精通している友人は、これをよく知っているはずです。 9 行目の processProperties メソッドに従います。

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);
}
ログイン後にコピー
4 行目では、新しい PlaceholderResolveStringValueResolver を作成し、Properties を渡します。これは、名前が示すように、.properties ファイル構成を保持する string

value パーサーです。

5 行目の BeanDefinitionVistor で、上記の StringValueResolver を渡します。その名前が示すように、これは文字列値パーサーを保持する Bean 定義アクセス ツールです。 コンストラクター

で渡された StringValueResolver を使用して文字列

を解析する必要があります。 行 7 は、BeanFactory を通じてすべての Bean 定義の名前を取得します。 8 行目ですべての Bean 定義の名前のトラバースを開始します。 11 行目の最初の判断に注意してください

"!(curName.equals(this.beanName)"

、this.beanName は PropertyPlaceholderConfigurer を参照します。これは PropertyPlaceholderConfigurer を意味します。プレースホルダー「${...}」は、それ自体では解析されません。

着重跟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());
}
ログイン後にコピー

看到这个方法轮番访问定义中的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注解

以上が.propertiesを読み込み、ソースコードをプレースホルダーに置き換える方法の紹介 ${...}の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

ソース:php.cn
このウェブサイトの声明
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。
人気のチュートリアル
詳細>
最新のダウンロード
詳細>
ウェブエフェクト
公式サイト
サイト素材
フロントエンドテンプレート