首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >配置文件@ConfigurationProperties读取List、Map参数

配置文件@ConfigurationProperties读取List、Map参数

作者头像
chengcheng222e
发布2021-11-04 16:17:20
发布2021-11-04 16:17:20
6.3K00
代码可运行
举报
文章被收录于专栏:简栈文化简栈文化
运行总次数:0
代码可运行
背景

在SpringBoot环境中,我们有“使用不完的”注解。这也是SpringBoot替代了传统的Spring项目中的xml配置的原因。在使用这些annotation的时候,我们一定要了解这些注解背后的原理以及约定。

代码语言:javascript
代码运行次数:0
运行
复制
package org.springframework.boot.context.properties;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

import org.springframework.core.annotation.AliasFor;

@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ConfigurationProperties {
  ......
}
支持的类型
List
代码语言:javascript
代码运行次数:0
运行
复制
custom.config.config1.folders[0]=/root
custom.config.config1.folders[1]=/home/user1
custom.config.config1.folders[2]=/home/user2

对应的Java实现

代码语言:javascript
代码运行次数:0
运行
复制
@ConfigurationProperties(prefix = "custom.config.config1")
public class Config1Properties{
 private List<String> folders;
 ...
}
Map
代码语言:javascript
代码运行次数:0
运行
复制
custom.config.config1.map.key1=value1
custom.config.config1.map.key2=value2
custom.config.config1.map.key3=value3
custom.config.config1.map.key4=value4
custom.config.config1.map.key5=value5

对应的Java实现

代码语言:javascript
代码运行次数:0
运行
复制
@ConfigurationProperties(prefix = "custom.config.config1")
public class Config1Properties{
 private Map<String, String> map;
 ...
}
Object
代码语言:javascript
代码运行次数:0
运行
复制
custom.config.config1.server.host=host1
custom.config.config1.server.port=22
custom.config.config1.server.username=username1
custom.config.config1.server.password=password1

对应的Java实现

代码语言:javascript
代码运行次数:0
运行
复制
@ConfigurationProperties(prefix = "custom.config.config1")
public class Config1Properties{
 private ServerProperties server;
 ...
 public static class ServerProperties {
  private String host;
  private int port;
  private String username;
  private String password;
  ...
 }
}
Object List
代码语言:javascript
代码运行次数:0
运行
复制
custom.config.config1.servers[0].host=host1
custom.config.config1.servers[0].port=22
custom.config.config1.servers[0].username=username1
custom.config.config1.servers[0].password=password1
custom.config.config1.servers[1].host=host2
custom.config.config1.servers[1].port=22
custom.config.config1.servers[1].username=username2
custom.config.config1.servers[1].password=password2

对应的Java实现

代码语言:javascript
代码运行次数:0
运行
复制
@ConfigurationProperties(prefix = "custom.config.config1")
public class Config1Properties{
 private List<ServerProperties> servers;
 ...
 public static class ServerProperties {
  private String host;
  private int port;
  private String username;
  private String password;
  ...
 }
}
Map的使用案例

比如,我们同时需要连接多个OSS(阿里对象存储),那我们就可以利用ConfigurationProperties的方式来配置多个。而且可以通过Spring的加载动态的注入到容器中去。

配置中心的配置:

代码语言:javascript
代码运行次数:0
运行
复制
# OSS1配置
oss.multi.clients.accout.accessKeyId=xxx
oss.multi.clients.accout.accessKeySecret=xxx
oss.multi.clients.accout.privateEndpoint=xxx
oss.multi.clients.accout.bucketName=bucket-b-test

# OSS2配置
oss.multi.enabled=true
oss.multi.clients.xdtrans.accessKeyId=xxx
oss.multi.clients.xdtrans.accessKeySecret=xxx
oss.multi.clients.xdtrans.privateEndpoint=xxx
oss.multi.clients.xdtrans.bucketName=bucket-a-test

对应的Java实现

代码语言:javascript
代码运行次数:0
运行
复制
@Data
@EqualsAndHashCode(callSuper = false)
@ConfigurationProperties(prefix = OssConstants.MULTI_CONFIG_PREFIX)
public class MultiOssProperties {
 private Map<String, OssProperties> clients;

 @Data
 public static class OssProperties {
  private String accessKeyId;
  private String accessKeySecret;
  private String publicEndpoint;
  private String privateEndpoint;
  private String bucketName;
  private String object;
 }

动态的定义我们需要的BeanDefinition。

代码语言:javascript
代码运行次数:0
运行
复制
public class MultiOssScannerConfigurer implements BeanDefinitionRegistryPostProcessor, InitializingBean, ApplicationContextAware, BeanNameAware {

 private ApplicationContext applicationContext;

 @Setter
 private MultiOssProperties multiOssProperties;

 @Override
 public void setBeanName(String name) {
  log.info("init bean {}", name);
 }

 @Override
 public void afterPropertiesSet() throws Exception {
  Objects.requireNonNull(this.multiOssProperties, "multiOssProperties不能为空");
  Objects.requireNonNull(this.applicationContext, "applicationContext不能为空");
 }

  // 动态的定义Bean
 @Override
 public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry beanDefinitionRegistry) throws BeansException {
  String beanSuffixName = StringUtils.capitalize(OssConstants.BEAN_SUFFIX_NAME);
    // productCodes实际与oss.multi.clients.xdtrans的xdtrans保持一致
  multiOssProperties.getClients().forEach((productCode, ossProperties) -> {
   AbstractBeanDefinition beanDefinition = BeanDefinitionBuilder.genericBeanDefinition(OssClient.class,
     () -> OssClientUtils.buildOssClient(ossProperties))
     .getRawBeanDefinition();
   beanDefinition.setInitMethodName("init");
   beanDefinition.setDestroyMethodName("shutDown");
   beanDefinitionRegistry.registerBeanDefinition(productCode + beanSuffixName, beanDefinition);
  });
 }

