解决 @ConfigurationProperties 合并多个yml 或者properties 文件里面的属性
一、 介绍
如果我们在 yml 文件里面配置了 List 类型的属性, 然后 在 配置属性的类上面 使用 @ConfigurationProperties 注解,便可以直接注入 List 属性 ,如果把属性分在多个文件 里面, Spring 是不支持的.
最后解决方案很简单, 但是也很不错, Spring 维护者 建议的最佳方案
我自己内心的想法: 一开始 看了源码之后,知道是不支持的, 便开始想办法解决, 想了好多种方法
- 继承 InitializingBean , EnvironmentAware ,重写 afterPropertiesSet() ,通过 environment.getProperties(“xxx”) 进行重新赋值
- 通过 继承 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
四、 小结
最后发现,用占位符 的方法完全可以解决, 一开始还在想其他方案, 还是 代码之上是思想 , 还需要不断的 扩展自己的思维.
支付宝 | 微信 |
---|---|
![]() | ![]() |
如果有帮助记得打赏哦 | 特别需要您的打赏哦 |