springboot加载命令行参数ApplicationArguments的实现

目录
  • 一、介绍
  • 二、通过应用程序参数获取配置
    • 1. 通过bean获取应用程序参数
    • 2. 通过@Value注解获取
  • 三、源码解读 - 封装应用程序参数
    • 1. DefaultApplicationArguments
    • 2. Source类
    • 3. SimpleCommandLinePropertySource
    • 4. SimpleCommandLineArgsParser
    • 5. CommandLinePropertySource
    • 6. PropertySource
  • 四、源码解读 - 为什么可以通过@Value注解获取参数配置
  • 五、源码解读 - 将应用程序参数注册到IOC容器
  • 六、总结

一、介绍

使用springboot开发的同学们,都一定会从配置文件application.yml中读取配置。比如我们常常会在上传文件的功能中,把文件的保存路径写在配置文件中,然后在代码中通过@Value()注解从配置文件读取对应的配置,如下所示:

在配置文件中定义文件路径

file:
  location: /data/files

在代码中获取保存路径

@Component
public class upload {
    @Value("${file.location}")
    private String fileLocation; // 文件路径/data/files

    public void upload(File file) {
        // 将文件保存到fileLocation中。
    }
}

这种读取配置的方式非常方便,但是有一个让人抓狂的缺点

多人协作开发的情况下,同事A在配置文件中修改file.location的值为E:\\后将代码提交到git仓库,这时同事B把最新代码拉下来后由于他的电脑中不存在E盘导致该功能出现bug,很多同学不嫌麻烦,每次拉下最新代码后都会把这种配置重新修改以适合自己电脑的要求。

幸运的是,springboot在读取配置参数方面为我们提供了多种方式,并且不同方式之间存在优先级差异,如命令行配置的优先级大于配置文件的优先级。如下图为springboot官方的描述

从上图可知,命令行配置是在非单元测试环境下优先级最高的。

在我们通过java -jar命令启动项目时,添加额外的参数,就可以解决上面提及的多人协作开发的问题了。

二、通过应用程序参数获取配置

当我们使用IDEA启动springboot项目时,可以对项目的启动设置命令行参数,命令行参数的格式为--name=value--name,如下所示

1. 通过bean获取应用程序参数

启动项目后,我们从IOC容器中获取命令行参数对应的beanspringApplicationArguments,再从该bean中就可以获取到我们在命令行中配置的参数了。

springboot悄悄替我们向IOC容器中注册一个ApplicationArguments类型的bean,beanName为springApplicationArguments,该bean中保存着我们设置的应用程序参数。

@SpringBootApplication
public class ArgumentApplication {

    public static void main(String[] args) {
        ConfigurableApplicationContext applicationContext = SpringApplication.run(ArgumentApplication.class, args);

        // 获取应用程序参数
        ApplicationArguments applicationArguments =(ApplicationArguments)applicationContext
            																	.getBean("springApplicationArguments");
        // 获取命令行中name的配置
        List<String> name = applicationArguments.getOptionValues("name");
        System.out.println(name);
    }
}

输出如下所示

当然,你也可以通过@Autowired的方式在类里注入ApplicationArguments实例来获取其中的配置。

2. 通过@Value注解获取

当然我们更常用的方式是通过@Value注解来获取,如下所示

新建一个ComponentA,并用@Component注解标注为springBean,然后为其定义@Value标注的成员变量name

@Component
public class ComponentA {

    @Value("${name}")
    private String name;

    public ComponentA() {
    }

    public String getName() {
        return name;
    }
}

项目启动后,从IOC容器中获取ComponentA,并调用getName()方法来验证name的值

@SpringBootApplication
public class ArgumentApplication {

    public static void main(String[] args) {
        ConfigurableApplicationContext applicationContext = SpringApplication.run(ArgumentApplication.class, args);

        // 从配置文件中获取
        ComponentA componentA = (ComponentA) applicationContext.getBean("componentA");
        System.out.println(componentA.getName());
    }
}

输出,结果符合预期

三、源码解读 - 封装应用程序参数

