spring:与commons-configuration2集成中实现XML配置参数名的别名(alias)

去年写过一篇博客《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.jdbcspring.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;
  }
}

参考资料

《Use Configuration in Spring》

  • 21
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

10km

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值