SpringBoot外化配置源码解析:命令参数获取文件加载

本文主要是介绍SpringBoot外化配置源码解析:命令参数获取文件加载,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

命令参数的获取

命令行参数就是在启动 Spring Boot 项目时通过命令行传递的参数。比如,用如下命令来启动一个 Spring Boot 的项目。

java -jar app.jar --name=SpringBoot

那么,参数--name=SpringBoot 是如何一 步步传递到 Spring 内部的呢?这就是本节要分析的代码内容。

默认情况下,SpringApplication 会将以 上类似 name 的命令行参数(以“”开通)解析封装成一-个 PropertySource 对象 (5.2 节已经具体讲到),并将其添加到 Spring-Environment 当中,而命令行参数的优先级要高于其他配置源。

下面,我们通过代码来追踪启动过程中整个参数的获取、解析和封装过程。首先,参数是通过 SpringApplication 的 run 方法的 args 参数来传递的。

在 SpringApplication 的 run 方 法 中 , 通 过 以 下 操 作 先 将 args 封 装 于 对 象ApplicationArguments 中,然后又将封装之后的对象传递入 prepareEnvironment 方法。

public ConfigurableApplicationContext run(String... args) {
ApplicationArguments applicationArguments = new DefaultApplicat ionArgu-
ments(args);ConfigurableEnvironment environment = prepareEnvironment(listeners,
applicationArg
uments);
} catch (Throwable ex) {
}
}

在 prepareEnvironment 方法中通过 applicationArguments. getSourceArgs()获得传递的参数数组,并作为参数调用 configureEnvironment 方法,此处获得的 args 依旧是未解析的参数值,代码如下。

private ConfigurableEnvironment prepareEnvironment (
SpringApplicationRunL isteners listeners,
ApplicationArguments applicationArguments) {
configureEnvironment (environment, applicationArguments . getSourceArgs());
}
在 configureEnvironment 方法中又将参数传递给 configurePropertySources 方法。
protected void configureEnvironment (ConfigurableEnvironment environment,
String[] args) {
configurePropertySources(environment, args);
}
}

而在 configurePropertySources 方法中才对参数进行了真正的解析和封装。

