任何框架想要灵活都会使用配置文件,使用配置文件就意味着要读取它,配置文件中一个通用的套路就是使用占位符
。它就类似于变量,让开发者只定义一次,方便修改。那么,各框架是如何读取配置文件并解析占位符的呢?
Spring也免不了要解析占位符的。
之前(2016年03月)曾研究过MyBatis-V3.2.6的源代码,当时把MyBatis的源码中关于解析占位符的代码抽离出来了(代码在这里)。而今(2018-05-08)看Spring的源码就发现(简单的说)框架也就是加载配置文件、解析成自己的类等这些简单的工作。
Spring的源码果真优秀!
其中有几个接口,AliasRegistry、BeanFactory、ResourceLoader等,具体含义我就不敢乱说了,只敢意会……
其中看到了类SimpleAliasRegistry的,简单说它就是维护一个key-value形式的名称-别名【其实可以用Map来做啊,但是要考虑到key中含有占位符的问题(如一个key是http://${ip}:${port}/aaa/bbb/ccc)的情况) 】,它有个方法叫resolveAliases(),用来把名称-别名里的占位符替换成具体的值,该方法的参数类型是StringValueResolver,该接口有个实现类叫PlaceholderResolvingStringValueResolver,但是被Spring整成了内部私有类了(在外部无法实例化)。
我的目标很简单,能够使用Spring框架的某些类完成占位符替换的工作。
因为无法直接单独使用StringValueResolver的某一实现类来完成,所以就慢慢找到了PropertyResolver接口。可以在
org.springframework.beans.factory.config.PropertyPlaceholderConfigurer#processProperties()
方法中找到真正的调用。
我尝试着自行编写PlaceholderResolvingStringValueResolver类(因为不想动它本身的代码)来完成,发现还要自行编写PropertyPlaceholderConfigurerResolver,而PropertyPlaceholderConfigurerResolver又调用了PropertyPlaceholderConfigurer里的方法,写了一个又一个类,越发感觉此方案行不通。(即使行得通,新写了很多类也不太合适)
思索N久……
看到org.springframework.beans.factory.config.PropertyPlaceholderConfigurer.PlaceholderResolvingStringValueResolver的构造方法中创建了一个PropertyPlaceholderHelper对象,最终调用PropertyPlaceholderHelper.replacePlaceholders(String, PlaceholderResolver)方法进行点位符操作的。而PropertyPlaceholderHelper还有一个方法叫replacePlaceholders(String, Properties),它明显符合我们的要求!于是测试类出现:
public class PropertiesParseTest {
private static final Logger LOG = LoggerFactory.getLogger(PropertiesParseTest.class);
// 注意,只有name1和name2是对应的key,所以当<code>pph.replacePlaceholders(alias2, props);</code>时alias2对应的并没有key,所以就原样输出了
private final String name1 = "name1";
private final String alias1 = "alias1";
private final String name2 = "name2-${name1}-a${aaa}";
private final String alias2 = "alias2-${alias1}-";
protected String placeholderPrefix = PlaceholderConfigurerSupport.DEFAULT_PLACEHOLDER_PREFIX;
protected String placeholderSuffix = PlaceholderConfigurerSupport.DEFAULT_PLACEHOLDER_SUFFIX;
@Test
public void testParse() {
Properties props = new Properties();
props.setProperty(name1, alias1);
props.setProperty(name2, alias2);
PropertyPlaceholderHelper pph = new PropertyPlaceholderHelper(placeholderPrefix, placeholderSuffix);
String name1Final = pph.replacePlaceholders(name1, props);
String alias1Final = pph.replacePlaceholders(alias1, props);
String name2Final = pph.replacePlaceholders(name2, props);
String alias2Final = pph.replacePlaceholders(alias2, props);
LOG.debug("name1Final: " + name1Final);
LOG.debug("alias1Final: " + alias1Final);
LOG.debug("name2Final: " + name2Final);
LOG.debug("alias2Final: " + alias2Final);
}
}
原来操作竟如此简单:创建PropertyPlaceholderHelper,并调用replacePlaceholders(String, Properties)方法即可。
说明:
- replacePlaceholders(String, Properties)会直接调用replacePlaceholders(String, PlaceholderResolver)方法的,看下源码就可知道。
- 结果很简单,但过程是耗时的。