springboot通过启动类的main()方法接收命令行中以--定义的应用程序参数,将参数按照不同类型以Map<String, List<String>>List<String>保存并封装到CommandLineArgs对象中,然后以name="commandLineArgs",source=CommandLineArgs对象将其封装到Source中,而SourceApplicationArguments内部属性,springboot将ApplicationArguments注入IOC容器。

从上面的例子中我们发现,springboot把我们配置的命令行参数封装到ApplicationArguments了,而ApplicationArguments又被springboot注册到IOC容器中,其对应的beanName为"springApplicationArguments",下面我们通过分析源码来逐步解开它是如何操作的。

首先,大家在写springboot启动类时,有没有注意到其中main()方法的参数String[] args,如下所示

@SpringBootApplication
public class ArgumentApplication {

    public static void main(String[] args) {
        SpringApplication.run(ArgumentApplication.class, args);
    }
}

但这个参数想必有很多同学不知道它是干嘛用的,它的作用就是用来接收启动命令中设置的--name=key参数,比如java -jarApplication.jar --name=key ,我们可以通过断点进行验证

在源码run()方法中我们追踪args这个参数的调用链如下:

public ConfigurableApplicationContext run(String... args) {
    // ...
    SpringApplicationRunListeners listeners = getRunListeners(args);
	// ...
    ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
    // ...
}

从源码可以看出,参数args可以被用来获取运行监听器构造应用参数,因此我们把注意力放在构造应用参数上来。

1. DefaultApplicationArguments

看一下该类的结构,从它的构造方法我们得知,该类是把我们传入的--应用程序参数封装成一个Source对象,同时也保存一份原始的args参数,当我们需要获取参数时,都是调用Source对象提供的方法获取的,因此Source这个类尤其关键,我们需要弄清楚它是如何分析应用程序参数并将其封装到Source中的。

public class DefaultApplicationArguments implements ApplicationArguments {

	private final Source source;
	private final String[] args;

	public DefaultApplicationArguments(String... args) {
		Assert.notNull(args, "Args must not be null");
		this.source = new Source(args);
		this.args = args;
	}
	// ...
	private static class Source extends SimpleCommandLinePropertySource {

		Source(String[] args) {
			super(args);
		}
        // ...
	}
}

2. Source类

Source类是DefaultApplicationArguments的内部类,上面已经展示其具体实现的源码,它的构造函数就是把接收的应用程序参数传递给父类的构造函数。

下面我们看一下他的UML图

由于Source的构造函数直接把参数args交给其父类的构造函数,而Source本身没有多余的处理,因此我们直接进入其父类SimpleCommandLinePropertySource

3. SimpleCommandLinePropertySource

public class SimpleCommandLinePropertySource extends CommandLinePropertySource<CommandLineArgs> {

	public SimpleCommandLinePropertySource(String... args) {
		super(new SimpleCommandLineArgsParser().parse(args));
	}

	public SimpleCommandLinePropertySource(String name, String[] args) {
		super(name, new SimpleCommandLineArgsParser().parse(args));
	}
}

在这个类中,又是直接调用父类的构造方法,且没有自身的实现。但不同的,这里将我们设置的应用程序进行转换成CommandLineArgs对象交给父类构造函数。

它是怎么分析我们传入的应用程序参数的,又将其转换成什么样的结构呢?

4. SimpleCommandLineArgsParser

该类只有一个静态方法parse(),从命名也可以看出,该类的功能就是对命令行参数提供简单的转换器

class SimpleCommandLineArgsParser {

	public CommandLineArgs parse(String... args) {
		CommandLineArgs commandLineArgs = new CommandLineArgs();
		for (String arg : args) {
            // 以 -- 开头的应用程序参数
			if (arg.startsWith("--")) {
				String optionText = arg.substring(2);
				String optionName;
				String optionValue = null;
				int indexOfEqualsSign = optionText.indexOf('=');
				if (indexOfEqualsSign > -1) {
                    // --key=value这种形式的参数
					optionName = optionText.substring(0, indexOfEqualsSign);
					optionValue = optionText.substring(indexOfEqualsSign + 1);
				}
				else {
                    // --key这种形式的参数
					optionName = optionText;
				}
				if (optionName.isEmpty()) {
					throw new IllegalArgumentException("Invalid argument syntax: " + arg);
				}
				commandLineArgs.addOptionArg(optionName, optionValue);
			}
			else {
                // 不以 -- 开头的应用程序参数
				commandLineArgs.addNonOptionArg(arg);
			}
		}
		return commandLineArgs;
	}
}

