当前位置: 首页 > news >正文

springboot service如何动态读取外部配置文件

在项目中, 读取外部配置文件的需求很常见

例如

  1. 这个配置文件包含些敏感内容
  2. 这个配置文件与server 环境密切相关
  3. 这个配置文件依赖于其他系统传入





静态读取外部文件

静态读取外部文件的写法有很多种
下面举1个常用的例子

例子

@Configuration
@PropertySource(value = "file:${external-custom-config1-path}", ignoreResourceNotFound = true)
public class ExternalConfig {@Bean("customConfig1")public String customConfig() {return getCustomConfig();}@Value("${external.custom.config1:not defined}")private String customConfig;private String getCustomConfig() {return this.customConfig;}
}

上面创建了1个 Configuration 的bean, 里面再定义1个 名字是customConfig1的 bean, 它就是配置项
@PropertySource 作用是引入1个配置文件, 其中配置文件的路径我们写在了application.yaml

external-custom-config1-path: /app/config/external-config.properties

实际上的文件位置在 /app/config/external-config.properties




让程序容忍读取配置文件失败

容忍配置文件不存在:
在@PropertySource 里加上 ignoreResourceNotFound = true
容忍配置项读取失败:
@Value(“${external.custom.config1:not defined}”) 这里加上:和默认值, 如果读取失败, 下面的变量就获取到默认值




如何使用这个配置项

既然我们把配置项定义为了1个bean, 那么在其他bean里直接用 @Autowire 引用就得
下面是把配置项 作为actuator/info 接口输出项的例子:

@Component
@Slf4j
public class AppVersionInfo implements InfoContributor {@Value("${pom.version}") // https://stackoverflow.com/questions/3697449/retrieve-version-from-maven-pom-xml-in-codeprivate String appVersion;@Autowiredprivate String hostname;@Autowiredprivate InfoService infoservice;@Value("${spring.datasource.url}")private String dbUrl;@Value("${spring.profiles.active}")private String appEnvProfile;@Autowiredprivate String customConfig1;@Override// https://docs.spring.io/spring-boot/docs/current/reference/html/production-ready-features.html#production-ready-endpoints-infopublic void contribute(Info.Builder builder) {log.info("AppVersionInfo: contribute ...");builder.withDetail("app", "Cloud Order Service").withDetail("appEnvProfile", appEnvProfile).withDetail("version", appVersion).withDetail("hostname",hostname).withDetail("dbUrl", dbUrl).withDetail("description", "This is a simple Spring Boot application to for cloud order...").withDetail("customConfig1", customConfig1).withDetail("SystemVariables", infoservice.getSystemVariables());}
}

注意上customConfig1 这个bean的引用
至于actuator框架的用法请参考:
利用SpringBoot Actuator 来构造/health /info 等监控接口




测试
gateman@MoreFine-S500:~/projects/coding/sql_backup$ curl 127.0.0.1:8080/actuator/info | jq .% Total    % Received % Xferd  Average Speed   Time    Time     Time  CurrentDload  Upload   Total   Spent    Left  Speed
100  2356    0  2356    0     0   553k      0 --:--:-- --:--:-- --:--:--  575k
{"app": "Cloud Order Service","appEnvProfile": "dev","version": "1.0.2","hostname": "MoreFine-S500","dbUrl": "jdbc:mysql://34.39.2.90:6033/demo_cloud_order?useUnicode=true&characterEncoding=utf-8&useSSL=false&allowPublicKeyRetrieval=true","description": "This is a simple Spring Boot application to for cloud order...","customConfig1": "value of config1",}
}gateman@MoreFine-S500:~/projects/coding/sql_backup$ cat /app/config/external-config.properties 
external.custom.config1=value of config1

能正确读取




这个方法的一些limitation
  1. 不能动态读取, 因为只会在构造bean (springboot 程序启动)时读取1个次配置文件
  2. 只support properties 类型的配置文件, 不支持yaml读取





动态读取外部文件

首先我们列出一些方案

  1. 方案1, 每次调用配置项都去读取一次配置文件
    这种方案看起来可行, 但是如果读取配置频繁的情况下, 会导致大量IO资源浪费, 否决

  2. 方案2, 利用@RefreshScope
    这个方案也是AI 推荐的方案, 但是要引入spring cloud config 的library , 利用spring cloud config bus 功能实现配置文件获取更新
    这种方法适合于spring cloud 微服务框架, 但是如果在k8s 环境下, 使用spring cloud 框架太重了。
    否决

  3. 方案3, 不再依赖于bean 来保存配置项, 构建1个定时机制去刷新配项的方法
    这个方案由于使用了定时器, 所以更新时间上会有gap, 但是几秒or 1分钟内的gap 很多场景下是可以容忍的, 下面例子就是基于这个方案