 @Override
 public void postProcessBeanFactory(ConfigurableListableBeanFactory configurableListableBeanFactory) throws BeansException {
 }

 @Override
 public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
  this.applicationContext = applicationContext;
 }

}

通过binder来让配置与对应的Java代码产生关系:

代码语言:javascript
代码运行次数:0
运行
复制
@Configuration
@EnableConfigurationProperties(MultiOssProperties.class)
@ConditionalOnProperty(prefix = OssConstants.MULTI_CONFIG_PREFIX, value = "enabled")
public class MultiOssAutoConfiguration {

 /**
  * 初始化多个 ossClient 自动配置
  *
  * @param environment 环境变量属性
  * @return OssClient 自动扫描注册器
  */
 @Bean
 public MultiOssScannerConfigurer multiOssScannerConfigurer(Environment environment) {
  Binder binder = Binder.get(environment);
  MultiOssProperties properties = binder.bind(OssConstants.MULTI_CONFIG_PREFIX, MultiOssProperties.class).get();
  MultiOssScannerConfigurer multiOssScannerConfigurer = new MultiOssScannerConfigurer();
  multiOssScannerConfigurer.setMultiOssProperties(properties);
  return multiOssScannerConfigurer;
 }
}

如何使用?

代码语言:javascript
代码运行次数:0
运行
复制
@Getter
@AllArgsConstructor
public enum OssTypeEnum {
  // 注意一下这里的beanName,要跟上面的postProcessBeanDefinitionRegistry保持一致
    XDtransOssClient("xdtransOssClient", "oss1"),
    DianDianOssClient("ddacctOssClient", "oss2"),
    ;

    private final String beanName;
    private final String desc;

    // 根据BeanName来Spring容器中获取即可
    public OssClient getBean() {
        return SpringContextHolder.getBean(beanName, OssClient.class);
    }
Binder是如何映射的?

通过上面的代码binder.bind(OssConstants.MULTI_CONFIG_PREFIX, MultiOssProperties.class).get();来进行bind。

代码语言:javascript
代码运行次数:0
运行
复制
protected final <T> T bind(ConfigurationPropertyName name, Bindable<T> target, BindHandler handler, Context context, boolean allowRecursiveBinding) {
  context.clearConfigurationProperty();
  try {
   target = handler.onStart(name, target, context);
   if (target == null) {
    return null;
   }
   Object bound = bindObject(name, target, handler, context,allowRecursiveBinding);
   return handleBindResult(name, target, handler, context, bound);
  } catch (Exception ex) {
   return handleBindError(name, target, handler, context, ex);
  }
}

如果我们的key是:oss.multi.clients.accout.xxx

实际上对应的是Map,那么它的引用名字就是clients。具体的key就是accout,那么对应的value就是OssProperties。

代码语言:javascript
代码运行次数:0
运行
复制
private Object bindBean(ConfigurationPropertyName name, Bindable<?> target,
  BindHandler handler, Context context, boolean allowRecursiveBinding) {
 if (containsNoDescendantOf(context.getSources(), name)
   || isUnbindableBean(name, target, context)) {
  return null;
 }
 BeanPropertyBinder propertyBinder = (propertyName, propertyTarget) -> bind(
   name.append(propertyName), propertyTarget, handler, context, false);
 Class<?> type = target.getType().resolve(Object.class);
 if (!allowRecursiveBinding && context.hasBoundBean(type)) {
  return null;
 }
 return context.withBean(type, () -> {
  Stream<?> boundBeans = BEAN_BINDERS.stream()
    .map((b) -> b.bind(name, target, context, propertyBinder));
  return boundBeans.filter(Objects::nonNull).findFirst().orElse(null);
 });
}

http://static.cyblogs.com/QQ20200422-222025@2x.jpg

具体的一个bind情况。

代码语言:javascript
代码运行次数:0
运行
复制
private static final List<BeanBinder> BEAN_BINDERS;

static {
 List<BeanBinder> binders = new ArrayList<>();
 binders.add(new JavaBeanBinder());
 BEAN_BINDERS = Collections.unmodifiableList(binders);
}

public <T> T bind(ConfigurationPropertyName name, Bindable<T> target, Context context,
  BeanPropertyBinder propertyBinder) {
 boolean hasKnownBindableProperties = hasKnownBindableProperties(name, context);
 Bean<T> bean = Bean.get(target, hasKnownBindableProperties);
 if (bean == null) {
  return null;
 }
 BeanSupplier<T> beanSupplier = bean.getSupplier(target);
 boolean bound = bind(propertyBinder, bean, beanSupplier);
 return (bound ? beanSupplier.get() : null);
}
// 返回对应的对象
public BeanSupplier<T> getSupplier(Bindable<T> target) {
 return new BeanSupplier<>(() -> {
  T instance = null;
  if (target.getValue() != null) {
   instance = target.getValue().get();
  }
  if (instance == null) {
   instance = (T) BeanUtils.instantiateClass(this.resolvedType);
  }
  return instance;
 });
}
参考地址
  • https://blog.csdn.net/sayyy/article/details/83657119

如果大家喜欢我的文章,可以关注个人订阅号。欢迎随时留言、交流。如果想加入微信群的话一起讨论的话,请加管理员简栈文化-小助手(lastpass4u),他会拉你们进群。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2020-04-23,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 简栈文化 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 背景
  • 支持的类型
    • List
    • Map
    • Object
    • Object List
  • Map的使用案例
  • Binder是如何映射的?
  • 参考地址
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档