从源码得知,应用程序参数的转换过程非常简单,就是根据--=进行字符串裁剪,然后将这些参数封装到CommandLineArgs里。而在CommandLineArgs中用不同的字段来保存不同类型的应用程序参数。如下

class CommandLineArgs {
	// 保存 --key=value  和 --key这两种类型的应用程序参数
	private final Map<String, List<String>> optionArgs = new HashMap<>();
    // 保存 key 这一种类型的应用程序参数
	private final List<String> nonOptionArgs = new ArrayList<>();
}

回到上一节SimpleCommandLinePropertySource,它的构造函数就是将应用程序参数转换为CommandLineArgs然后交给父类构造函数,那下面我们看其父类CommandLinePropertySource

5. CommandLinePropertySource

CommandLinePropertySource中,我们主要看其构造函数。

public abstract class CommandLinePropertySource<T> extends EnumerablePropertySource<T> {

	public static final String COMMAND_LINE_PROPERTY_SOURCE_NAME = "commandLineArgs";

	public CommandLinePropertySource(T source) {
		super(COMMAND_LINE_PROPERTY_SOURCE_NAME, source);
	}
}

很显然,又是直接调用父类的构造函数,而且向其父类构造函数传入的是"commandLineArgs"字符串 和 CommandLineArgs对象。那我们继续,进入父类EnumerablePropertySource,然后又将这两个参数继续传递给父类PropertySource

public abstract class EnumerablePropertySource<T> extends PropertySource<T> {

	public EnumerablePropertySource(String name, T source) {
		super(name, source);
	}
}

6. PropertySource

通过前面一系列对父类构造函数的调用,最终将name初始化为"commandLineArgs"字符串 ,将source初始化为 CommandLineArgs对象。

public abstract class PropertySource<T> {

	protected final String name;

	protected final T source;

	public PropertySource(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;
	}
}

四、源码解读 - 为什么可以通过@Value注解获取参数配置

在前面我们将应用程序参数封装到ApplicationArguments对象中后,springboot又将这些应用程序参数添加到environment对象中,并且对已存在的配置进行覆盖,因此与配置文件中定义的参数类似,都可以通过@Value注解获取。

在下面的源码中,主要表达的是应用程序参数在各个方法调用中的传递,最关键的部分我们要看configurePropertySources()方法。该方法将应用程序参数配置到运行环境environment

public ConfigurableApplicationContext run(String... args) {
    // ...
	ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
	ConfigurableEnvironment environment = prepareEnvironment(listeners, bootstrapContext, applicationArguments);
	// ...
}

private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners,
			DefaultBootstrapContext bootstrapContext, ApplicationArguments applicationArguments) {
    // Create and configure the environment
    ConfigurableEnvironment environment = getOrCreateEnvironment();
    configureEnvironment(environment, applicationArguments.getSourceArgs());
}

protected void configureEnvironment(ConfigurableEnvironment environment, String[] args) {
    // ...
    configurePropertySources(environment, args);
    // ...
}

// 将应用程序设置到environment对象中,与配置文件中的参数处于同一environment对象中,因此可以通过@Value注解获取参数配置
protected void configurePropertySources(ConfigurableEnvironment environment, String[] args) {
    MutablePropertySources sources = environment.getPropertySources();
    DefaultPropertiesPropertySource.ifNotEmpty(this.defaultProperties, sources::addLast);
    if (this.addCommandLineProperties && args.length > 0) {
        String name = CommandLinePropertySource.COMMAND_LINE_PROPERTY_SOURCE_NAME;
        if (sources.contains(name)) {
            // 环境中已存在相同的配置,则进行覆盖
            PropertySource<?> source = sources.get(name);
            CompositePropertySource composite = new CompositePropertySource(name);
            composite.addPropertySource(
                new SimpleCommandLinePropertySource("springApplicationCommandLineArgs", args));
            composite.addPropertySource(source);
            sources.replace(name, composite);
        }
        else {
            sources.addFirst(new SimpleCommandLinePropertySource(args));
        }
    }
}

