当前报道:6 种方式读取 Springboot 的配置(原理+实战)

首页>滚动 > 正文
2023-06-20 09:06:24

来源:互联网

大家好,我是田螺~


(相关资料图)

从配置文件中获取属性应该是SpringBoot开发中最为常用的功能之一,但就是这么常用的功能,仍然有很多开发者在这个方面踩坑。

我整理了几种获取配置属性的方式,目的不仅是要让大家学会如何使用,更重要的是弄清配置加载、读取的底层原理,一旦出现问题可以分析出其症结所在,而不是一报错取不到属性,无头苍蝇般的重启项目,在句句卧槽中逐渐抓狂~

以下示例源码 Springboot 版本均为 2.7.6

下边我们一一过下这几种玩法和原理,看看有哪些是你没用过的!话不多说,开始搞~

一、Environment

使用 Environment 方式来获取配置属性值非常简单,只要注入Environment类调用其方法getProperty(属性key)即可,但知其然知其所以然,简单了解下它的原理,因为后续的几种获取配置的方法都和它息息相关。

@Slf4j@SpringBootTestpublicclassEnvironmentTest{@ResourceprivateEnvironmentenv;@Testpublicvoidvar1Test(){Stringvar1=env.getProperty("env101.var1");log.info("Environment配置获取{}",var1);}}

1、什么是 Environment?

Environment 是 springboot 核心的环境配置接口,它提供了简单的方法来访问应用程序属性,包括系统属性、操作系统环境变量、命令行参数、和应用程序配置文件中定义的属性等等。

2、配置初始化

Springboot 程序启动加载流程里,会执行SpringApplication.run中的prepareEnvironment()方法进行配置的初始化,那初始化过程每一步都做了什么呢?

