解决 @ConfigurationProperties 合并多个yml 或者properties 文件里面的 属性( List type ) 的合并

一、 介绍

如果我们在 yml 文件里面配置了 List 类型的属性, 然后 在 配置属性的类上面 使用 @ConfigurationProperties 注解,便可以直接注入 List 属性 ,如果把属性分在多个文件 里面, Spring 是不支持的.

最后解决方案很简单, 但是也很不错, Spring 维护者 建议的最佳方案

我自己内心的想法: 一开始 看了源码之后,知道是不支持的, 便开始想办法解决, 想了好多种方法

  1. 继承 InitializingBean , EnvironmentAware ,重写 afterPropertiesSet() ,通过 environment.getProperties(“xxx”) 进行重新赋值
  2. 通过 继承 beanpostprocessor, 加上 反射, 也是重新赋值

最后搞得挺烦的 ,但是最终的解决方案让我 又一次加深理解了这句话 : 代码之上是思想,代码之上是思想,代码之上是思想 ,说三遍!!!

一、案例 背景

如果我们在 yml 文件里面配置了 List 类型的属性, 然后 在 配置属性的类上面 使用 @ConfigurationProperties 注解,便可以直接注入 List 属性 ,例如:

我们在 the application.yml 里面配置如下:

test:
  db:
    datasources:
      - url: jdbc:mysql://localhost:3306/writedb
        username: root
        password: root
      - url: jdbc: mysql://localhost:3306/readdb
        username: root
        password: root

我们在 类里面加上 注解 @ConfigurationProperties(prefix = “test.db”) ,只要启动时扫描到,

@ConfigurationProperties(prefix = "test.db")
@Configuration
public class AutoLoadDataSourceProperties {

    private List<DataSourceCommonProperties> datasources;	
	
	// get
        // set
}

二、 改动 以及结果

但是 现在需要将里面的 用户名/密码抽到另外一个 文件里面, 需要统一管理, 不能在 配置文件里面单独配置 用户名和密码, 于是就分开配置

the application.yml :

test:
  db:
    datasources:
      - url: jdbc:mysql://localhost:3306/writedb
      - url: jdbc:mysql://localhost:3306/readdb

另外一个文件 application-secrets.yml

test:
  db:
    datasources:
      - username: root
        password: root
      - username: root
        password: root

这样配置之后是不生效的, 并没有进行合并, 翻开源码看了一下, 确实是没有合并的,源码解析如下.

二、 源码分析

对了List 这种 属性的赋值, 前面的就 暂时不分析了, 直接进入重点 赋值 阶段 :

2.1 CollectionBinder.class

最重要的是 在 Binder.class 类里面 , 根据不同的类型 获取对应的 绑定 类 ,我们这里是List 类型, 就返回 new CollectionBinder(context);

	private AggregateBinder<?> getAggregateBinder(Bindable<?> target, Context context) {
		Class<?> resolvedType = target.getType().resolve(Object.class);
		if (Map.class.isAssignableFrom(resolvedType)) {
			return new MapBinder(context);
		}
		if (Collection.class.isAssignableFrom(resolvedType)) {
			return new CollectionBinder(context);
		}
		if (target.getType().isArray()) {
			return new ArrayBinder(context);
		}
		return null;
	}

2.2 CollectionBinder.class#bindAggregate

针对List 类型的属性绑定,主要在 方法 bindAggregate 里面 , 详细的 在 方法 bindIndexed

	@Override
	protected Object bindAggregate(ConfigurationPropertyName name, Bindable<?> target,
			AggregateElementBinder elementBinder) {
		Class<?> collectionType = (target.getValue() != null) ? List.class : target.getType().resolve(Object.class);
		ResolvableType aggregateType = ResolvableType.forClassWithGenerics(List.class,
				target.getType().asCollection().getGenerics());
		ResolvableType elementType = target.getType().asCollection().getGeneric();
		IndexedCollectionSupplier result = new IndexedCollectionSupplier(
				() -> CollectionFactory.createCollection(collectionType, elementType.resolve(), 0));
		bindIndexed(name, target, elementBinder, aggregateType, elementType, result);
		if (result.wasSupplied()) {
			return result.get();
		}
		return null;
	}

2.2 CollectionBinder.class#bindIndexed

这里就是最核心的, 从下面的逻辑我们可以看出 ,就是 通过 getSources() 获取到所有的配置文件,
然后 依次去 获取, 如果匹配上了, 下面就 直接 return 了, 后续不再匹配,这样就导致 其他 文件里面的属性没有填充.

这里 就是决定了List 属性是不能分在多个文件里面的

	protected final void bindIndexed(ConfigurationPropertyName name, Bindable<?> target,
			AggregateElementBinder elementBinder, ResolvableType aggregateType, ResolvableType elementType,
			IndexedCollectionSupplier result) {
		for (ConfigurationPropertySource source : getContext().getSources()) {
			bindIndexed(source, name, target, elementBinder, result, aggregateType, elementType);
			if (result.wasSupplied() && result.get() != null) {
				return;
			}
		}
	}

三、 方案解决

解决方案很简单 , 就是用占位符 代替啊, 弄其他的方案完全没有必要. , 而且 官方也 说了 ,不合并是刻意这么做的,可能考虑到 List 里面还会有其他的嵌套赋值 之类的 复杂情况吧

test:
  db:
    datasources:
      - url: jdbc:mysql://localhost:3306/writedb
        username: ${test.db.datasources.write.username}
        password: ${test.db.datasources.write.password}
      - url: jdbc: mysql://localhost:3306/readdb
        username: ${test.db.datasources.read.username}
        password: ${test.db.datasources.read.password}

<!-- 在另外一个里面配置如下 -->
test:
  db:
    datasources:
      write:
        username: root
        password: root
      read:
        username: root
        password: root

在这里插入图片描述

四、 小结

最后发现,用占位符 的方法完全可以解决, 一开始还在想其他方案, 还是 代码之上是思想 , 还需要不断的 扩展自己的思维.

支付宝微信
支付宝微信
如果有帮助记得打赏哦特别需要您的打赏哦
  • 5
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

一直打铁

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

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

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

打赏作者

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

抵扣说明:

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

余额充值