五、源码解读 - 将应用程序参数注册到IOC容器

在前面的章节,我们通过源码分析得出结论,springboot将应用程序参数封装到ApplicationArguments和运行环境Environment中。接下来我们看它是如何注册到IOC容器的。

public ConfigurableApplicationContext run(String... args) {
    ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
    ConfigurableEnvironment environment = prepareEnvironment(listeners, bootstrapContext, applicationArguments);
    // ...
    prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);
	// ...
}

private void prepareContext(DefaultBootstrapContext bootstrapContext, ConfigurableApplicationContext context,
			ConfigurableEnvironment environment, SpringApplicationRunListeners listeners,
			ApplicationArguments applicationArguments, Banner printedBanner) {
    // ...
    ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
    beanFactory.registerSingleton("springApplicationArguments", applicationArguments);
    // ...
}

springboot将应用程序参数ApplicationArguments直接通过beanFactory.registerSingleton()方法手动地注册到IOC容器中,beanName为springApplicationArguments

六、总结

springboot将我们配置的命令行参数封装到ApplicationArguments,并使用"springApplicationArguments"作为beanName将其注册到IOC容器。

设置应用程序参数时,符合要求的设置为:--key=value--key 以及 key。可以通过@Value注解直接获取应用程序参数。可以通过@Autowired依赖注入一个ApplicationArguments实例来读取应用程序参数。

