网站建设开发背景,国外网站如何搭建网页,免费文字logo生成器,wordpress动静分离cdn从配置文件中获取属性应该是SpringBoot开发中最为常用的功能之一#xff0c;但就是这么常用的功能#xff0c;仍然有很多开发者在这个方面踩坑。 我整理了几种获取配置属性的方式#xff0c;目的不仅是要让大家学会如何使用#xff0c;更重要的是弄清配置加载、读取的底层…从配置文件中获取属性应该是SpringBoot开发中最为常用的功能之一但就是这么常用的功能仍然有很多开发者在这个方面踩坑。我整理了几种获取配置属性的方式目的不仅是要让大家学会如何使用更重要的是弄清配置加载、读取的底层原理一旦出现问题可以分析出其症结所在而不是一报错取不到属性无头苍蝇般的重启项目在句句卧槽中逐渐抓狂以下示例源码 Springboot 版本均为 2.7.6下边我们一一过下这几种玩法和原理看看有哪些是你没用过的话不多说开始搞一、Environment使用 Environment 方式来获取配置属性值非常简单只要注入Environment类调用其方法getProperty(属性key)即可但知其然知其所以然简单了解下它的原理因为后续的几种获取配置的方法都和它息息相关。Slf4j SpringBootTest public class EnvironmentTest { Resource private Environment env; Test public void var1Test() { String var1 env.getProperty(env101.var1); log.info(Environment 配置获取 {}, var1); } }1、什么是 EnvironmentEnvironment 是 springboot 核心的环境配置接口它提供了简单的方法来访问应用程序属性包括系统属性、操作系统环境变量、命令行参数、和应用程序配置文件中定义的属性等等。2、配置初始化Springboot 程序启动加载流程里会执行SpringApplication.run中的prepareEnvironment()方法进行配置的初始化那初始化过程每一步都做了什么呢private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners, DefaultBootstrapContext bootstrapContext, ApplicationArguments applicationArguments) { /** * 1、创建 ConfigurableEnvironment 对象首先调用 getOrCreateEnvironment() 方法获取或创建 * ConfigurableEnvironment 对象该对象用于存储环境参数。如果已经存在 ConfigurableEnvironment 对象则直接使用它否则根据用户的配置和默认配置创建一个新的。 */ ConfigurableEnvironment environment 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、将默认的属性源中的所有属性值移到环境对象的队列末尾 这样用户自定义的属性值就可以覆盖默认的属性值。这是为了避免用户无意中覆盖了 Spring Boot 所提供的默认属性。 */ DefaultPropertiesPropertySource.moveToEnd(environment); Assert.state(!environment.containsProperty(spring.main.environment-prefix), Environment prefix cannot be set via properties.); // 6、将 Spring Boot 应用程序的属性绑定到环境对象上以便能够正确地读取和使用这些配置属性 bindToSpringApplication(environment); // 7、如果没有自定义的环境类型则使用 EnvironmentConverter 类型将环境对象转换为标准的环境类型并添加到 ConfigurableEnvironment 对象中。 if (!this.isCustomEnvironment) { EnvironmentConverter environmentConverter new EnvironmentConverter(getClassLoader()); environment environmentConverter.convertEnvironmentIfNecessary(environment, deduceEnvironmentClass()); } // 8、再次加载系统配置以防止被其他配置覆盖 ConfigurationPropertySources.attach(environment); return environment; }看看它的配置加载流程步骤创建环境对象ConfigurableEnvironment用于存储环境参数configureEnvironment方法加载默认的application.properties和application.yml配置文件以及用户指定的配置文件将其封装为PropertySource添加到环境对象中attach() 加载所有的系统属性并将它们添加到环境对象中listeners.environmentPrepared() 发送环境参数配置已经准备就绪的监听通知moveToEnd() 将系统默认的属性源中的所有属性值移到环境对象的队列末尾这样用户自定义的属性值就可以覆盖默认的属性值。bindToSpringApplication 应用程序的属性绑定到 Bean 对象上attach() 再次加载系统配置以防止被其他配置覆盖上边的配置加载流程中各种配置属性会封装成一个个抽象的数据结构PropertySource中这个数据结构代码格式如下key-value形式。public abstract class PropertySourceT { protected final String name; // 属性源名称 protected final T source; // 属性源值一个泛型比如MapProperty public String getName(); // 获取属性源的名字 public T getSource(); // 获取属性源值 public boolean containsProperty(String name); //是否包含某个属性 public abstract Object getProperty(String name); //得到属性名对应的属性值 }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 对象会被维护到集合队列中。ListPropertySource? sources new ArrayListPropertySource?()配置初始化完毕应用程序上下文AbstractApplicationContext会加载配置这样程序在运行时就可以随时获取配置信息了。private void prepareContext(DefaultBootstrapContext bootstrapContext, ConfigurableApplicationContext context, ConfigurableEnvironment environment, SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments, Banner printedBanner) { // 应用上下文加载环境对象 context.setEnvironment(environment); postProcessApplicationContext(context); ......... }3、读取配置看明白上边配置加载的流程其实读取配置就容易理解了无非就是遍历队列里的PropertySource拿属性名称name匹配对应的属性值source。PropertyResolver是获取配置的关键类其内部提供了操作PropertySource队列的方法核心方法getProperty(key)获取配置值看了下这个类的依赖关系发现Environment是它子类。那么直接用 PropertyResolver 来获取配置属性其实也是可以的到这我们就大致明白了 Springboot 配置的加载和读取了。Slf4j SpringBootTest public class EnvironmentTest { Resource private PropertyResolver env; Test public void var1Test() { String var1 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 SpringBootTest public class EnvVariablesTest { Value(${env101.var1}) private String var1; Test public void var1Test(){ log.info(配置文件属性: {},var1); } }毫无悬念成功拿到配置数据。虽然Value注解方式使用起来很简单如果使用不当还会遇到不少坑。1、缺失配置如果在代码中引用变量配置文件中未进行配值就会出现类似下图所示的错误。为了避免此类错误导致服务启动异常我们可以在引用变量的同时给它赋一个默认值以确保即使在未正确配值的情况下程序依然能够正常运行。Value(${env101.var1:我是小富}) private String var1;2、静态变量static赋值还有一种常见的使用误区就是将 Value 注解加到静态变量上这样做是无法获取属性值的。静态变量是类的属性并不属于对象的属性而 Spring是基于对象的属性进行依赖注入的类在应用启动时静态变量就被初始化此时 Bean还未被实例化因此不可能通过 Value 注入属性值。Slf4j SpringBootTest public class EnvVariablesTest { Value(${env101.var1}) private static String var1; Test public void var1Test(){ log.info(配置文件属性: {},var1); } }即使 Value 注解无法直接用在静态变量上我们仍然可以通过获取已有 Bean实例化后的属性值再将其赋值给静态变量来实现给静态变量赋值。我们可以先通过 Value 注解将属性值注入到普通 Bean中然后在获取该 Bean对应的属性值并将其赋值给静态变量。这样就可以在静态变量中使用该属性值了。Slf4j SpringBootTest public class EnvVariablesTest { private static String var3; private static String var4; Value(${env101.var3}) public void setVar3(String var3) { var3 var3; } EnvVariablesTest(Value(${env101.var4}) String var4){ var4 var4; } public static String getVar4() { return var4; } public static String getVar3() { return var3; } }3、常量final赋值Value 注解加到final关键字上同样也无法获取属性值因为 final 变量必须在构造方法中进行初始化并且一旦被赋值便不能再次更改。而 Value 注解是在 bean 实例化之后才进行属性注入的因此无法在构造方法中初始化 final 变量。Slf4j SpringBootTest public class EnvVariables2Test { private final String var6; Autowired EnvVariables2Test( Value(${env101.var6}) String var6) { this.var6 var6; } /** * value注解 final 获取 */ Test public void var1Test() { log.info(final 注入: {}, var6); } }4、非注册的类中使用只有标注了Component、Service、Controller、Repository或Configuration等容器管理注解的类由 Spring 管理的 bean 中使用 Value注解才会生效。而对于普通的POJO类则无法使用 Value注解进行属性注入。/** * value注解 非注册的类中使用 * Component、Service、Controller、Repository 或 Configuration 等 * 容器管理注解的类中使用 Value注解才会生效 */ Data Slf4j Component public class TestService { Value(${env101.var7}) private String var7; public String getVar7(){ return this.var7; } }5、引用方式不对如果我们想要获取 TestService 类中的某个变量的属性值需要使用依赖注入的方式而不能使用 new 的方式。通过依赖注入的方式创建 TestService 对象Spring 会在创建对象时将对象所需的属性值注入到其中。/** * value注解 引用方式不对 */ Test public void var7_1Test() { TestService testService new TestService(); 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) public class MyConf { private String var1; private String var2; }在需要使用var1、var2属性值的地方将 MyConf 对象注入到依赖对象中即可。Slf4j SpringBootTest public class ConfTest { Resource private MyConf myConf; Test public void myConfTest() { log.info(ConfigurationProperties注解 配置获取 {}, JSON.toJSONString(myConf)); } }四、PropertySources 注解除了系统默认的application.yml或者application.properties文件外我们还可能需要使用自定义的配置文件来实现更加灵活和个性化的配置。与默认的配置文件不同的是自定义的配置文件无法被应用自动加载需要我们手动指定加载。PropertySources注解的实现原理相对简单应用程序启动时扫描所有被该注解标注的类获取到注解中指定自定义配置文件的路径将指定路径下的配置文件内容加载到 Environment 中这样可以通过Value注解或Environment.getProperty()方法来获取其中定义的属性值了。如何使用在 src/main/resources/ 目录下创建自定义配置文件xiaofu.properties增加两个属性。env101.var9var9-程序员小富 env101.var10var10-程序员小富在需要使用自定义配置文件的类上添加PropertySources注解注解 value属性中指定自定义配置文件的路径可以指定多个路径用逗号隔开。Data Configuration PropertySources({ PropertySource(value classpath:xiaofu.properties,encoding utf-8), PropertySource(value classpath:xiaofu.properties,encoding utf-8) }) public class PropertySourcesConf { Value(${env101.var10}) private String var10; Value(${env101.var9}) private String var9; }成功获取配置了但是当我试图加载.yaml文件时启动项目居然报错了经过一番摸索我发现PropertySources 注解只内置了PropertySourceFactory适配器。也就是说它只能加载.properties文件。那如果我想要加载一个.yaml类型文件则需要自行实现yaml的适配器YamlPropertySourceFactory。public class YamlPropertySourceFactory implements PropertySourceFactory { Override public PropertySource? createPropertySource(String name, EncodedResource encodedResource) throws IOException { YamlPropertiesFactoryBean factory new YamlPropertiesFactoryBean(); factory.setResources(encodedResource.getResource()); Properties properties factory.getObject(); return new PropertiesPropertySource(encodedResource.getResource().getFilename(), properties); } }而在加载配置时要显示的指定使用YamlPropertySourceFactory适配器这样就完成了PropertySource注解加载 yaml 文件。Data Configuration PropertySources({ PropertySource(value classpath:xiaofu.yaml, encoding utf-8, factory YamlPropertySourceFactory.class) }) public class PropertySourcesConf2 { Value(${env101.var10}) private String var10; Value(${env101.var9}) private String var9; }五、YamlPropertiesFactoryBean 加载 YAML 文件我们可以使用YamlPropertiesFactoryBean类将 YAML 配置文件中的属性值注入到 Bean 中。Configuration public class MyYamlConfig { Bean public static PropertySourcesPlaceholderConfigurer yamlConfigurer() { PropertySourcesPlaceholderConfigurer configurer new PropertySourcesPlaceholderConfigurer(); YamlPropertiesFactoryBean yaml new YamlPropertiesFactoryBean(); yaml.setResources(new ClassPathResource(xiaofu.yml)); configurer.setProperties(Objects.requireNonNull(yaml.getObject())); return configurer; } }可以通过Value注解或Environment.getProperty()方法来获取其中定义的属性值。Slf4j SpringBootTest public class YamlTest { Value(${env101.var11}) private String var11; Test public void myYamlTest() { log.info(Yaml 配置获取 {}, var11); } }六、自定义读取如果上边的几种读取配置的方式你都不喜欢就想自己写个更流批的轮子那也很好办。我们直接注入PropertySources获取所有属性的配置队列你是想用注解实现还是其他什么方式就可以为所欲为了。Slf4j SpringBootTest public class CustomTest { Autowired private PropertySources propertySources; Test public void customTest() { for (PropertySource? propertySource : propertySources) { log.info(自定义获取 配置获取 name {} ,{}, propertySource.getName(), propertySource.getSource()); } } }总结我们可以通过Value注解、Environment类、ConfigurationProperties注解、PropertySource注解等方式来获取配置信息。其中Value 注解适用于单个值的注入而其他几种方式适用于批量配置的注入。不同的方式在效率、灵活性、易用性等方面存在差异在选择配置获取方式时还需要考虑个人编程习惯和业务需求。如果重视代码的可读性和可维护性则可以选择使用ConfigurationProperties注解如果更注重运行效率则可以选择使用Environment类。总之不同的场景需要选择不同的方式以达到最优的效果。