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

相关文章

IDEA中配置Tomcat全过程

《IDEA中配置Tomcat全过程》文章介绍了在IDEA中配置Tomcat的六步流程,包括添加服务器、配置部署选项、设置应用服务器及启动,并提及Maven依赖可能因约定大于配置导致问题,需检查依赖版本... 目录第一步第二步第三步第四步第五步第六步总结第一步选择这个方框第二步选择+号,找到Tomca

分布式锁在Spring Boot应用中的实现过程

《分布式锁在SpringBoot应用中的实现过程》文章介绍在SpringBoot中通过自定义Lock注解、LockAspect切面和RedisLockUtils工具类实现分布式锁,确保多实例并发操作... 目录Lock注解LockASPect切面RedisLockUtils工具类总结在现代微服务架构中,分布

Java使用Thumbnailator库实现图片处理与压缩功能

《Java使用Thumbnailator库实现图片处理与压缩功能》Thumbnailator是高性能Java图像处理库,支持缩放、旋转、水印添加、裁剪及格式转换,提供易用API和性能优化,适合Web应... 目录1. 图片处理库Thumbnailator介绍2. 基本和指定大小图片缩放功能2.1 图片缩放的

Win10安装Maven与环境变量配置过程

《Win10安装Maven与环境变量配置过程》本文介绍Maven的安装与配置方法,涵盖下载、环境变量设置、本地仓库及镜像配置,指导如何在IDEA中正确配置Maven,适用于Java及其他语言项目的构建... 目录Maven 是什么?一、下载二、安装三、配置环境四、验证测试五、配置本地仓库六、配置国内镜像地址

Spring Boot集成/输出/日志级别控制/持久化开发实践

《SpringBoot集成/输出/日志级别控制/持久化开发实践》SpringBoot默认集成Logback,支持灵活日志级别配置(INFO/DEBUG等),输出包含时间戳、级别、类名等信息,并可通过... 目录一、日志概述1.1、Spring Boot日志简介1.2、日志框架与默认配置1.3、日志的核心作用

破茧 JDBC:MyBatis 在 Spring Boot 中的轻量实践指南

《破茧JDBC:MyBatis在SpringBoot中的轻量实践指南》MyBatis是持久层框架,简化JDBC开发,通过接口+XML/注解实现数据访问,动态代理生成实现类,支持增删改查及参数... 目录一、什么是 MyBATis二、 MyBatis 入门2.1、创建项目2.2、配置数据库连接字符串2.3、入

Springboot项目启动失败提示找不到dao类的解决

《Springboot项目启动失败提示找不到dao类的解决》SpringBoot启动失败,因ProductServiceImpl未正确注入ProductDao,原因:Dao未注册为Bean,解决:在启... 目录错误描述原因解决方法总结***************************APPLICA编

深度解析Spring Security 中的 SecurityFilterChain核心功能

《深度解析SpringSecurity中的SecurityFilterChain核心功能》SecurityFilterChain通过组件化配置、类型安全路径匹配、多链协同三大特性,重构了Spri... 目录Spring Security 中的SecurityFilterChain深度解析一、Security

SpringBoot多环境配置数据读取方式

《SpringBoot多环境配置数据读取方式》SpringBoot通过环境隔离机制,支持properties/yaml/yml多格式配置,结合@Value、Environment和@Configura... 目录一、多环境配置的核心思路二、3种配置文件格式详解2.1 properties格式(传统格式)1.

Apache Ignite 与 Spring Boot 集成详细指南

《ApacheIgnite与SpringBoot集成详细指南》ApacheIgnite官方指南详解如何通过SpringBootStarter扩展实现自动配置,支持厚/轻客户端模式,简化Ign... 目录 一、背景:为什么需要这个集成? 二、两种集成方式(对应两种客户端模型) 三、方式一:自动配置 Thick