到此这篇关于springboot加载命令行参数ApplicationArguments的实现的文章就介绍到这了,更多相关springboot加载命令行参数内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • SpringBoot打Jar包在命令行运行流程详解

    首先写一个测试文件 然后点击IDEA右侧的maven,然后选择package,之后点击上面运行或者直接双击即可, 等下方控制台构建成功即可: 然后找到项目目录下target下即可看到打的jar包 然后在此目录的地址栏中直接输入:cmd 之后输入:java -jar demo2-0.0.1-SNAPSHOT.jar 回车后会出现下方即运行成功 最后在地址栏访问看一下效果 到此就结束了. 备注: 以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持我们.

  • springboot命令行启动的方法详解

    springboot命令行启动 创建的springboot项目想看看效果,不想打开idea等开发工具,使用直接使用命令行启动. maven的命令启动 需要将 jdk的bin目录和maven的bin目录添加到环境变量path中,若是没有,mvn就要用在maven的bin环境中的全路径 若是没有添加环境变量 mvn就要是E:\software\apache-maven-3.3.9\bin\mvn(安装路径\bin\mvn) java就要是C:\software\jdk\bin\java.exe(安装

  • SpringBoot java-jar命令行启动原理解析

    在spring boot里,很吸引人的一个特性是可以直接把应用打包成为一个jar/war,然后这个jar/war是可以直接启动的,而不需要另外配置一个Web Server.那么spring boot如何启动的呢?今天我们就来一起探究一下它的原理.首先我们来创建一个基本的spring boot工程来帮助我们分析,本次spring boot版本为 2.2.5.RELEASE. // SpringBootDemo.java @SpringBootApplication public class Spr

  • springboot加载命令行参数ApplicationArguments的实现

    目录 一.介绍 二.通过应用程序参数获取配置 1. 通过bean获取应用程序参数 2. 通过@Value注解获取 三.源码解读 - 封装应用程序参数 1. DefaultApplicationArguments 2. Source类 3. SimpleCommandLinePropertySource 4. SimpleCommandLineArgsParser 5. CommandLinePropertySource 6. PropertySource 四.源码解读 - 为什么可以通过@Val

  • SpringBoot加载外部依赖过程解析

    这篇文章主要介绍了SpringBoot加载外部依赖过程解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 背景 公司一个项目的大数据平台进行改造,之前使用Structured Streaming作为实时计算框架,需要替换为替换为Kafka Streams,并使用SpringBoot包装,使其可以纳入微服务体系. 然而由于之前并没有接触过SpringFramework相关技术,并且项目工期较为紧张,因此只好花了2天时间看了看Spring和Spri

  • 浅谈Python 命令行参数argparse写入图片路径操作

    什么是命令行参数? 命令行参数是在运行时给予程序/脚本的标志.它们包含我们程序的附加信息,以便它可以执行. 并非所有程序都有命令行参数,因为并非所有程序都需要它们. 为什么我们使用命令行参数? 如上所述,命令行参数在运行时为程序提供附加信息. 这允许我们在不改变代码的情况下动态地为我们的程序提供不同的输入 . 您可以绘制命令行参数类似于函数参数的类比.如果你知道如何在各种编程语言中声明和调用函数,那么当你发现如何使用命令行参数时,你会立刻感到宾至如归. 鉴于这是计算机视觉和图像处理博客,您在这里

  • 解决SpringBoot加载application.properties配置文件的坑

    SpringBoot加载application.properties配置文件的坑 事情的起因是这样的 一次,本人在现场升级程序,升级完成后进行测试,结果接口调用都报了这么个错误: 大概意思是https接口需要证书校验,这就奇怪了,项目启动加载的是包外的application.properties配置文件,配置文件里没有配置使用https啊.本人马上检查了下包内的application.properties配置文件,发现包内确实配置了https相关的配置项: 明明包外的配置文件优先级高于包内的,为

  • CommonLisp中解析命令行参数示例

    目录 clingon 子命令 选项与参数 选项名称 必要性与默认值 可多次使用的选项 信号选项 选择型选项 clingon clingon 是一个 Common Lisp 的命令行选项的解析器,它可以轻松地解析具有复杂格式的命令行选项.例如,下面的代码可以打印给定次数的打招呼信息 #!/bin/sh #|-*- mode:lisp -*-|# #| exec ros -Q -- $0 "$@" |# (progn ;;init forms (ros:ensure-asdf) #+qui

  • SpringBoot加载读取配置文件过程详细分析

    目录 配置文件的读取顺序 多坏境的配置文件 个性化配置 自定义配置文件名称和路径 加载yml文件 springboot默认读取的配置文件名字是:“application.properties”和“application.yml”,默认读取四个位置的文件:根目录下.根目录的config目录下.classpath目录下.classpath目录里的config目录下: 配置文件的读取顺序 根目录/config/application.properties 根目录/config/application.

  • 在Perl中使用Getopt::Long模块来接收用户命令行参数

    我们在linux常常用到一个程序需要加入参数,现在了解一下 perl 中的有关控制参数的模块 Getopt::Long ,比直接使用 @ARGV 的数组强大多了.我想大家知道在 Linux 中有的参数有二种形式. •长参数  –help•短参数   -h也就是-和–的分别.–表示完整参数.-表示简化参数.在 Perl 的这个模块中也支持这二种方法.这要介绍的二 Getopt 其实有二个模块,一个叫 Getopt::Long 一个叫 Getopt::Std.下面就只介绍 Getopt::Long

  • Ghost8.0详细使用方法与命令行参数

    GHOST的命令行参数: GHOST有许多有用的命令行参数,以下为常用的: 1.-rb  本次GHOST操作结束退出时自动REBOOT.一个很有用的参数,特别是在复制系统时你可以放心离开了. 2.-fx  本次GHOST操作结束退出时自动回到DOS提示符(前提是你是以DOS命令的方式启动的GHOST). 3.-sure  对所有要求确认的提示或警告一律回答"YES".此参数有一定的危险性,只建议高级用户使用,小心为妙! 4.-fro  如果源分区发现坏簇,则略过提示强制拷贝.此参数可用

  • Python 获得命令行参数的方法(推荐)

    本篇将介绍python中sys, getopt模块处理命令行参数 如果想对python脚本传参数,python中对应的argc, argv(c语言的命令行参数)是什么呢? 需要模块:sys 参数个数:len(sys.argv) 脚本名:    sys.argv[0] 参数1:     sys.argv[1] 参数2:     sys.argv[2] test.py import sys print "脚本名:", sys.argv[0] for i in range(1, len(sy

  • Python如何使用argparse模块处理命令行参数

    这篇文章主要介绍了Python如何使用argparse模块处理命令行参数,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 介绍 argparse是python3标准库里用来处理命令行参数的模块 使用三步走: 创建 ArgumentParser() 对象 调用 add_argument() 方法添加参数 使用 parse_args() 解析添加的参数 实例 我们先来创建一个python文件 vim test.py 然后开始在输入模式(i)下输入代码

随机推荐