privateConfigurableEnvironmentprepareEnvironment(SpringApplicationRunListenerslisteners,DefaultBootstrapContextbootstrapContext,ApplicationArgumentsapplicationArguments){/***1、创建ConfigurableEnvironment对象:首先调用getOrCreateEnvironment()方法获取或创建*ConfigurableEnvironment对象,该对象用于存储环境参数。如果已经存在ConfigurableEnvironment对象,则直接使用它;否则,根据用户的配置和默认配置创建一个新的。*/ConfigurableEnvironmentenvironment=getOrCreateEnvironment();/***2、解析并加载用户指定的配置文件,将其作为PropertySource添加到环境对象中。该方法默认会解析application.properties和application.yml文件,并将其添加到ConfigurableEnvironment对象中。*PropertySource或PropertySourcesPlaceholderConfigurer加载应用程序的定制化配置。*/configureEnvironment(environment,applicationArguments.getSourceArgs());//3、加载所有的系统属性,并将它们添加到ConfigurableEnvironment对象中ConfigurationPropertySources.attach(environment);//4、通知监听器环境参数已经准备就绪listeners.environmentPrepared(bootstrapContext,environment);/***5、将默认的属性源中的所有属性值移到环境对象的队列末尾,这样用户自定义的属性值就可以覆盖默认的属性值。这是为了避免用户无意中覆盖了SpringBoot所提供的默认属性。*/DefaultPropertiesPropertySource.moveToEnd(environment);Assert.state(!environment.containsProperty("spring.main.environment-prefix"),"Environmentprefixcannotbesetviaproperties.");//6、将SpringBoot应用程序的属性绑定到环境对象上,以便能够正确地读取和使用这些配置属性bindToSpringApplication(environment);//7、如果没有自定义的环境类型,则使用EnvironmentConverter类型将环境对象转换为标准的环境类型,并添加到ConfigurableEnvironment对象中。if(!this.isCustomEnvironment){EnvironmentConverterenvironmentConverter=newEnvironmentConverter(getClassLoader());environment=environmentConverter.convertEnvironmentIfNecessary(environment,deduceEnvironmentClass());}//8、再次加载系统配置,以防止被其他配置覆盖ConfigurationPropertySources.attach(environment);returnenvironment;}

看看它的配置加载流程步骤:

创建 环境对象ConfigurableEnvironment 用于存储环境参数;

configureEnvironment 方法加载默认的 application.properties 和 application.yml 配置文件;以及用户指定的配置文件,将其封装为 PropertySource添加到环境对象中;

attach(): 加载所有的系统属性,并将它们添加到环境对象中;

listeners.environmentPrepared(): 发送环境参数配置已经准备就绪的监听通知;

moveToEnd(): 将 系统默认的属性源中的所有属性值移到环境对象的队列末尾,这样用户自定义的属性值就可以覆盖默认的属性值。

bindToSpringApplication: 应用程序的属性绑定到 Bean 对象上;

attach(): 再次加载系统配置,以防止被其他配置覆盖;

上边的配置加载流程中,各种配置属性会封装成一个个抽象的数据结构 PropertySource中,这个数据结构代码格式如下,key-value形式。

publicabstractclassPropertySource{protectedfinalStringname;//属性源名称protectedfinalTsource;//属性源值(一个泛型,比如Map,Property)publicStringgetName();//获取属性源的名字publicTgetSource();//获取属性源值publicbooleancontainsProperty(Stringname);//是否包含某个属性publicabstractObjectgetProperty(Stringname);//得到属性名对应的属性值}

PropertySource 有诸多的实现类用于管理应用程序的配置属性。不同的 PropertySource 实现类可以从不同的来源获取配置属性,例如文件、环境变量、命令行参数等。其中涉及到的一些实现类有:

关系图

MapPropertySource: Map 键值对的对象转换为 PropertySource 对象的适配器;

PropertiesPropertySource: Properties 对象中的所有配置属性转换为 Spring 环境中的属性值;

ResourcePropertySource: 从文件系统或者 classpath 中加载配置属性,封装成 PropertySource对象;

ServletConfigPropertySource: Servlet 配置中读取配置属性,封装成 PropertySource 对象;

ServletContextPropertySource: Servlet 上下文中读取配置属性,封装成 PropertySource 对象;

StubPropertySource: 是个空的实现类,它的作用仅仅是给 CompositePropertySource 类作为默认的父级属性源,以避免空指针异常;

CompositePropertySource: 是个复合型的实现类,内部维护了 PropertySource集合队列,可以将多个 PropertySource 对象合并;

SystemEnvironmentPropertySource: 操作系统环境变量中读取配置属性,封装成 PropertySource 对象;

上边各类配置初始化生成的 PropertySource 对象会被维护到集合队列中。

List>sources=newArrayList>()

配置初始化完毕,应用程序上下文AbstractApplicationContext会加载配置,这样程序在运行时就可以随时获取配置信息了。

privatevoidprepareContext(DefaultBootstrapContextbootstrapContext,ConfigurableApplicationContextcontext,ConfigurableEnvironmentenvironment,SpringApplicationRunListenerslisteners,ApplicationArgumentsapplicationArguments,BannerprintedBanner){//应用上下文加载环境对象context.setEnvironment(environment);postProcessApplicationContext(context);.........}

3、读取配置

看明白上边配置加载的流程,其实读取配置就容易理解了,无非就是遍历队列里的PropertySource,拿属性名称name匹配对应的属性值source。

PropertyResolver是获取配置的关键类,其内部提供了操作PropertySource 队列的方法,核心方法getProperty(key)获取配置值,看了下这个类的依赖关系,发现 Environment 是它子类。

那么直接用 PropertyResolver 来获取配置属性其实也是可以的,到这我们就大致明白了 Springboot 配置的加载和读取了。

@Slf4j@SpringBootTestpublicclassEnvironmentTest{@ResourceprivatePropertyResolverenv;@Testpublicvoidvar1Test(){Stringvar1=env.getProperty("env101.var1");log.info("Environment配置获取{}",var1);}}

二、@Value 注解

@Value注解是Spring框架提供的用于注入配置属性值的注解,它可用于类的成员变量、方法参数和构造函数参数上,这个记住很重要!

在应用程序启动时,使用 @Value 注解的 Bean 会被实例化。所有使用了 @Value 注解的 Bean 会被加入到 PropertySourcesPlaceholderConfigurer 的后置处理器集合中。

当后置处理器开始执行时,它会读取 Bean 中所有 @Value 注解所标注的值,并通过反射将解析后的属性值赋值给标有 @Value 注解的成员变量、方法参数和构造函数参数。

需要注意,在使用 @Value 注解时需要确保注入的属性值已经加载到 Spring 容器中,否则会导致注入失败。

如何使用

在src/main/resources目录下的application.yml配置文件中添加env101.var1属性。

env101:var1:var1-公众号:程序员小富

只要在变量上加注解 @Value("${env101.var1}")就可以了,@Value 注解会自动将配置文件中的env101.var1属性值注入到var1字段中,跑个单元测试看一下结果。

@Slf4j@SpringBootTestpublicclassEnvVariablesTest{@Value("${env101.var1}")privateStringvar1;@Testpublicvoidvar1Test(){log.info("配置文件属性:{}",var1);}}

毫无悬念,成功拿到配置数据。

虽然@Value注解方式使用起来很简单,如果使用不当还会遇到不少坑。

1、缺失配置

如果在代码中引用变量,配置文件中未进行配值,就会出现类似下图所示的错误。

为了避免此类错误导致服务启动异常,我们可以在引用变量的同时给它赋一个默认值,以确保即使在未正确配值的情况下,程序依然能够正常运行。

@Value("${env101.var1:我是小富}")privateStringvar1;

2、静态变量(static)赋值

还有一种常见的使用误区,就是将 @Value 注解加到静态变量上,这样做是无法获取属性值的。静态变量是类的属性,并不属于对象的属性,而 Spring是基于对象的属性进行依赖注入的,类在应用启动时静态变量就被初始化,此时 Bean还未被实例化,因此不可能通过 @Value 注入属性值。

@Slf4j@SpringBootTestpublicclassEnvVariablesTest{@Value("${env101.var1}")privatestaticStringvar1;@Testpublicvoidvar1Test(){log.info("配置文件属性:{}",var1);}}

即使 @Value 注解无法直接用在静态变量上,我们仍然可以通过获取已有 Bean实例化后的属性值,再将其赋值给静态变量来实现给静态变量赋值。

我们可以先通过 @Value 注解将属性值注入到普通 Bean中,然后在获取该 Bean对应的属性值,并将其赋值给静态变量。这样,就可以在静态变量中使用该属性值了。

@Slf4j@SpringBootTestpublicclassEnvVariablesTest{privatestaticStringvar3;privatestaticStringvar4;@Value("${env101.var3}")publicvoidsetVar3(Stringvar3){var3=var3;}EnvVariablesTest(@Value("${env101.var4}")Stringvar4){var4=var4;}publicstaticStringgetVar4(){returnvar4;}publicstaticStringgetVar3(){returnvar3;}}

3、常量(final)赋值

@Value 注解加到final关键字上同样也无法获取属性值,因为 final 变量必须在构造方法中进行初始化,并且一旦被赋值便不能再次更改。而 @Value 注解是在 bean 实例化之后才进行属性注入的,因此无法在构造方法中初始化 final 变量。

@Slf4j@SpringBootTestpublicclassEnvVariables2Test{privatefinalStringvar6;@AutowiredEnvVariables2Test(@Value("${env101.var6}")Stringvar6){this.var6=var6;}/***@value注解final获取*/@Testpublicvoidvar1Test(){log.info("final注入:{}",var6);}}

4、非注册的类中使用

只有标注了@Component、@Service、@Controller、@Repository 或 @Configuration 等容器管理注解的类,由 Spring 管理的 bean 中使用 @Value注解才会生效。而对于普通的POJO类,则无法使用 @Value注解进行属性注入。

/***@value注解非注册的类中使用*`@Component`、`@Service`、`@Controller`、`@Repository`或`@Configuration`等*容器管理注解的类中使用@Value注解才会生效*/@Data@Slf4j@ComponentpublicclassTestService{@Value("${env101.var7}")privateStringvar7;publicStringgetVar7(){returnthis.var7;}}

5、引用方式不对

如果我们想要获取 TestService 类中的某个变量的属性值,需要使用依赖注入的方式,而不能使用 new 的方式。通过依赖注入的方式创建 TestService 对象,Spring 会在创建对象时将对象所需的属性值注入到其中。

/***@value注解引用方式不对*/@Testpublicvoidvar7_1Test(){TestServicetestService=newTestService();log.info("引用方式不对注入:{}",testService.getVar7());}

最后总结一下 @Value注解要在 Bean的生命周期内使用才能生效。

三、@ConfigurationProperties 注解

@ConfigurationProperties注解是 SpringBoot 提供的一种更加便捷来处理配置文件中的属性值的方式,可以通过自动绑定和类型转换等机制,将指定前缀的属性集合自动绑定到一个Bean对象上。

加载原理

在 Springboot 启动流程加载配置的 prepareEnvironment() 方法中,有一个重要的步骤方法 bindToSpringApplication(environment),它的作用是将配置文件中的属性值绑定到被 @ConfigurationProperties 注解标记的 Bean对象中。但此时这些对象还没有被 Spring 容器管理,因此无法完成属性的自动注入。

那么这些Bean对象又是什么时候被注册到 Spring 容器中的呢?

这就涉及到了 ConfigurationPropertiesBindingPostProcessor 类,它是 Bean后置处理器,负责扫描容器中所有被 @ConfigurationProperties 注解所标记的 Bean对象。如果找到了,则会使用 Binder 组件将外部属性的值绑定到它们身上,从而实现自动注入。

bindToSpringApplication 主要是将属性值绑定到 Bean 对象中;

ConfigurationPropertiesBindingPostProcessor 负责在 Spring 容器启动时将被注解标记的 Bean 对象注册到容器中,并完成后续的属性注入操作;

如何使用

演示使用 @ConfigurationProperties 注解,在 application.yml 配置文件中添加配置项:

env101:var1:var1-公众号:程序员小富var2:var2-公众号:程序员小富

创建一个 MyConf 类用于承载所有前缀为env101的配置属性。

@Data@Configuration@ConfigurationProperties(prefix="env101")publicclassMyConf{privateStringvar1;privateStringvar2;}

在需要使用var1、var2属性值的地方,将 MyConf 对象注入到依赖对象中即可。

@Slf4j@SpringBootTestpublicclassConfTest{@ResourceprivateMyConfmyConf;@TestpublicvoidmyConfTest(){log.info("@ConfigurationProperties注解配置获取{}",JSON.toJSONString(myConf));}}

四、@PropertySources 注解

除了系统默认的 application.yml 或者 application.properties 文件外,我们还可能需要使用自定义的配置文件来实现更加灵活和个性化的配置。与默认的配置文件不同的是,自定义的配置文件无法被应用自动加载,需要我们手动指定加载。

@PropertySources 注解的实现原理相对简单,应用程序启动时扫描所有被该注解标注的类,获取到注解中指定自定义配置文件的路径,将指定路径下的配置文件内容加载到 Environment 中,这样可以通过 @Value 注解或 Environment.getProperty() 方法来获取其中定义的属性值了。

如何使用

在 src/main/resources/ 目录下创建自定义配置文件 xiaofu.properties,增加两个属性。

env101.var9=var9-程序员小富env101.var10=var10-程序员小富

在需要使用自定义配置文件的类上添加 @PropertySources 注解,注解 value属性中指定自定义配置文件的路径,可以指定多个路径,用逗号隔开。

@Data@Configuration@PropertySources({@PropertySource(value="classpath:xiaofu.properties",encoding="utf-8"),@PropertySource(value="classpath:xiaofu.properties",encoding="utf-8")})publicclassPropertySourcesConf{@Value("${env101.var10}")privateStringvar10;@Value("${env101.var9}")privateStringvar9;}

成功获取配置了

但是当我试图加载.yaml文件时,启动项目居然报错了,经过一番摸索我发现,@PropertySources 注解只内置了PropertySourceFactory适配器。也就是说它只能加载.properties文件。

那如果我想要加载一个.yaml类型文件,则需要自行实现yaml的适配器 YamlPropertySourceFactory。

publicclassYamlPropertySourceFactoryimplementsPropertySourceFactory{@OverridepublicPropertySourcecreatePropertySource(Stringname,EncodedResourceencodedResource)throwsIOException{YamlPropertiesFactoryBeanfactory=newYamlPropertiesFactoryBean();factory.setResources(encodedResource.getResource());Propertiesproperties=factory.getObject();returnnewPropertiesPropertySource(encodedResource.getResource().getFilename(),properties);}}

而在加载配置时要显示的指定使用 YamlPropertySourceFactory适配器,这样就完成了@PropertySource注解加载 yaml 文件。

@Data@Configuration@PropertySources({@PropertySource(value="classpath:xiaofu.yaml",encoding="utf-8",factory=YamlPropertySourceFactory.class)})publicclassPropertySourcesConf2{@Value("${env101.var10}")privateStringvar10;@Value("${env101.var9}")privateStringvar9;}

五、YamlPropertiesFactoryBean 加载 YAML 文件

我们可以使用 YamlPropertiesFactoryBean 类将 YAML 配置文件中的属性值注入到 Bean 中。

@ConfigurationpublicclassMyYamlConfig{@BeanpublicstaticPropertySourcesPlaceholderConfigureryamlConfigurer(){PropertySourcesPlaceholderConfigurerconfigurer=newPropertySourcesPlaceholderConfigurer();YamlPropertiesFactoryBeanyaml=newYamlPropertiesFactoryBean();yaml.setResources(newClassPathResource("xiaofu.yml"));configurer.setProperties(Objects.requireNonNull(yaml.getObject()));returnconfigurer;}}

可以通过 @Value 注解或 Environment.getProperty() 方法来获取其中定义的属性值。

@Slf4j@SpringBootTestpublicclassYamlTest{@Value("${env101.var11}")privateStringvar11;@TestpublicvoidmyYamlTest(){log.info("Yaml配置获取{}",var11);}}

六、自定义读取

如果上边的几种读取配置的方式你都不喜欢,就想自己写个更流批的轮子,那也很好办。我们直接注入PropertySources获取所有属性的配置队列,你是想用注解实现还是其他什么方式,就可以为所欲为了。

@Slf4j@SpringBootTestpublicclassCustomTest{@AutowiredprivatePropertySourcespropertySources;@TestpublicvoidcustomTest(){for(PropertySourcepropertySource:propertySources){log.info("自定义获取配置获取name{},{}",propertySource.getName(),propertySource.getSource());}}}

总结

我们可以通过 @Value 注解、Environment 类、@ConfigurationProperties 注解、@PropertySource 注解等方式来获取配置信息。

其中,@Value 注解适用于单个值的注入,而其他几种方式适用于批量配置的注入。不同的方式在效率、灵活性、易用性等方面存在差异,在选择配置获取方式时,还需要考虑个人编程习惯和业务需求。

如果重视代码的可读性和可维护性,则可以选择使用 @ConfigurationProperties 注解;如果更注重运行效率,则可以选择使用 Environment 类。总之,不同的场景需要选择不同的方式,以达到最优的效果。

标签:

THE END
免责声明:本文系转载,版权归原作者所有;旨在传递信息,不代表热讯制鞋网的观点和立场。

相关热点

新华社电 上海市文化和旅游局近日发布《上海市密室剧本杀内容备案管理规定(征求意见稿)》,并截至12月8日面向社会公众广泛征求意见。这
2021-11-19 13:46:03
《中国证券报》17日刊发文章《备战2022 基金经理调仓换股布新局》。文章称,距离2021年结束仅剩一个多月,基金业绩分化明显。部分排名靠前
2021-11-19 13:46:03
交通运输部办公厅 中国人民银行办公厅 中国银行保险监督管理委员会办公厅关于进一步做好货车ETC发行服务有关工作的通知各省、自治区、直
2021-11-19 13:45:58
新华社北京11月17日电 题:从10月份市场供需积极变化看中国经济韧性新华社记者魏玉坤、丁乐读懂中国经济,一个直观的视角就是市场供需两端
2021-11-19 13:45:58
全国教育财务工作会议披露的消息称,2020年,中国国家财政性教育经费投入达4 29万亿元,占GDP总量的4 206%,我国国家财政性教育经费支出占G
2021-11-19 13:45:48
如果你也热爱“种草”,前方高能预警!让你心心念念、“浏览”忘返的网络平台,可能早已成为一块块“韭菜地”。近日,据《半月谈》报道,有...
2021-11-19 13:45:48
日前,工业和信息化部印发《“十四五”信息通信行业发展规划》(以下简称《规划》),描绘了未来5年信息通信行业的发展趋势。《规划》指出...
2021-11-19 13:45:40
本报讯(中青报·中青网记者 周围围)2021年快递业务旺季正式拉开帷幕。国家邮政局监测数据显示,仅11月1日当日,全国共揽收快递包裹5 69
2021-11-19 13:45:40
人民网曼谷11月17日电 (记者赵益普)17日上午,中国援柬埔寨第七批200万剂科兴新冠疫苗抵达金边国际机场。当天,柬埔寨政府在机场举行了
2021-11-19 13:45:35
金坛压缩空气储能国家试验示范项目主体工程一角受访者供图依托清华大学非补燃压缩空气储能技术,金坛压缩空气储能项目申请专利百余项,建立
2021-11-19 13:45:35
视觉中国供图42亿立方米据有关部门预计,今年山西煤炭产量有望突破12亿吨,12月份山西外送电能力将超过900万千瓦,今冬明春煤层气产量将达4
2021-11-19 13:44:34
14省份相继发布2021年企业工资指导线——引导企业合理提高职工工资今年以来,天津、新疆、内蒙古、陕西、西藏、山东、江西、山西、福建、四
2021-11-19 13:44:34
中新网客户端北京11月18日电 (记者 谢艺观)“一条路海角天涯,两颗心相依相伴,风吹不走誓言,雨打不湿浪漫,意济苍生苦与痛,情牵天下喜
2021-11-19 13:44:31
近日,交通运输部等三部门发布《关于进一步做好货车ETC发行服务有关工作的通知》。通知提到,对不具备授信条件的用户,商业银行可在依法合
2021-11-19 13:44:31
欧莱雅面膜陷优惠“年度最大”风波 涉及该事件集体投诉超6000人次美妆大牌双十一促销翻车?近日,因预售价格比双十一现货贵出66%,欧莱雅
2021-11-19 13:44:13
43 6%受访者会在工作两三年后考虑跳槽54 3%受访者认为跳槽对个人职业发展有利有弊如今对不少年轻人来说,想对一份工作“从一而终”不太容易
2021-11-19 13:44:13
超八成受访青年表示如有机会愿意开展副业 规划能力最重要64 4%受访青年指出做副业跟风心态最要不得如今,“身兼数职”已成为年轻人当中的
2021-11-19 13:44:01
发展氢能正当其时【科学随笔】氢能是一种二次能源,它通过一定的方法利用其他能源制取,具有清洁无污染、可储存、与多种能源便捷转换等优点
2021-11-19 13:44:01
“千杯不醉”的解酒“神药”能信吗?专家:网红“解酒药” 其实不算药俗话说,“酒逢知己千杯少”,酒一直是国人饭桌上至关重要的存在。尽...
2021-11-19 13:43:57
最新文章

相关推荐