likes
comments
collection
share

第5节 Spring源码之 setConfigLocations 方法

作者站长头像
站长
· 阅读数 21

setConfigLocations 方法是在创建 ClassPathXmlApplicationContext 对象时调用,代码如下:

public ClassPathXmlApplicationContext(String[] configLocations,
              boolean refresh, @Nullable ApplicationContext parent) throws BeansException {
   super(parent);
   // 设置资源文件路径
   setConfigLocations(configLocations);
   if (refresh) {
      refresh();
   }
}
  • 方法作用:在 spring 容器启动时解析资源文件路径

第5节 Spring源码之 setConfigLocations 方法 可能有人会说不就是一个application-bean.xml文件名称,有什么好解析的!如果按照常规操作来定义这样一个xml文件,确实索然无味。

一、setConfigLocations解析资源文件业务场景

假设在实际开发中有这样的配置需求,开发环境、生成环境 配置 bean 的 xml 文件不同,如下所示: 第5节 Spring源码之 setConfigLocations 方法

看到我们的资源文件有 application-dev-bean.xmlapplication-prod-bean.xml 文件,那么你怎么做的动态解析呢?

1. 解决方案

使用占位符动态替换 application-${bean.path}-bean.xml 第5节 Spring源码之 setConfigLocations 方法

  • 运行结果: 能够正常读取 application-dev-bean.xml中的 bean 信息

第5节 Spring源码之 setConfigLocations 方法

2. 原理分析

核心解析逻辑是在:

// 解析资源文件名称中包含的占位符,比如 ${...},该段逻辑是能够解析:spring-${username-${username}}.xml 这种格式的资源文件的
this.configLocations[i] = resolvePath(locations[i]).trim();

第5节 Spring源码之 setConfigLocations 方法 这段代码中实现的,来一步步看其中的奥秘。首先看方法调用链路,找资源文件解析的核心方法:

第5节 Spring源码之 setConfigLocations 方法

核心源码PropertyPlaceholderHelper # parseStringValue

    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) {
             // 找到要替换的字符串,也就是 bean.path
             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 中还有占位符
             placeholder = parseStringValue(placeholder, placeholderResolver, visitedPlaceholders);

             // 获取占位符变量对应的值,即 bean.path 的值
             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();
    }

总结:资源文件名称解析,Spring 替我们做了很多有意思的处理:

  • 能够解析资源文件中的占位符 ${...}
  • 能够递归解析,比如 spring-{username-{username}}.xml

二、 PropertyPlaceholderHelper占位符解析工具类

通过上面的源码分析,我们可以看到核心的解析逻辑是在 PropertyPlaceholderHelper 中实现的,那么是否可以利用PropertyPlaceholderHelper在实际开发中进行一些简单的解析占位符逻辑呢?

比如现在有这样一个业务需求,给每个员工发送一个节日祝福的邮件,邮件内容是:

  • 祝张三端午节日快乐!
  • 祝张三国庆节日快乐! 那么我们就可以利用占位符来定义动态的祝福模板:
  • 祝福 ${username} ${holiday}节日快乐!
public class PropertyPlaceholderHelperTests {

	private final PropertyPlaceholderHelper helper = new PropertyPlaceholderHelper("${", "}");

	@Test
	public void template() {
		String holidayText = "祝福${username}${holiday}节日快乐!";

		Properties props = new Properties();
		props.setProperty("username", "张三");
		props.setProperty("holiday", "中秋节");
		String result = helper.replacePlaceholders(holidayText, props);
		System.out.println(result);
	}
}

第5节 Spring源码之 setConfigLocations 方法

通过这个案例,我想说的是源码学习,不仅仅是看,更要学以致用!