springboot加載命令行參數(shù)ApplicationArguments的實現(xiàn)
一、介紹
使用springboot開發(fā)的同學們,都一定會從配置文件application.yml中讀取配置。比如我們常常會在上傳文件的功能中,把文件的保存路徑寫在配置文件中,然后在代碼中通過@Value()注解從配置文件讀取對應(yīng)的配置,如下所示:
在配置文件中定義文件路徑
file: location: /data/files
在代碼中獲取保存路徑
@Component
public class upload {
@Value("${file.location}")
private String fileLocation; // 文件路徑/data/files
public void upload(File file) {
// 將文件保存到fileLocation中。
}
}
這種讀取配置的方式非常方便,但是有一個讓人抓狂的缺點:
在多人協(xié)作開發(fā)的情況下,同事A在配置文件中修改file.location的值為E:\\后將代碼提交到git倉庫,這時同事B把最新代碼拉下來后由于他的電腦中不存在E盤導致該功能出現(xiàn)bug,很多同學不嫌麻煩,每次拉下最新代碼后都會把這種配置重新修改以適合自己電腦的要求。
幸運的是,springboot在讀取配置參數(shù)方面為我們提供了多種方式,并且不同方式之間存在優(yōu)先級差異,如命令行配置的優(yōu)先級大于配置文件的優(yōu)先級。如下圖為springboot官方的描述

從上圖可知,命令行配置是在非單元測試環(huán)境下優(yōu)先級最高的。
在我們通過java -jar命令啟動項目時,添加額外的參數(shù),就可以解決上面提及的多人協(xié)作開發(fā)的問題了。
二、通過應(yīng)用程序參數(shù)獲取配置
當我們使用IDEA啟動springboot項目時,可以對項目的啟動設(shè)置命令行參數(shù),命令行參數(shù)的格式為--name=value 或 --name,如下所示

1. 通過bean獲取應(yīng)用程序參數(shù)
啟動項目后,我們從IOC容器中獲取命令行參數(shù)對應(yīng)的beanspringApplicationArguments,再從該bean中就可以獲取到我們在命令行中配置的參數(shù)了。
springboot悄悄替我們向IOC容器中注冊一個ApplicationArguments類型的bean,beanName為springApplicationArguments,該bean中保存著我們設(shè)置的應(yīng)用程序參數(shù)。
@SpringBootApplication
public class ArgumentApplication {
public static void main(String[] args) {
ConfigurableApplicationContext applicationContext = SpringApplication.run(ArgumentApplication.class, args);
// 獲取應(yīng)用程序參數(shù)
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,并調(diào)用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());
}
}
輸出,結(jié)果符合預期

三、源碼解讀 - 封裝應(yīng)用程序參數(shù)
springboot通過啟動類的
main()方法接收命令行中以--定義的應(yīng)用程序參數(shù),將參數(shù)按照不同類型以Map<String, List<String>>和List<String>保存并封裝到CommandLineArgs對象中,然后以name="commandLineArgs",source=CommandLineArgs對象將其封裝到Source中,而Source為ApplicationArguments內(nèi)部屬性,springboot將ApplicationArguments注入IOC容器。
從上面的例子中我們發(fā)現(xiàn),springboot把我們配置的命令行參數(shù)封裝到ApplicationArguments了,而ApplicationArguments又被springboot注冊到IOC容器中,其對應(yīng)的beanName為"springApplicationArguments",下面我們通過分析源碼來逐步解開它是如何操作的。
首先,大家在寫springboot啟動類時,有沒有注意到其中main()方法的參數(shù)String[] args,如下所示
@SpringBootApplication
public class ArgumentApplication {
public static void main(String[] args) {
SpringApplication.run(ArgumentApplication.class, args);
}
}
但這個參數(shù)想必有很多同學不知道它是干嘛用的,它的作用就是用來接收啟動命令中設(shè)置的--name=key參數(shù),比如java -jarApplication.jar --name=key ,我們可以通過斷點進行驗證

