去年写过一篇博客《java:commons-configuration2与spring的集成》介绍了如何在spring环境下使用commons-configuration2提供的更丰富的配置管理功能。实现在各种Spring场景下以与application.yml中定义的配置参数一致的方式进行访问。
问题
commons-configuration2与spring的集成的问题算是解决了。但新问题来了。我们这个项目原本不是基于spring框架的,但是配置管理是基于commons-configuration2的,XML配置文件中也有数据库相关的配置,示例如下:
<database description="数据库参数配置">
<jdbc>
<!-- JDBC驱动类名,目前只支持mysql,不要修改 -->
<driver description="JDBC driver class name" hide="true">com.mysql.jdbc.Driver</driver>
<url description="数据库连接URL" hide="true"></url>
<username description="数据库访问用户名"></username>
<password description="数据库访问密码"></password>
</jdbc>
</database>
迁移到spring框架后。spring关于数据库的相关配置却是如下这样的:
<spring description="spring配置">
<datasource description="spring数据源配置">
<username description="数据库访问用户名"></username>
<password description="数据库访问密码"></password>
<driver-class-name description="JDBC driver class name"></driver-class-name>
<url description="数据库连接URL"></url>
<!-- 其他略 ->
</datasource>
</spring>
为了保持兼容性,项目有一些模块读取配置还是要通过读取database.jdbc
下的节点来配置数据库访问,如果要让spring框架能正常连接数据库就还需要配置spring.datasource
。
如果要求对database.jdbc
和spring.datasource
两个节点都要配置相同的参数,就对系统运维来说就不合理了。而且容易出错。
alias
如何能即保持兼容性,又不增加运维复杂度,不需要维护两处相同配置呢?
不论是commons-configuration2还是spring官方都没有给出答案,这个问题只能自己来解决:
可以如下在对spring.datasource
下的节点增加别名(alias)。
<spring description="spring配置">
<datasource description="spring数据源配置">
<username description="数据库访问用户名" alias="database.jdbc.username"></username>
<password description="数据库访问密码" alias="database.jdbc.password"></password>
<driver-class-name description="JDBC driver class name",alias="database.jdbc.driver"></driver-class-name>
<url description="数据库连接URL" alias="database.jdbc.url"></url>
<!-- 其他略 ->
</datasource>
</spring>
在spring读取一个节点的值时,我们希望读取该节点为空时,对于有别名(alias)的节点,尝试去读取别名指定的节点并返回。这样以来,只需要在database.jdbc
节点下配置数据库相关参数就可以了,spring框架也能读取到同样的值。
实现这个逻辑需要的思路就是参照org.apache.commons.configuration2.spring.ConfigurationPropertySource
实现org.springframework.core.env.PropertySource
接口的
getProperty(String)
方法,当属性(节点)未定义,但该属性如果定义了alias(别名),则尝试读取别名的值。
如下为实现代码:
import java.util.Spliterators;
import java.util.stream.StreamSupport;
import org.apache.commons.configuration2.Configuration;
import org.springframework.core.env.EnumerablePropertySource;
/**
* 参照{@link org.apache.commons.configuration2.spring.ConfigurationPropertySource}
* 实现{@link org.springframework.core.env.PropertySource}接口<br>
* {@link #getProperty(String)}实现别名尝试机制,当属性未定义,但该属性如果定义了alias(别名),则尝试读取别名的值
* @author guyadong
*
*/
public class AliasConfigurationPropertySource extends EnumerablePropertySource<Configuration>
{
private volatile String[] propertyNames;
public AliasConfigurationPropertySource(final String name, final Configuration source)
{
super(name, source);
}
protected AliasConfigurationPropertySource(final String name)
{
super(name);
}
/**
* 懒加载方式获取所有属性名
*/
@Override
public String[] getPropertyNames() {
if(null == propertyNames) {
synchronized (this) {
if(null == propertyNames) {
/**
* 过滤掉所有属性字段,
* 在 org.apache.commons.configuration2.Configuration.getKeys()返回的属性名中结尾格式为'[@xxx]的是XML节点中的属性,这些数据对于spring是无用的
* 如以 '.[@description]' 结尾的属性名即代表如下XML中的'description'属性
* <node description="hello"></node>
*/
propertyNames = StreamSupport.stream(Spliterators.spliteratorUnknownSize(source.getKeys(), 0),false)
.filter(key->!key.matches(".+\\[@\\w+\\]"))
.toArray(String[]::new);
}
}
}
return propertyNames;
}
@Override
public Object getProperty(final String name) {
Object v = source.getProperty(name);
if(null == v){
/**
* 如果定义了alias(别名),则尝试读取别名的值
*/
String alias = source.getString(name + "[@alias]");
if(!isNullOrEmpty(alias)){
return source.getProperty(alias);
}
}
if(v instanceof String){
return emptyToNull((String)v);
}
return v;
}
static boolean isNullOrEmpty( String string) {
return string == null || string.isEmpty();
}
static String emptyToNull(String string) {
return isNullOrEmpty(string) ? null : string;
}
}
使用示例:
以上一篇博客《java:commons-configuration2与spring的集成》中的方案1为例,在其基础上用AliasConfigurationPropertySource代替org.apache.commons.configuration2.spring.ConfigurationPropertySource
就可以了
import org.apache.commons.configuration2.builder.fluent.Configurations;
import org.apache.commons.configuration2.ex.ConfigurationException;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.support.PropertySourcesPlaceholderConfigurer;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.MutablePropertySources;
@Configuration
class Config {
@Bean
public PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer(ConfigurableEnvironment env)
throws ConfigurationException {
PropertySourcesPlaceholderConfigurer configurer = new PropertySourcesPlaceholderConfigurer();
MutablePropertySources sources = new MutablePropertySources();
/**
* 将xml配置文件封装为AliasConfigurationPropertySource,最后添加到 PropertySourcesPlaceholderConfigurer
*/
sources.addLast(new AliasConfigurationPropertySource("xml configuration", new Configurations().xml("aXmlConfigFile.xml")));
configurer.setPropertySources(sources);
configurer.setEnvironment(env);
return configurer;
}
}