0


SpringBoot源码解析(四):解析应用参数args

SpringBoot源码系列文章

SpringBoot源码解析(一):SpringApplication构造方法

SpringBoot源码解析(二):引导上下文DefaultBootstrapContext

SpringBoot源码解析(三):启动开始阶段

SpringBoot源码解析(四):解析应用参数args


目录

前言

**前文深入解析了SpringBoot启动的开始阶段,包括获取和启动应用启动监听器、事件与广播机制,以及如何通过匹配监听器实现启动过程各阶段的自定义逻辑。接下来,我们将探讨SpringBoot启动类

main函数中的参数args的作用及其解析过程

。**

**

SpringBoot版本2.7.18

SpringApplication的run方法的执行逻辑如下,本文将详细介绍

第3小节:解析应用参数

**

// SpringApplication类方法publicConfigurableApplicationContextrun(String... args){// 记录应用启动的开始时间long startTime =System.nanoTime();// 1.创建引导上下文,用于管理应用启动时的依赖和资源DefaultBootstrapContext bootstrapContext =createBootstrapContext();ConfigurableApplicationContext context =null;// 配置无头模式属性,以支持在无图形环境下运行// 将系统属性 java.awt.headless 设置为 trueconfigureHeadlessProperty();// 2.获取Spring应用启动监听器,用于在应用启动的各个阶段执行自定义逻辑SpringApplicationRunListeners listeners =getRunListeners(args);// 启动开始方法(发布开始事件、通知应用监听器ApplicationListener)
    listeners.starting(bootstrapContext,this.mainApplicationClass);try{// 3.解析应用参数ApplicationArguments applicationArguments =newDefaultApplicationArguments(args);// 4.准备应用环境,包括读取配置文件和设置环境变量ConfigurableEnvironment environment =prepareEnvironment(listeners, bootstrapContext, applicationArguments);// 配置是否忽略 BeanInfo,以加快启动速度configureIgnoreBeanInfo(environment);// 5.打印启动BannerBanner printedBanner =printBanner(environment);// 6.创建应用程序上下文
        context =createApplicationContext();// 设置应用启动的上下文,用于监控和管理启动过程
        context.setApplicationStartup(this.applicationStartup);// 7.准备应用上下文,包括加载配置、添加 Bean 等prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);// 8.刷新上下文,完成 Bean 的加载和依赖注入refreshContext(context);// 9.刷新后的一些操作,如事件发布等afterRefresh(context, applicationArguments);// 计算启动应用程序的时间,并记录日志Duration timeTakenToStartup =Duration.ofNanos(System.nanoTime()- startTime);if(this.logStartupInfo){newStartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), timeTakenToStartup);}// 10.通知监听器应用启动完成
        listeners.started(context, timeTakenToStartup);// 11.调用应用程序中的 `CommandLineRunner` 或 `ApplicationRunner`,以便执行自定义的启动逻辑callRunners(context, applicationArguments);}catch(Throwable ex){// 12.处理启动过程中发生的异常,并通知监听器handleRunFailure(context, ex, listeners);thrownewIllegalStateException(ex);}try{// 13.计算应用启动完成至准备就绪的时间,并通知监听器Duration timeTakenToReady =Duration.ofNanos(System.nanoTime()- startTime);
        listeners.ready(context, timeTakenToReady);}catch(Throwable ex){// 处理准备就绪过程中发生的异常handleRunFailure(context, ex,null);thrownewIllegalStateException(ex);}// 返回已启动并准备就绪的应用上下文return context;}

一、入口

  • 将main方法的参数args封装成一个对象DefaultApplicationArguments,以便方便地解析和访问启动参数
// 3.解析应用参数ApplicationArguments applicationArguments =newDefaultApplicationArguments(args);

二、默认应用程序参数DefaultApplicationArguments

1、功能概述

**

DefaultApplicationArguments

是SpringBoot中的一个类,用于处理启动时传入的参数。它实现了

ApplicationArguments

接口,并提供了一些便捷的方法来访问传入的

命令行参数

选项参数

。**

// 3.解析应用参数ApplicationArguments applicationArguments =newDefaultApplicationArguments(args);
  • 解析命令行参数:将 main 方法中的 args 参数解析成选项参数和非选项参数,方便应用在启动时读取外部传入的配置
  • 访问选项参数:支持以--key=value格式的选项参数,通过方法getOptionNames()getOptionValues(String name)获取特定的选项及其值
  • 访问非选项参数:对于不以--开头的参数,可以通过getNonOptionArgs()获取它们的列表

2、使用示例

  • 假设我们在命令行中运行应用,传递了一些参数
java -jar myapp.jar --server.port=8080 arg1 arg2
  • 在代码中,我们可以使用DefaultApplicationArguments来解析这些参数
publicstaticvoidmain(String[] args){DefaultApplicationArguments appArgs =newDefaultApplicationArguments(args);// 获取所有选项参数名称System.out.println("选项参数:"+ appArgs.getOptionNames());// 输出: ["server.port"]// 获取指定选项的值(所有以 `--` 开头的选项参数名称)System.out.println("server.port 值:"+ appArgs.getOptionValues("server.port"));// 输出: ["8080"]// 获取非选项参数(所有不以 `--` 开头的参数,通常用于传递无标记的参数值)System.out.println("非选项参数:"+ appArgs.getNonOptionArgs());// 输出: ["arg1", "arg2"]}

3、接口ApplicationArguments

  • ApplicationArguments是DefaultApplicationArguments类的父接口
// 提供对用于运行应用的参数的访问。publicinterfaceApplicationArguments{// 返回传递给应用程序的原始未处理参数String[]getSourceArgs();// 返回所有选项参数的名称Set<String>getOptionNames();// 返回解析的选项参数集合中是否包含具有给定名称的选项booleancontainsOption(String name);// 返回与给定名称的选项参数关联的值集合List<String>getOptionValues(String name);// 返回解析的非选项参数集合List<String>getNonOptionArgs();}
  • getOptionNames():返回所有选项参数的名称- 例如:参数是"--foo=bar --debug",则返回["foo", "debug"]
  • getOptionValues(String name):返回与给定名称的选项参数关联的值集合- 如果选项存在但没有值(例如:"--foo"),返回一个空集合- 如果选项存在且有单一值(例如:"--foo=bar"),返回一个包含一个元素的集合["bar"]- 如果选项存在且有多个值(例如:"--foo=bar --foo=baz"),返回包含每个值的集合["bar", "baz"]- 如果选项不存在,返回null
  • getNonOptionArgs():返回解析的非选项参数集合

4、实现类DefaultApplicationArguments

  • 代码很简单,对外暴露使用DefaultApplicationArguments,内部实现都在Source
// ApplicationArguments的默认实现类,用于解析应用程序启动时传入的参数。publicclassDefaultApplicationArgumentsimplementsApplicationArguments{privatefinalSource source;// 用于解析和存储参数的内部辅助类privatefinalString[] args;// 启动时传入的原始参数// 构造函数,使用传入的参数数组初始化对象publicDefaultApplicationArguments(String... args){Assert.notNull(args,"Args must not be null");// 确保传入参数不为 nullthis.source =newSource(args);// 使用内部类 Source 解析参数this.args = args;// 保存原始参数}// 获取原始未处理的参数数组@OverridepublicString[]getSourceArgs(){returnthis.args;}// 获取所有选项参数的名称集合@OverridepublicSet<String>getOptionNames(){String[] names =this.source.getPropertyNames();// 该集合不能被修改(即添加、删除元素等操作会抛出 UnsupportedOperationException 异常)returnCollections.unmodifiableSet(newHashSet<>(Arrays.asList(names)));}// 检查是否包含指定名称的选项参数@OverridepublicbooleancontainsOption(String name){returnthis.source.containsProperty(name);}// 获取指定名称的选项参数的值集合@OverridepublicList<String>getOptionValues(String name){List<String> values =this.source.getOptionValues(name);return(values !=null)?Collections.unmodifiableList(values):null;}// 获取所有非选项参数(不以 "--" 开头的参数)@OverridepublicList<String>getNonOptionArgs(){returnthis.source.getNonOptionArgs();}// 内部类,用于处理和解析命令行参数// 继承自 SimpleCommandLinePropertySource,可以获取选项参数和非选项参数privatestaticclassSourceextendsSimpleCommandLinePropertySource{// 使用参数数组初始化 SourceSource(String[] args){super(args);}// 获取所有非选项参数。@OverridepublicList<String>getNonOptionArgs(){returnsuper.getNonOptionArgs();}// 获取指定名称的选项参数的值列表@OverridepublicList<String>getOptionValues(String name){returnsuper.getOptionValues(name);}}}

四、Source

Source是DefaultApplicationArguments解析参数内部的真正实现类,类图如下,逐一分析。

在这里插入图片描述

1、属性源PropertySource

**

PropertySource

是Spring框架中的一个核心抽象类,用于表示

属性(键值对)的来源

。通过将各种配置来源(如

系统属性

环境变量

配置文件

等)封装为PropertySource对象,Spring可以提供统一的接口来读取和管理这些配置数据。**

  • 属性源名称:每个PropertySource实例都具有唯一的名称,用于区分不同的属性源
  • 属性源对象PropertySource<T>是一个泛型类,其中T代表具体的属性源类型
  • getProperty(String name):用于在属性源对象中检索具体的属性,name表示具体属性的键,返回具体属性的值
publicabstractclassPropertySource<T>{protectedfinalLog logger =LogFactory.getLog(getClass());protectedfinalString name;// 属性源的名称protectedfinalT source;// 属性源的数据源对象// 使用给定的名称和源对象创建属性源publicPropertySource(String name,T source){Assert.hasText(name,"Property source name must contain at least one character");Assert.notNull(source,"Property source must not be null");this.name = name;this.source = source;}// 使用给定的名称和一个新的Object对象作为底层源创建属性源publicPropertySource(String name){this(name,(T)newObject());}// 返回属性源名称publicStringgetName(){returnthis.name;}// 返回属性源的底层源对象。publicTgetSource(){returnthis.source;}// 判断属性源是否包含给定名称的属性(子类可以实现更高效的算法)// containsProperty和getProperty参数的name与上面定义的属性源名称的name不是一回事publicbooleancontainsProperty(String name){return(getProperty(name)!=null);}// 返回与给定名称关联的属性值,如果找不到则返回null,子类实现@NullablepublicabstractObjectgetProperty(String name);...}

2、枚举属性源EnumerablePropertySource

**

EnumerablePropertySource

继承自PropertySource,主要用于定义

getPropertyNames()

方法,可以获取属性源对象中

所有属性键的名称

。**

publicabstractclassEnumerablePropertySource<T>extendsPropertySource<T>{// 使用给定的名称和源对象创建属性源(调用父类PropertySource的构造方法)publicEnumerablePropertySource(String name,T source){super(name, source);}// 也是调用父类构造protectedEnumerablePropertySource(String name){super(name);}// 判断属性源是否包含具有给定名称的属性(重新了PropertySource的此方法)@OverridepublicbooleancontainsProperty(String name){returnObjectUtils.containsElement(getPropertyNames(), name);}// 返回所有属性的名称publicabstractString[]getPropertyNames();}

3、命令行属性源CommandLinePropertySource

**

CommandLinePropertySource

是Spring框架中用于

处理命令行参数

PropertySource

实现。它可以将应用程序启动时传入的命令行参数解析成

键值对

,便于在应用配置中使用。**

  • 命令行属性源名称默认为commandLineArgs
  • getOptionValues(String name):通过命令行属性源(即选项参数键值对)的键获取对应的值
  • getNonOptionArgs():通过命令行属性源(即键默认为nonOptionArgs的非选项参数键值对)获取对于的值
  • 例:java -jar your-app.jar --server.port=8081 --spring.profiles.active=prod arg1 arg2- 选项参数会有多个键值对,key1为server.port,key2为spring.profiles.active- 非选项参数永远只有一个键值对,所有key都是nonOptionArgs
publicabstractclassCommandLinePropertySource<T>extendsEnumerablePropertySource<T>{// CommandLinePropertySource实例的默认名称publicstaticfinalString COMMAND_LINE_PROPERTY_SOURCE_NAME ="commandLineArgs";// 表示非选项参数的属性键的默认名称publicstaticfinalString DEFAULT_NON_OPTION_ARGS_PROPERTY_NAME ="nonOptionArgs";privateString nonOptionArgsPropertyName = DEFAULT_NON_OPTION_ARGS_PROPERTY_NAME;// 创建一个新的命令行属性源,使用默认名称publicCommandLinePropertySource(T source){super(COMMAND_LINE_PROPERTY_SOURCE_NAME, source);}// 创建一个新的命令行属性源,具有给定名称publicCommandLinePropertySource(String name,T source){super(name, source);}// 可以通过set方法设置非选项参数的键的名称publicvoidsetNonOptionArgsPropertyName(String nonOptionArgsPropertyName){this.nonOptionArgsPropertyName = nonOptionArgsPropertyName;}// 首先检查指定的名称是否是特殊的“非选项参数”属性,// 如果是,则委托给抽象方法#getNonOptionArgs()// 否则,委托并返回抽象方法#containsOption(String)@OverridepublicfinalbooleancontainsProperty(String name){if(this.nonOptionArgsPropertyName.equals(name)){return!getNonOptionArgs().isEmpty();}returnthis.containsOption(name);}// 首先检查指定的名称是否是特殊的“非选项参数”属性,// 如果是,则委托给抽象方法#getNonOptionArgs(),返回用逗号隔开字符串// 否则,委托并返回抽象方法#getOptionValues(name),返回用逗号隔开字符串@Override@NullablepublicfinalStringgetProperty(String name){if(this.nonOptionArgsPropertyName.equals(name)){Collection<String> nonOptionArguments =getNonOptionArgs();if(nonOptionArguments.isEmpty()){returnnull;}else{returnStringUtils.collectionToCommaDelimitedString(nonOptionArguments);}}Collection<String> optionValues =getOptionValues(name);if(optionValues ==null){returnnull;}else{returnStringUtils.collectionToCommaDelimitedString(optionValues);}}// 返回从命令行解析的选项参数集合中是否包含具有给定名称的选项protectedabstractbooleancontainsOption(String name);// 返回与给定名称的选项参数关联的值集合@NullableprotectedabstractList<String>getOptionValues(String name);// 返回从命令行解析的非选项参数集合,永不为nullprotectedabstractList<String>getNonOptionArgs();}

4、简单命令行属性源SimpleCommandLinePropertySource

**

SimpleCommandLinePropertySource

是Spring框架中的一个类,继承自CommandLinePropertySource,用于

解析和处理命令行参数

。它设计为简单易用,通过接收一个字符串数组(即命令行参数 args),将参数分为

"选项参数"

"非选项参数"

两类。**

  • 命令行属性源对象类型为CommandLineArgs,通过new SimpleCommandLineArgsParser().parse(args)获取
publicclassSimpleCommandLinePropertySourceextendsCommandLinePropertySource<CommandLineArgs>{// 构造函数:创建一个使用默认名称commandLineArgs的SimpleCommandLinePropertySource实例的命令行属性源publicSimpleCommandLinePropertySource(String... args){super(newSimpleCommandLineArgsParser().parse(args));}// 创建指定名称命令行属性源publicSimpleCommandLinePropertySource(String name,String[] args){super(name,newSimpleCommandLineArgsParser().parse(args));}// 获取所有选项参数的名称@OverridepublicString[]getPropertyNames(){returnStringUtils.toStringArray(this.source.getOptionNames());}// 检查是否包含指定名称的选项@OverrideprotectedbooleancontainsOption(String name){returnthis.source.containsOption(name);}// 获取指定选项名称的值列表@Override@NullableprotectedList<String>getOptionValues(String name){returnthis.source.getOptionValues(name);}// 获取所有非选项参数的列表@OverrideprotectedList<String>getNonOptionArgs(){returnthis.source.getNonOptionArgs();}}

五、解析参数原理

**在上一节中,我们了解了

应用程序参数args

被解析后的

结构

存储方式

。接下来,我们回到文章开头,详细解析参数是如何被逐步解析出来的。**

// 3.解析应用参数ApplicationArguments applicationArguments =newDefaultApplicationArguments(args);
  • 根据new DefaultApplicationArguments(args)寻找解析arg的位置

在这里插入图片描述

  • 解析完arg调用父类CommandLinePropertySource的构造方法

在这里插入图片描述

1、解析方法

SimpleCommandLineArgsParser通过遍历传入的命令行参数数组,根据参数的格式,将参数解析并分为选项参数和非选项参数。

  • 选项参数解析规则- 选项参数必须以--前缀开头,例如 --name=John 或 --debug- 如果包含等号 =,= 左边的部分是选项名称,右边的部分是选项值- 如果没有等号,则视为不带值的选项- 如果获取不到选项名称(例如传入 --=value或–),抛出异常,表示参数格式无效
  • 非选项参数解析规则- 所有不以--开头的参数被视为非选项参数
classSimpleCommandLineArgsParser{publicCommandLineArgsparse(String... args){// 创建 CommandLineArgs 实例,用于存储解析结果CommandLineArgs commandLineArgs =newCommandLineArgs();for(String arg : args){// 遍历每个命令行参数if(arg.startsWith("--")){// 如果参数以 "--" 开头,则视为选项参数String optionText = arg.substring(2);// 去掉 "--" 前缀String optionName;// 选项名称String optionValue =null;// 选项值,默认为 nullint indexOfEqualsSign = optionText.indexOf('=');// 查找等号的位置if(indexOfEqualsSign >-1){// 如果找到了等号
                    optionName = optionText.substring(0, indexOfEqualsSign);// 等号前的部分为选项名称
                    optionValue = optionText.substring(indexOfEqualsSign +1);// 等号后的部分为选项值}else{
                    optionName = optionText;// 如果没有等号,整个文本为选项名称,值为 null}// 如果选项名称为空,抛出异常,例如,只输入了 "--=" 或 "--"if(optionName.isEmpty()){thrownewIllegalArgumentException("Invalid argument syntax: "+ arg);}// 将解析出的选项名称和值添加到 CommandLineArgs 对象中
                commandLineArgs.addOptionArg(optionName, optionValue);}else{// 如果参数不是选项参数,直接作为非选项参数添加到 CommandLineArgs 对象中
                commandLineArgs.addNonOptionArg(arg);}}return commandLineArgs;// 返回解析结果}}
  • 属性源对象类型CommandLineArgs
// 命令行参数的简单表示形式,分为“带选项参数”和“无选项参数”。classCommandLineArgs{// 存储带选项的参数,每个选项可以有一个或多个值privatefinalMap<String,List<String>> optionArgs =newHashMap<>();// 存储无选项的参数privatefinalList<String> nonOptionArgs =newArrayList<>();// 为指定的选项名称添加一个选项参数,并将给定的值添加到与此选项关联的值列表中(可能有零个或多个)publicvoidaddOptionArg(String optionName,@NullableString optionValue){if(!this.optionArgs.containsKey(optionName)){this.optionArgs.put(optionName,newArrayList<>());}if(optionValue !=null){this.optionArgs.get(optionName).add(optionValue);}}// 返回命令行中所有带选项的参数名称集合publicSet<String>getOptionNames(){returnCollections.unmodifiableSet(this.optionArgs.keySet());}// 判断命令行中是否包含指定名称的选项。publicbooleancontainsOption(String optionName){returnthis.optionArgs.containsKey(optionName);}// 返回与给定选项关联的值列表。// 表示null表示该选项不存在;空列表表示该选项没有关联值。@NullablepublicList<String>getOptionValues(String optionName){returnthis.optionArgs.get(optionName);}// 将给定的值添加到无选项参数列表中publicvoidaddNonOptionArg(String value){this.nonOptionArgs.add(value);}// 返回命令行中指定的无选项参数列表publicList<String>getNonOptionArgs(){returnCollections.unmodifiableList(this.nonOptionArgs);}}

2、解析参数的存储和访问

**解析方法很简单,所有内容都在SimpleCommandLineArgsParser的parse方法中完成。相比之下,

存储

访问

方式更为复杂。**


**存储位置位于属性源对象

PropertySource

中。从代码可知,args表示命令行参数,因此

属性源名称

为命令行属性源默认名称

commandLineArgs

属性源对象

为解析args后的

键值对

。**

在这里插入图片描述


**访问查询方式的底层实现就是操作CommandLineArgs中的

optionArgs(选项参数)

nonOptionArgs(非选项参数)

两个集合,但此过程经过多次跳转,最终依次通过

DefaultApplicationArguments -> DefaultApplicationArguments#Source -> SimpleCommandLinePropertySource -> CommandLineArgs

获取,其中CommandLineArgs就是是命令行属性源对象。这种设计主要是为了提供更灵活、安全的访问方式,避免直接暴露内部数据结构带来的潜在风险。**

在这里插入图片描述

在这里插入图片描述

3、实际应用

之前在SpringBoot基础(二):配置文件详解文章中有介绍过配置文件设置临时属性,这次回过头再来看,就很清晰明了了。

在这里插入图片描述
在这里插入图片描述

总结

  • 在SpringBoot启动时,启动类main函数中的args参数被解析为两类- 选项参数(如 --server.port=8080)- 非选项参数(如 arg1、arg2)
  • 对外暴露应用参数对象ApplicationArguments提供查询方法- getOptionValues(String name)方法可以获取选项参数- getNonOptionArgs() 方法则用于获取非选项参数- 这些参数在启动过程的后续阶段可供使用
标签: spring boot java 后端

本文转载自: https://blog.csdn.net/qq_35512802/article/details/143686967
版权归原作者 冬天vs不冷 所有, 如有侵权,请联系我们删除。

“SpringBoot源码解析(四):解析应用参数args”的评论:

还没有评论