例子:

添加1个配置文件在/app/config

gateman@MoreFine-S500:/app/config$ pwd
/app/config
gateman@MoreFine-S500:/app/config$ ls
external-config2.properties  external-config.properties  external-config.yml
gateman@MoreFine-S500:/app/config$ cat external-config2.properties 
external.custom.config2=value of config2222
gateman@MoreFine-S500:/app/config$ 

构建1个DynamicExternalConfig类

@Configuration("dynamicExternalConfig")
@PropertySource(value = "file:${external-custom-config2-path}", ignoreResourceNotFound = true)
@Slf4j
public class DynamicExternalConfig {private Properties properties = new Properties();// could not add @Bean otherwise the call from AppVersionInfo will not make a refresh call@Getter@Value("${external.custom.config2:not defined}")private String customConfig;@Value("${external-custom-config2-path}")private String externalConfigPath;@Scheduled(fixedRate = 6000) // Refresh every 6 secondspublic void refreshConfig() {try {log.info("trying to refresh configuration customConfig2");FileInputStream fis = new FileInputStream(externalConfigPath);properties.clear();properties.load(fis);fis.close();customConfig = properties.getProperty("external.custom.config2", "not defined");} catch (IOException e) {log.error("failed to refresh configuration customConfig2", e);//throw new RuntimeException(e);}catch (Exception e) {log.error("failed to refresh configuration customConfig2, and it's not an IO exception", e);throw new RuntimeException(e);}}
}

值得注意的是:

  1. 虽然这个类还是基于业务考虑加上@Configuration 注解, 但是实际上不再创建child bean 项, 只是简单地让它成1个bean
  2. 简单地用1个 属性 customConfig 保存配置项
  3. 暴露Getter 方法让其他类调用这个配置项值
  4. 添加1个定时方法 @Scheduled refreshConfig
  5. 在这个方法内容忍IO Exception 但是不容忍其他Exception

但是在调用这个配置项的类中, 调用方法与本文静态读取配置的例子也有区别, 不能直接使用@Autowired 来引入 customConfig了, 必须用getter方法

@Component
@Slf4j
public class AppVersionInfo implements InfoContributor {@Value("${pom.version}") // https://stackoverflow.com/questions/3697449/retrieve-version-from-maven-pom-xml-in-codeprivate String appVersion;@Autowiredprivate String hostname;@Autowiredprivate InfoService infoservice;@Value("${spring.datasource.url}")private String dbUrl;@Value("${spring.profiles.active}")private String appEnvProfile;@Autowiredprivate String customConfig1;@Autowiredprivate DynamicExternalConfig dynamicExternalConfig;@Override// https://docs.spring.io/spring-boot/docs/current/reference/html/production-ready-features.html#production-ready-endpoints-infopublic void contribute(Info.Builder builder) {log.info("AppVersionInfo: contribute ...");builder.withDetail("app", "Cloud Order Service").withDetail("appEnvProfile", appEnvProfile).withDetail("version", appVersion).withDetail("hostname",hostname).withDetail("dbUrl", dbUrl).withDetail("description", "This is a simple Spring Boot application to for cloud order...").withDetail("customConfig1", customConfig1).withDetail("customConfig2", dynamicExternalConfig.getCustomConfig()).withDetail("SystemVariables", infoservice.getSystemVariables());}
}

注意customConfig1 和 customConfig2 的读取区别

这样, 就能简单地实现外部配置文件的动态更新读取


http://www.mrgr.cn/news/9437.html

相关文章:

  • 【小程序】小程序的eventBus
  • 状态压缩DP---最短Hamilton路径
  • [Day 63] 區塊鏈與人工智能的聯動應用:理論、技術與實踐
  • 【Remi Pi开发环境搭建】主要包括虚拟机的创建以及开发板镜像的烧录
  • 6G网络的关键技术、应用前景与挑战并存的科技征途
  • Maven-08.依赖管理-生命周期
  • STM32(七):定时器——输入捕获
  • 异常处理 || 抛出 || 捕获 || noexcept || 异常类
  • C语言第17篇
  • Android中实现WebView的秒开场景及方案
  • OSPF与BGP防环机制总结
  • flutter开发小技巧
  • ffplay源码分析(二)结构体VideoState
  • 电脑U口管理软件分享|U口管理软件哪个好?
  • 交叉编译Qt5.12.8附带编译opengl
  • 编程思想:编程范式:面向对象
  • 递归搜索与回溯专题篇一
  • 目标检测多模态大模型实践:貌似是全网唯一Shikra的部署和测试教程,内含各种踩坑以及demo代码
  • 幂等方案分析
  • chrome扩展程序本地打包