当前位置: 首页 > 后端技术 > Java

你所不知道的SpringBoot外部配置特性!

时间:2023-04-01 15:03:43 Java

作为一名Java程序员,相信大家都知道,我们日常的SpringBoot项目都会有一个配置文件application.properties文件。里面配置了很多参数,比如服务的端口等,这些只是默认值。在不改变配置文件中的内容的情况下,我们可以通过在部署时传递相应的参数来替换默认参数。那么问题来了,你有没有想过为什么这是可能的?为什么SpringBoot部署时传递的启动配置生效,而配置文件中的配置却没有生效?或者两者的优先级是什么?外部配置要说明上面的问题,我们需要知道SpringBoot支持哪些配置形式,以及这些配置方式的优先级长什么样。只有弄清楚这一点,才能真正解决配置优先级的问题。在SpringBoot的官方文档中,我们可以看到用我拙劣的英文翻译了这样一段描述。大概意思是:SpringBoot提供了配置文件外化的功能,让你在不同的环境下使用一样的。应用程序代码。您可以使用属性文件、YAML文件、环境变量和命令行参数外部化配置文件。属性值可以通过@Value注解直接注入bean,通过Environment抽象(环境映射)在别处访问,或者使用@ConfigurationProperties绑定到结构化对象。有哪些外部配置?上面既然说了SpringBoot对外提供配置,那么SpringBoot到底提供了哪些配置呢?还是通过官方文档,我们可以看到如下配置列表。从上图我们可以看出,SpringBoot一共内置了17个外部配置方法,这17个的优先级是从上到下排列的。在这些方法中,我们常用的有4个命令行方法,9个Java系统环境变量,10个操作系统环境变量,12到15个配置文件。通过上面的顺序,我们可以解释为什么我们通过命令行配置的参数会生效,而配置文件中的默认值会被忽略,从而达到覆盖配置的目的。在上面的PropertySource文档中也提到,SpringBoot主要是通过PropertySource机制来实现多属性源。SpringBoot的PropertySource是一种加载和解析配置属性的机制,可以从各种来源获取,比如文件、System环境变量、JVM系统属性、命令行参数等。PropertySource是Spring框架中的一个抽象接口,它定义了如何阅读财产来源。通过SpringBoot的代码我们可以看出org.springframework.core.env.PropertySource是一个抽象类,子类中有很多实现。上面我们提到的命令行PropertySource就是org.springframework.core.env.CommandLinePropertySource。整体的类图如下,涵盖了很多内容。有兴趣的朋友可以仔细研究一下。另外,在SpringBoot中,我们还可以使用@PropertySource注解来自定义加载指定的属性文件。例如,可以在应用程序的主类中添加以下注释:@SpringBootApplication@PropertySource("classpath:customer.properties")publicclassCustomerProperties{//...}这将告诉SpringBoot查找名为的类路径customer.properties文件并将其加载为属性源。然后,可以使用@Value注释将属性值注入到bean中,如下所示:@ServicepublicclassMyService{@Value("${my.property}")privateStringmyProperty;//...}其中${my.property}是从customer.properties文件中获取的属性值。如果找不到该属性,SpringBoot将使用默认值。因为是自定义属性,没有默认值,会报错,无法启动项目。具体实现是SpringBoot在启动时会自动加载并解析所有的PropertySources,包括默认的PropertySources和自定义的PropertySources。这些属性值保存在Spring环境中,可以通过Spring的Environment对象访问。当一个属性被注入到一个bean中时,Spring查找Environment对象并尝试解析该属性的值。总之,SpringBoot的PropertySource提供了一种加载和解析应用程序配置属性的简便方法,可以从多个来源获取这些属性。它通过将属性值存储在Spring环境中,使得在应用程序的不同部分使用变得容易。调试为了验证上面提到的命令行参数配置优先于配置文件,我们创建一个SpringBoot工程,在application.properties文件中配置一个参数name=JavaGeekTech,在IDEA启动窗口配置name=JAVA_JIKEJUSHU,如下所示,编写一个简单的HelloController类,并通过@Value注解注入name属性,接下来我们需要调试看看SpringBoot是如何赋值name属性的。经验证,名称将被分配为JAVA_JIKEJISHU,而不是JavaGeekTech。包com.example.demo.controller;导入org.springframework.beans.factory.annotation.Autowired;导入org.springframework.beans.factory.annotation.Value;导入org.springframework.web.bind.annotation.GetMapping;导入org.springframework.web.bind.annotation.RequestParam;导入org.springframework.web.bind.annotation.RestController;@RestControllerpublicclassHelloController{@Value("${name}")私有字符串名称;@GetMapping(value="/hello")publicStringhello(){returnhelloService.sayHello(name);}}然后我们开始调试,因为我们是基于SpringBoot的,属性赋值是在创建bean的时候,从createBean,到doCreateBean,到org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#populateBean,因为每个bean都会被处理由许多PostProcessors,属性分配的PostProcessor是org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor#postProcessProperties调用元数据org.springframework.beans.factory.annotation.AutowiredAnnota中的.injecttionBeanPostProcessor.AutowiredFieldElement#inject,再到org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.AutowiredFieldElement#resolveFieldValue,org.springframework.beans.factory.support.DefaultListableBeanFactory#resolveDependency,org.springframework.beans.factory.support.DefaultListableBeanFactory#doResolveDependency,org.springframework.beans.factory.support.AbstractBeanFactory#resolveEmbeddedValue,org.springframework.core.env.AbstractPropertyResolver#resolveRequiredPlaceholders,org.springframework.core.env.PropertySourcesPropertyResolver#getPropertyAsRawString,org.springframework.core.env.PropertyResolver#SourcesPropertygetProperty(java.lang.String,java.lang.Class,boolean)整体的调用链还是挺长的,但是只要顺着思路配合断点还是可以看出来的。在getProperty方法中,我们可以看到如下逻辑,根据key获取的值为JAVA_JIKEJISHU。继续跟踪getProperty方法,可以看到这个方法org.springframework.boot.context.properties.source.ConfigurationPropertySourcesPropertySource#findConfigurationProperty(org.springframework.boot.context.properties.source.ConfigurationPropertyName),其中getSource()有我们配置的两个属性源的数据如下。根据代码逻辑,我们也可以看到,在迭代的时候,如果找到了,就直接返回,所以结果就是JAVA_JIKEJISHU。总结一下,今天我很棒。带大家研究了一个SpringBoot的外部配置,通过一个实战案例跟踪代码调用链给大家测试了一下。虽然我们经常用到这个知识点,但是我们并没有看到底层的源码。那时候,我们并不知道这样一个功能的底层到底有多复杂。在这里还是不得不佩服SpringBoot的开发者,同时建议大家在日常开发中要多看底层源码。通过不断的看源码,可以更好的理解特性的实现原理,加强自己。能力。