protected void configurePropertySources(ConfigurableEnvironment environmeString[]
args) {
//获得环境中的属性资源信息
MutablePropertySources sources = environment . getPropertySources();
//如果默认属性配置存在,则将其放置在属性资源的最后位置
if (this. defaultProperties != null && !this . defaultProperties . isEmpty())
{
sources. addL ast (new MapPropertySource(" defaultProperties", this . default
Properties));
//如果命令行属性未被禁用且存在
if (this . addCommandL ineProperties && args.length > 0) {
String name = CommandL inePropertySource . COMMAND_ LINE_ PROPERTY_
SOURCE_
NAME;
//如果默礼属性资源中不包含该命令则将命令行属性放置在第一位
//如果包含则通过 Compos itePropertySource 进行处理
if (sources . contains(name)) {PropertySource<?> source = sources . get(name);
CompositePropertySource composite = new CompositePropertySource(nam
e);
composite . addPropertySource (new SimpleCommandL inePropertySource(
"springApplicationCommandL ineArgs", args));
composite . addPropertySource(source);
sources . replace(name, composite);
} else|
/不存在,则添加并放置在第一位
sources . addFirst(new SimpleCommandL inePropertySource(args));
}

configurePropertySources 方法在之前章节中有过讲解,下面针对命令行参数再次进行讲解和深入分析,重点介绍两个内容:参数的优先级和命令行参数的解析。

先说参数的优先级,从上面的代码注解中可以看到,configurePropertySources 方法第一步获得环境变量中存储配置信息的

sources;第二步判断默认参数是否为空,如果不为空,则将默认参数放置在 sources 的最后位置,这里已经明显反映了参数的优先级是通过顺序来体现的;第三步,如果命令参数未被禁用,且不为空,则要么将原有默认参数替换掉,要么直接放在第一位,这-一步中的替换操作也是另外一种优先级形式的体现。

顺便提一下, 在上面的代码中,addCommandL ineProperties 参数是可以进行设置的,当不允许使用命令行参数时,可以通过 SpringApplication 的
setAddCommandLineProperties方法将其设置为 false 来禁用。

命令行参数的解析用到了
SimpleCommandLinePropertySource 类,而该类的相关使用在上一节中已经详细介绍了。

通过上面一系列的代码追踪,我们了解了通过命令传递的参数是如何一步步被封装入 Spring的 Environment 当中的。下一 节,我们将分析配置文件中的参数获取。

配置文件的加载

Spring Boot 启动时默认会加载 classpath 下的 application.yml 或 application.properties 文件。配置文件的加载过程主要是利用 Spring Boot 的事件机制来完成的,也就是我们之前章节所讲到的 SpringApplicationRunL isteners 中的 environmentPrepared 方法来启动加载配置文件的事件。通过该方法发布的事件会被注册的
ConfigFileApplicationListener 监听到,从而实现资源的加载。

下面,我们通过源代码的追踪来分析这一过程。该事件同样是在 SpringApplication 的 run方法中来完成的。前半部分的调用过程与上一节命令行参数获取的方法调用一样,不同的是当执行到 prepareEnvironment 中,当执行完上一节中的 configureEnvironment 方法之后,便通过事件发布来通知监听器加载资源。

private ConfigurableEnvironment prepareEnvironment{
SpringApplicationRunL isteners listeners ,
ApplicationArguments applicationArguments) {
//获取或创建环境
ConfigurableEnvironment environment = getOrCreateEnvironment();
//配置环境,主要包括 PropertySources 和 activeProfiles 的配置
configureEnvironment( environment, applicat ionArguments . getSourceArgs());
// listener 环境准备(之前章节已经提到
listeners . environmentPrepared( environment);
}
}

该事件监听器通过
EventPublishingRunListener 的 environmentPrepared方法来发布一个 ApplicationEnvironmentPreparedEvent 事件。

public class EventPublishingRunL istener implements SpringApplicationRunList
ener ,
Ordered {
@Override
public void environmentPrepared(ConfigurableEnvironment environment) {
this. initialMulticaster. multicastEvent(new ApplicationEnvironmentPre-
paredEvent(
this. application, this.args, e
nvironment));
}

在 META-INF/spring .factories 中注册的
ConfigFileApplicationListener 会监听到对应事件,并进行相应的处理。spring.factories 中 ConfigFileApplicationListener 的注册配置如下。

# Application Listeners
org. springframework. context . ApplicationListener=\
org. springframework . boot . context . config. ConfigFileApplicationListener
在 ConfigFileApplicationListener 类中我们会看到很多与配置文件加载相关的常量。
public class ConfigFileApplicationListener
implements EnvironmentPostProcessor, SmartApplicationListener, Ordered {
//默认的加戴配置文件路径
private static final String DEFAULT_ SEARCH_ LOCATIONS =
"classpath:/,classpath:/config/ ,file:./, file:./config/";
//默认的配置文件名称private static final String DEFAULT_ NAMES = " application";
//激活配置文件的属性名
public static final String ACTIVE_ PROFILES_ PROPERTY = " spring. profiles. ac
tive";
}

我们通过这些基本的常量,已经可以看出默认加载配置文件的路径和默认的名称了。再回到刚才的事件监听,入口方法为
ConfigFileApplicationListener 的 onApplicationEvent 方法。

@Override
public void onApplicationEvent (ApplicationEvent event) {
//对应前面发布的事件,执行此业务逻辑
if (event instanceof ApplicationEnvironmentPreparedEvent) {
onApplicationEnvironmentPreparedEvent( (Applicat ionEnvironmentPrepared-
Event) event);
if (event instanceof Applicat ionPreparedEvent) {
onApplicationPreparedEvent(event);
}

上面代码中调用的
onApplicationEnvironmentPreparedEvent 方法如下,该方法会获得注册的处理器,遍历并依次调用其 postProcessEnvironment 方法。

private void onApplicat ionEnvi ronmentPreparedEvent( Applicat ionEnvironmentPre -
paredEvent event) {
List<EnvironmentPostProcessor> postProcessors = loadPostProcessors();
postProcessors . add(this);
Annotat ionAwareOrderComparator . sort (postProcessors);
//遍历并依次调用其 postProcessEnvironment 方法
for (EnvironmentPostProcessor postProcessor : postProcessors) {
postProcessor. postProcessEnvironment ( event . getEnvironment( ) ,
event . getSpringApplication());
}

其中 EnvironmentPostProcessor 接口的实现类也是在 META-INF/sprig.factories 文件中注册的。

# Environment Post 处理器配置
org. springframework. boot . env. EnvironmentPostProcessor=\
org. springframework . boot . cloud . CloudF oundryVcapEnvironmentPostProcessor, \
org. springframework. boot . env. SpringApplicationJsonEnvironmentPostProcessor,\
org . springframework . boot. env . SystemEnvironmentPropertySourceEnvironmentPostProcessor


ConfigFileApplicationListener 本身也是 EnvironmentPostProcessor 接口的实现类我们跟着ConfigFileApplicationListener 中 postProcessEnvironment 的调用链路代码一-直往下看,会发现最后在其内部类 Loader 的 load 方法中进行配置文件的加载操作。其中关于文件路径及其名称的组合代码如下。

private void load(String location, String name, Profile profile,
DocumentFilterFactory filterFactory, DocumentConsumer con
sumer) {
Set<String> processed = new HashSet<>() ;
for (PropertySourceLoader loader : this. propertySourceLoaders) {
for (String fileExtension : loader . getFileExtensions()) {
if (processed . add(fileExtension))
loadForFileExtension(loader, location + name, "." + fileExtension,
profile, filterFactory, consumer);
}

在该方法中可以看到 loadForFileExtension 的第二个参数“文件路径+名称"和第三个参数“扩展名称”的拼接组成方式。

location 默认值就是常量 DEFAULT_ SEARCH_ LOCATIONS 的值。

在 for 循环中遍历的 PropertySourceLoader 也是在 META-INF/spring.factories 中注册的,

并且在 Loader 的构造方法中通过 SpringFactoriesLoader 的 IoadFactories 方法来获得。

# PropertySource 加载器配置
org. springframework. boot . env . PropertySourceLoader=\
org. springframework . boot . env . PropertiesPropertySourceLoader, \
org. springframework . boot . env. YamlPropertySourceLoader

当查看
PropertiesPropertySourceLoader 和 YamlPropertySourceLoader 两个加载器代码,就会发现它们分别定义了所支持文件类型及其加载方法。PropertiesPropertySourceL oader支持配置文件类型的定义代码如下。

public class PropertiesPropertySourceLoader implements PropertySourceLoader
private static final String XML_ FILE_ EXTENSION =”.xml";
@Override
public String[] getFileExtensions()
}}
return new String[] { " properties", "xml"YamlPropertySourceLoader 支持配置文件类型的
定义代码如下。
public class YamlPropertySourceLoader implements PropertySourceLoader {
@Override
public String[] getFileExtensions() {
return new String[] { "yml", "yaml" }
.}
}

其中
PropertiesPropertySourceLoader 对文件的加载通过 PropertiesLoaderUtils 类( 加载xml 文件)和 OriginTrackedPropertiesL oader 类来完成,而 YamlPropertySourceLoader 对文件的加载主要通过 OriginTrackedYamIL oader 来完成。

下面以
PropertiesPropertySourceLoader 使用的 OriginTrackedPropertiesL oader 为例进行源码分析。


PropertiesPropertySourceLoader 中加载相关的代码如下。

public class PropertiesPropertySourceLoader implements PropertySourceLoader
//加载指定的配置文件
@Override
public List<PropertySource<?>> load(String name, Resource resource) throws
IOException {
//调用 load 方法进 行加载并返@Map 形式的数据
Map<String, ?> properties = loadProperties(resource);
if (properties . isEmpty()) {
return Collections. emptyList();
//对返回结果进行处理和转换
return Collections. singletonList(new OriginTrackedMapPropertySource(name,
Collections . unmodifiableMap(properties),true));
//具体加裁过程
@SuppressWarnings({ "unchecked", "rawtypes" })
private Map<String, ?> loadProperties(Resource resource) throws IOException
String filename = resource . getFilename();
//加载 xmL 格式
if (filename != null && filename . endsWith(XML_ FILE_ EXTENSION)){return (Map) PropertiesLoaderUtils. loadProperties (resource);
//加戴 properties 格式
return new OriginTrackedPropertiesLoader(resource). load();
}
}

我们一起看以上代码中 properties 格式的加载,也就是最后一行代码的业务逻辑实现。这里创 建 了
OriginTrackedPropertiesLoader 对 象 并 调 用 了 其 load 方 法 。


OriginTrackedPropertiesLoader 的构造方法非常简单,只是把 resource 预置给其成员变量Resource resource。

再来重点看 load 方法的实现,代码如下。

class OriginTrackedPropertiesLoader {
private final Resource resource;
/**
* Load {@code . properties} data and return a map of {@code String} - >
* {@Link OriginTrackedValue}.
@param expandLists if list {@code name[]=a,b,c} shortcuts should be
expanded
@return the Loaded properties
@throws IOException on read error
*/
//加戴 properties 文件的数据并返 Emap 类型
//其中 expandLists 用于指定参数为"name[]=a, b,c"的列表是否进行扩展,默 itrue
Map<String, OriginTrackedValue> load(boolean expandLists) throws IOExcept
ion {
//创建配置文件的 reader
try (CharacterReader reader = new CharacterReader(this . resource)) {
Map<String, OriginTrackedValue> result = new LinkedHashMap<>();
StringBuilder buffer = new StringBuilder();
//读取文件中的数据
while (reader .read()) {
//读取文件中的 key
String key = loadKey(buffer, reader). trim();
/可扩展列表的处理
if (expandLists && key. endsWith("[]")) {
key = key. substring(0, key . length()- 2);
int index = 0do {
OriginTrackedValue value = loadValue(buffer, reader, true);
put(result, key + "[" + (index++) + "]",value);
if (!reader. isEndofLine()){
reader . read();
}
while (!reader . isEndOfLine());
} else
//读取文件中 value 并封装为 OriginTrackedValue
OriginTrackedValue value = loadValue(buffer, reader, false);
put(result, key, value);
return result;
}
}

以上代码展示了 OriginTrackedPropertiesL oader 的 load 方法的核心功能:创建 reader 读取配置文件、获得配置文件中配置的 key、 获取配置文件中的 value、封装 key-value 到 map中并返回。

关于 loadKey、loadValue 的操作无非就是字符串按照指定格式的解析,具体实现都在该类内部,就不附上代码了。

本节以 properties 类型的配置文件为例讲解了其解析加载过程是如何进行的,其他类型的操作过程基本一致,只不过不同文件的具体解析方式有所不同。因此,关于其他类型的代码解析就不在此深入拓展了,感兴趣的读者可以继续查看这两个类的其他源码进行了解。

本文给大家讲解的内容是命令参数的获取和配置文件的加载

  1. 下篇文章给大家讲解的是基于Profile的处理实现;
  2. 觉得文章不错的朋友可以转发此文关注小编;
  3. 感谢大家的支持!

这篇关于SpringBoot外化配置源码解析:命令参数获取文件加载的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/610615

相关文章

Java图片压缩三种高效压缩方案详细解析

《Java图片压缩三种高效压缩方案详细解析》图片压缩通常涉及减少图片的尺寸缩放、调整图片的质量(针对JPEG、PNG等)、使用特定的算法来减少图片的数据量等,:本文主要介绍Java图片压缩三种高效... 目录一、基于OpenCV的智能尺寸压缩技术亮点:适用场景:二、JPEG质量参数压缩关键技术:压缩效果对比

Java调用C++动态库超详细步骤讲解(附源码)

《Java调用C++动态库超详细步骤讲解(附源码)》C语言因其高效和接近硬件的特性,时常会被用在性能要求较高或者需要直接操作硬件的场合,:本文主要介绍Java调用C++动态库的相关资料,文中通过代... 目录一、直接调用C++库第一步:动态库生成(vs2017+qt5.12.10)第二步:Java调用C++

springboot+dubbo实现时间轮算法

《springboot+dubbo实现时间轮算法》时间轮是一种高效利用线程资源进行批量化调度的算法,本文主要介绍了springboot+dubbo实现时间轮算法,文中通过示例代码介绍的非常详细,对大家... 目录前言一、参数说明二、具体实现1、HashedwheelTimer2、createWheel3、n

关于WebSocket协议状态码解析

《关于WebSocket协议状态码解析》:本文主要介绍关于WebSocket协议状态码的使用方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录WebSocket协议状态码解析1. 引言2. WebSocket协议状态码概述3. WebSocket协议状态码详解3

Java利用docx4j+Freemarker生成word文档

《Java利用docx4j+Freemarker生成word文档》这篇文章主要为大家详细介绍了Java如何利用docx4j+Freemarker生成word文档,文中的示例代码讲解详细,感兴趣的小伙伴... 目录技术方案maven依赖创建模板文件实现代码技术方案Java 1.8 + docx4j + Fr

SpringBoot首笔交易慢问题排查与优化方案

《SpringBoot首笔交易慢问题排查与优化方案》在我们的微服务项目中,遇到这样的问题:应用启动后,第一笔交易响应耗时高达4、5秒,而后续请求均能在毫秒级完成,这不仅触发监控告警,也极大影响了用户体... 目录问题背景排查步骤1. 日志分析2. 性能工具定位优化方案:提前预热各种资源1. Flowable

CSS Padding 和 Margin 区别全解析

《CSSPadding和Margin区别全解析》CSS中的padding和margin是两个非常基础且重要的属性,它们用于控制元素周围的空白区域,本文将详细介绍padding和... 目录css Padding 和 Margin 全解析1. Padding: 内边距2. Margin: 外边距3. Padd

Oracle数据库常见字段类型大全以及超详细解析

《Oracle数据库常见字段类型大全以及超详细解析》在Oracle数据库中查询特定表的字段个数通常需要使用SQL语句来完成,:本文主要介绍Oracle数据库常见字段类型大全以及超详细解析,文中通过... 目录前言一、字符类型(Character)1、CHAR:定长字符数据类型2、VARCHAR2:变长字符数

基于SpringBoot+Mybatis实现Mysql分表

《基于SpringBoot+Mybatis实现Mysql分表》这篇文章主要为大家详细介绍了基于SpringBoot+Mybatis实现Mysql分表的相关知识,文中的示例代码讲解详细,感兴趣的小伙伴可... 目录基本思路定义注解创建ThreadLocal创建拦截器业务处理基本思路1.根据创建时间字段按年进

Python获取中国节假日数据记录入JSON文件

《Python获取中国节假日数据记录入JSON文件》项目系统内置的日历应用为了提升用户体验,特别设置了在调休日期显示“休”的UI图标功能,那么问题是这些调休数据从哪里来呢?我尝试一种更为智能的方法:P... 目录节假日数据获取存入jsON文件节假日数据读取封装完整代码项目系统内置的日历应用为了提升用户体验,