问题描述
昨天接到了一个需求是这样的,项目A作为公有工具类项目,被项目B和项目C所依赖,同时项目C依赖项目B。A中的配置依赖于外部的properties,在B和C中均配有同名properties以启动项目A,但是在实际运行过程中,项目B和项目C加载的是同一配置文件(可能取决于B和C的加载顺序)。现在的要求是在A中能读到所有配置的配置信息(同名配置append到属性上而不是覆盖)。之前的开发中一直用的都是SpringBoot不是Spring,对使用xml配置的这种方式感到很陌生,只能硬着头皮上。
项目A的xml配置信息
A是一个spring项目,使用classpath*的方式将外部配置加载到项目中。并使用@Value(${})的方式获取Properties里面的值。
<bean class="org.springframework.context.support.PropertySourcesPlaceholderConfigurer">
<property name="ignoreUnresolvablePlaceholders" value="true" />
<property name="ignoreResourceNotFound" value="true"/>
<property name="order" value="2018062911" />
<property name="locations">
<list>
<value>classpath*:conf_default.properties</value>
<value>classpath*:conf.properties</value>
</list>
</property>
</bean>
探索
有关classpath和classpath*
Spring可以通过指定classpath*:或classpath:前缀加路径的方式从classpath下加载文件。
- classpath*:可以从多个jar文件中加载相同的文件。
- classpath:只能加载找到的第一个文件。
而使用classpath加载一般的优先级为:当前classes > jar包中的classes
解决方案
找遍了也没找到这种类似的解决方案,但是有个自定义加载类从而不用@Value方式而是用静态方法读取配置的代码给了我思路。经过自己验证过目前有两种解决方案:
通过Resource读取
这种方法使用
Resource[] resources=resolver.getResources("classpath*:conf.properties" );
这样的方法来将所有的conf.properties配置文件加载到项目当中。经过检验,这种方式确实可以获取到所有的配置文件。后续思路是定义一个Properties的工具类,实现Properties的懒加载,后续项目中使用配置信息的时候使用静态方法来获取,而不是使用@Value注解的方式。
自定义加载类,重写重要方法
经过单步调试发现,Properties加载类中对于需要merge的属性,有一个方法进行处理。
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
try {
Properties mergedProps = this.mergeProperties();
this.convertProperties(mergedProps);
this.processProperties(beanFactory, mergedProps);
} catch (IOException var3) {
throw new BeanInitializationException("Could not load properties", var3);
}
}
再经过漫长的调用链发现,最终调用对properties赋值时,Spring采用的是对value直接覆盖的做法(Properties继承自HashTable)
String key = loadConvert(lr.lineBuf, 0, keyLen, convtBuf);
String value = loadConvert(lr.lineBuf, valueStart, limit - valueStart, convtBuf);
put(key, value);
到这里我们的目标就很明确了,要做的有如下几点。
- xml中自定义加载类PropertyUtil,其他不用改变
<bean class="com.zombie.a.PropertyUtil">
<property name="ignoreUnresolvablePlaceholders" value="true" />
<property name="ignoreResourceNotFound" value="true"/>
<property name="order" value="2018062911" />
<property name="locations">
<list merge="true">
<value>classpath*:conf-default.properties</value>
<value>classpath*:conf.properties</value>
</list>
</property>
</bean>
- 自定义MyProperties 继承自Properties,修改赋值逻辑
实际这一步只要改一行代码,但是很多依赖的函数我直接也一道copy到MyProperties了
put(key, get(key)==null?value:get(key)+","+value);
- 自定义加载类中替换Properties
我的做法是直接重写mergeProperties方法,其实也只用该一行代码,即:Properties result = new MyProperties();
protected Properties mergeProperties() throws IOException {
Properties result = new MyProperties();
if (this.localOverride) {
this.loadProperties(result);
}
if (this.localProperties != null) {
Properties[] var2 = this.localProperties;
int var3 = var2.length;
for(int var4 = 0; var4 < var3; ++var4) {
Properties localProp = var2[var4];
CollectionUtils.mergePropertiesIntoMap(localProp, result);
}
}
if (!this.localOverride) {
this.loadProperties(result);
}
return result;
}
验证
最后做了下验证,我定义了3个conf.properties 1个conf_default.properties最终同一属性的所有值都读到了。
default,prod,aaaa,bbb
完美!收工。
网友评论