在源碼run()方法中我們追蹤args這個參數(shù)的調(diào)用鏈如下:
public ConfigurableApplicationContext run(String... args) {
// ...
SpringApplicationRunListeners listeners = getRunListeners(args);
// ...
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
// ...
}從源碼可以看出,參數(shù)args可以被用來獲取運行監(jiān)聽器 和 構(gòu)造應(yīng)用參數(shù),因此我們把注意力放在構(gòu)造應(yīng)用參數(shù)上來。
1. DefaultApplicationArguments
看一下該類的結(jié)構(gòu),從它的構(gòu)造方法我們得知,該類是把我們傳入的--應(yīng)用程序參數(shù)封裝成一個Source對象,同時也保存一份原始的args參數(shù),當我們需要獲取參數(shù)時,都是調(diào)用Source對象提供的方法獲取的,因此Source這個類尤其關(guān)鍵,我們需要弄清楚它是如何分析應(yīng)用程序參數(shù)并將其封裝到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的內(nèi)部類,上面已經(jīng)展示其具體實現(xiàn)的源碼,它的構(gòu)造函數(shù)就是把接收的應(yīng)用程序參數(shù)傳遞給父類的構(gòu)造函數(shù)。
下面我們看一下他的UML圖

由于Source的構(gòu)造函數(shù)直接把參數(shù)args交給其父類的構(gòu)造函數(shù),而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));
}
}
在這個類中,又是直接調(diào)用父類的構(gòu)造方法,且沒有自身的實現(xiàn)。但不同的,這里將我們設(shè)置的應(yīng)用程序進行轉(zhuǎn)換成CommandLineArgs對象交給父類構(gòu)造函數(shù)。
它是怎么分析我們傳入的應(yīng)用程序參數(shù)的,又將其轉(zhuǎn)換成什么樣的結(jié)構(gòu)呢?
4. SimpleCommandLineArgsParser
該類只有一個靜態(tài)方法parse(),從命名也可以看出,該類的功能就是對命令行參數(shù)提供簡單的轉(zhuǎn)換器。
class SimpleCommandLineArgsParser {
public CommandLineArgs parse(String... args) {
CommandLineArgs commandLineArgs = new CommandLineArgs();
for (String arg : args) {
// 以 -- 開頭的應(yīng)用程序參數(shù)
if (arg.startsWith("--")) {
String optionText = arg.substring(2);
String optionName;
String optionValue = null;
int indexOfEqualsSign = optionText.indexOf('=');
if (indexOfEqualsSign > -1) {
// --key=value這種形式的參數(shù)
optionName = optionText.substring(0, indexOfEqualsSign);
optionValue = optionText.substring(indexOfEqualsSign + 1);
}
else {
// --key這種形式的參數(shù)
optionName = optionText;
}
if (optionName.isEmpty()) {
throw new IllegalArgumentException("Invalid argument syntax: " + arg);
}
commandLineArgs.addOptionArg(optionName, optionValue);
}
else {
// 不以 -- 開頭的應(yīng)用程序參數(shù)
commandLineArgs.addNonOptionArg(arg);
}
}
return commandLineArgs;
}
}
從源碼得知,應(yīng)用程序參數(shù)的轉(zhuǎn)換過程非常簡單,就是根據(jù)-- 和 =進行字符串裁剪,然后將這些參數(shù)封裝到CommandLineArgs里。而在CommandLineArgs中用不同的字段來保存不同類型的應(yīng)用程序參數(shù)。如下
class CommandLineArgs {
// 保存 --key=value 和 --key這兩種類型的應(yīng)用程序參數(shù)
private final Map<String, List<String>> optionArgs = new HashMap<>();
// 保存 key 這一種類型的應(yīng)用程序參數(shù)
private final List<String> nonOptionArgs = new ArrayList<>();
}
回到上一節(jié)SimpleCommandLinePropertySource,它的構(gòu)造函數(shù)就是將應(yīng)用程序參數(shù)轉(zhuǎn)換為CommandLineArgs然后交給父類構(gòu)造函數(shù),那下面我們看其父類CommandLinePropertySource。
5. CommandLinePropertySource
在CommandLinePropertySource中,我們主要看其構(gòu)造函數(shù)。
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);
}
}
很顯然,又是直接調(diào)用父類的構(gòu)造函數(shù),而且向其父類構(gòu)造函數(shù)傳入的是"commandLineArgs"字符串 和 CommandLineArgs對象。那我們繼續(xù),進入父類EnumerablePropertySource,然后又將這兩個參數(shù)繼續(xù)傳遞給父類PropertySource
public abstract class EnumerablePropertySource<T> extends PropertySource<T> {
public EnumerablePropertySource(String name, T source) {
super(name, source);
}
}
6. PropertySource
通過前面一系列對父類構(gòu)造函數(shù)的調(diào)用,最終將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注解獲取參數(shù)配置
在前面我們將應(yīng)用程序參數(shù)封裝到ApplicationArguments對象中后,springboot又將這些應(yīng)用程序參數(shù)添加到environment對象中,并且對已存在的配置進行覆蓋,因此與配置文件中定義的參數(shù)類似,都可以通過@Value注解獲取。
在下面的源碼中,主要表達的是應(yīng)用程序參數(shù)在各個方法調(diào)用中的傳遞,最關(guān)鍵的部分我們要看configurePropertySources()方法。該方法將應(yīng)用程序參數(shù)配置到運行環(huán)境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);
// ...
}
// 將應(yīng)用程序設(shè)置到environment對象中,與配置文件中的參數(shù)處于同一environment對象中,因此可以通過@Value注解獲取參數(shù)配置
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)) {
// 環(huán)境中已存在相同的配置,則進行覆蓋
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));
}
}
}
五、源碼解讀 - 將應(yīng)用程序參數(shù)注冊到IOC容器
在前面的章節(jié),我們通過源碼分析得出結(jié)論,springboot將應(yīng)用程序參數(shù)封裝到ApplicationArguments和運行環(huán)境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將應(yīng)用程序參數(shù)ApplicationArguments直接通過beanFactory.registerSingleton()方法手動地注冊到IOC容器中,beanName為springApplicationArguments。
六、總結(jié)
springboot將我們配置的命令行參數(shù)封裝到ApplicationArguments,并使用"springApplicationArguments"作為beanName將其注冊到IOC容器。
設(shè)置應(yīng)用程序參數(shù)時,符合要求的設(shè)置為:--key=value、--key 以及 key??梢酝ㄟ^@Value注解直接獲取應(yīng)用程序參數(shù)。可以通過@Autowired依賴注入一個ApplicationArguments實例來讀取應(yīng)用程序參數(shù)。
到此這篇關(guān)于springboot加載命令行參數(shù)ApplicationArguments的實現(xiàn)的文章就介紹到這了,更多相關(guān)springboot加載命令行參數(shù)內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
java存儲以及java對象創(chuàng)建的流程(詳解)
下面小編就為大家?guī)硪黄猨ava存儲以及java對象創(chuàng)建的流程(詳解)。小編覺得挺不錯的,現(xiàn)在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2017-05-05
利用openoffice+jodconverter-code-3.0-bate4實現(xiàn)ppt轉(zhuǎn)圖片
這篇文章主要為大家詳細介紹了利用openoffice+jodconverter-code-3.0-bate4實現(xiàn)ppt轉(zhuǎn)圖片,具有一定的參考價值,感興趣的小伙伴們可以參考一下2019-07-07
Spring Boot2.x集成JPA快速開發(fā)的示例代碼
這篇文章主要介紹了Spring Boot2.x集成JPA快速開發(fā),文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2020-05-05

