SpringBoot2.x基础篇:谈谈SpringBoot内提供的这几种配置绑定

常见配置绑定方式

SpringBoot在不断地版本迭代中陆续提供了不同的配置参数绑定的方式,我们可以单独获取一个配置参数也可以将一系列的配置映射绑定到JavaBean的属性字段,下面我们来看看这几种方式的配置绑定哪一种是你最常用到的。

示例配置参数

1
2
3
4
system:
config:
app-id: hengboy
app-secret: yuqiyu@admin

上面是一段示例的配置参数,提供给下面的配置绑定方式来使用。

@Configuration方式绑定

当我们需要将一个配置前缀下的参数映射绑定到JavaBean的属性字段时,我们可以考虑使用@ConfigurationProperties + @Configuration注解组合的方式,使用如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/**
* 系统配置
*
* @author 恒宇少年
*/
@Configuration
@ConfigurationProperties(prefix = SYSTEM_CONFIG_PREFIX)
@Data
public class SystemConfig {
/**
* 系统配置前缀
*/
public static final String SYSTEM_CONFIG_PREFIX = "system.config";

private String appId;
private String appSecret;
}

注意事项:配置参数与JavaBean属性之间的绑定是通过调用JavaBean属性的Setter方法来赋值的,所以我们需要提供对应属性字段的Setter方法。

由于@Configuration注解被@Component修饰,所以我们在使用时只需要注入SystemConfig配置绑定映射类即可,通过Getter方法来获取对应配置参数的值。

配置扫描路径方式绑定

如果你系统中需要创建的配置映射类较多,而且每一个类都需要交付给IOC容器进行托管,那么可以考虑使用@ConfigurationPropertiesScan + @ConfigurationProperties注解组合的方式,使用如下所示:

1
2
3
4
5
6
7
8
@SpringBootApplication
@ConfigurationPropertiesScan
public class ConfigureBindingAwayApplication {

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

我们首先需要在XxxApplication应用程序启动类上添加@ConfigurationPropertiesScan注解,表示我们需要使用自动扫描的方式来注册配置映射类,注解配置参数如下所示:

  • value:配置扫描的基础package,与basePackages作用一致,通过数组的形式来接收配置。
  • basePackages:配置扫描的基础package
  • basePackageClasses:配置基础扫描类,会将每一个扫描类所处于的package作为扫描基础package

当我们在使用@ConfigurationPropertiesScan注解时,如果不进行自定义扫描路径,默认使用SpringBoot应用程序扫描的packages

使用这种方式我们配置映射类就不再需要添加@Configuration注解了,这是因为我们在使用@ConfigurationPropertiesScan注解时,会通过@Import方式来引用配置映射类的注册实现,详见:org.springframework.boot.context.properties.ConfigurationPropertiesScanRegistrar#registerBeanDefinitions,配置映射类如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/**
* 系统配置
*
* @author 恒宇少年
*/
@ConfigurationProperties(prefix = SYSTEM_CONFIG_PREFIX)
@Data
public class SystemConfig {
/**
* 系统配置前缀
*/
public static final String SYSTEM_CONFIG_PREFIX = "system.config";

private String appId;
private String appSecret;
}

构造函数方式绑定

在上面的两种方式都是通过Setter方法来进行映射字段的赋值,而构造函数绑定方式是通过构造函数来进行赋值的,我们只需要在配置映射类上添加@ConstructorBinding注解并提供对应的构造函数即可,使用方式如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/**
* 系统配置
*
* @author 恒宇少年
*/
@ConfigurationProperties(prefix = SYSTEM_CONFIG_PREFIX)
@ConstructorBinding
@Getter
public class SystemConfig {
/**
* 系统配置前缀
*/
public static final String SYSTEM_CONFIG_PREFIX = "system.config";

public SystemConfig(String appId, String appSecret) {
this.appId = appId;
this.appSecret = appSecret;
}

private String appId;
private String appSecret;
}

在之前我也写过一篇关于构造函数映射配置参数的问题,详情访问:@ConstructorBinding注解的使用

第三方类绑定

如果我们需要将配置参数映射绑定到第三方依赖内提供的JavaBean,我们该使用什么方式呢?由于接收参数的类并不是我们自己编写的,所以没有办法对.class文件源码进行修改。

这时我们可以将第三方提供的JavaBean交给IOC容器托管,然后结合@ConfigurationProperties注解来映射绑定配置参数,使用方式如下所示:

1
2
3
4
5
@Bean
@ConfigurationProperties(prefix = SYSTEM_CONFIG_PREFIX)
public SystemConfig systemConfig() {
return new SystemConfig();
}

这种方式也需要第三方提供的JavaBean有映射字段的Setter方法,否则无法进行赋值。

我们知道通过@Bean注解修饰的方法,会将方法的返回值加入到IOC容器内,那我们在使用配置时,直接注入配置映射类就可以了。

总结

上面这几种配置绑定方式都遵循OOP实现,当然如果你只需要获取一个配置参数,使用@Value也是一个好的选择,没有更好,只有更合适,根据每一种绑定方式的特点合理的选择一个合适业务的方式。

SpringBoot2.x基础篇:使用YAML代替Properties的对应配置

YAML是一种用于指定层次结构配置数据的便捷格式,SpringBoot内部通过集成SnakeYAML来支持解析,那我们如果来使用YAML格式来代替Properties,我们需要了解每一种Properties对应YAML的配置代替方式。

推荐阅读

普通配置

普通的方式比较简单直接,不存在数组集合子类等相关配置,我们通过Properties方式编写了如下的配置内容:

1
2
3
system.config.max-value=100
system.config.min-value=10
system.config.location=classpath:/configs

那这种方式对应的YAML配置是什么样子的呢?

如下所示:

1
2
3
4
5
system:
config:
min-value: 10
max-value: 100
location: classpath:/configs

这两种方式对比之下,YAML层次感鲜明,更直观的查看配置信息,而Properties这种方式配置前缀相对来说是冗余的,如果配置前缀过长,每一行的配置内容则会更长。

List配置

如果你需要添加List/Set/Array类型的配置信息,使用Properties方式编写如下所示:

1
2
3
system.config.ports[0]=8080
system.config.ports[1]=8081
system.config.ports[2]=8082

注意事项:配置的索引从0开始。

对应上面配置的YAML实现如下所示:

1
2
3
4
5
6
system:
config:
ports:
- 8080
- 8081
- 8082

无论是Properties还是YAML格式,这种List的配置内容都可以通过如下的方式获取:

1
2
3
4
5
6
@Configuration
@ConfigurationProperties(prefix = "system.config")
@Data
public class LoadListConfig {
private List<String> ports;
}

List内实体配置

如果你的List内不是基本数据类型,而是一个实体类,使用Properties的配置方式如下所示:

1
2
3
4
system.users[0].username=admin
system.users[0].email=yuqiyu@vip.qq.com
system.users[1].username=hengboy
system.users[1].email=jnyuqy@gmail.com

其实跟上面的List配置差不多,不过如果你需要配置每一个索引内字段的值,就要一一指定配置值。

对应上面的YAML实现如下所示:

1
2
3
4
5
6
system:
users:
- username: admin
email: yuqiyu@vip.qq.com
- username: hengboy
email: jnyuqy@gmail.com

每一个 - 其实代表集合内的一个元素。

获取List实体配置时我们可以通过如下的方式:

1
2
3
4
5
6
7
8
9
10
11
12
13
@Data
@Configuration
@ConfigurationProperties(prefix = "system")
public class LoadSystemUserConfig {
private List<User> users;

@Getter
@Setter
public static class User {
private String username;
private String email;
}
}

YAML缺点

一种方案的诞生是为了解决相应的问题,虽然说存在既有道理,但是每一种方案也不是完美的都有自身的缺点。

下面简单说说YAML的缺点:

  • 配置时缩进要特别注意,如果存在空格缩进对应不齐就会出现问题
  • SpringBoot内无法通过@PropertySource注解加载YAML文件。

SpringBoot2.x基础篇:配置文件中占位符的使用

概念

占位符是一种灵活的配置方式,可以让我们很灵活的使用配置参数,@Value注解的配置也是占位符的一种体现方式,这种方式可以从Environment内获取对应的配置值

推荐阅读

配置方式

application.yml/properties配置文件内可以直接使用占位符来进行配置的相互引用,如下所示:

1
2
3
4
5
system:
name: ${spring.application.name}
spring:
application:
name: project-sample

在上面的配置中,name配置直接引用了spring.application.name的配置值,这样我们在系统中通过@Value("${name}")或者通过@ConfigurationProperties方式使用时,得到的值都为project-sample

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// @Value方式
@Value("${system.name}")
private String name;

// @ConfigurationProperties方式
@Configuration
@ConfigurationProperties(prefix = "system")
static class LoadConfig {
private String name;

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}
}

这样方式极大地减少了相同的配置出现,让我们在配置文件中也可以实现类似于常量的定义。

使用默认值

当我们使用@Value注解来注入配置参数时,如果所引入的配置为NULL,启动项目时会抛出异常,项目无法正常启动,所以我们有必要添加一个默认值,如下所示:

1
2
3
4
5
system:
name: ${spring.application.name:default}
#spring:
# application:
# name: project-sample

在上面配置中把spring.application.name注释掉,当我们使用${spring.application.name}占位符时其实并未引用到有效的值,通过${xxx:defaultValue}的形式可以配置默认值,当占位符所引用的配置为NULL时,将会使用默认值(默认值的类型要对配置匹配)。

也可以通过@Value("${system.name:default}")这种方式配置默认值,不建议使用这种方式,默认值有变动时,我们还要一个一个修改,太麻烦了,不要给自己找事干…

当然对于配置的注入还是推荐使用@ConfigurationProperties,完全遵循OOP设计方式,在应用程序启动时进行赋值,就算是引用的配置为NULL没有默认值,也不会出现启动异常的问题。

“短”命令行参数

如果你对命令行参数不熟悉,可以访问 SpringBoot2.x基础篇:灵活的使用外部化配置信息 学习。

在实际部署应用程序时,有很多的配置是动态的,命令行参数是一个不错的方式,不过SpringBoot所提供的配置参数名称都比较长,对此我们完全可以利用占位符配置方式实现自定义。

占位符是从Environment内读取对应的配置值,而命令行参数在应用程序启动时会被一并加入到Environment中,因此也就实现了占位符动态配置,其实这个“短”的含义,是你定义的新的配置名称比较短而已。

假设我们的端口号需要动态指定,配置文件中可以通过如下的方式配置:

1
2
server:
port: ${port:8080}

port是我们定义的“短”占位符,在应用程序启动时并未指定则使用默认值8080

1
java -jar project-sample.jar --port=9090

通过--port=9090命令行参数,应用程序启动时端口号就变为了9090

SpringBoot2.x基础篇:配置文件的加载顺序以及优先级覆盖

SpringBoot约定了配置文件,默认为application.properties,通过该文件可以修改很多默认的配置,当然我们还可以在该配置文件内添加自定义的配置,该文件通过key=value的形式进行配置。

推荐阅读

疑惑配置提示?

当我们使用开发工具来配置时,就会出现相应的提示,这要完全要归功于spring-configuration-metadata.json配置元数据文件,该文件内记录了配置的名称类型归属类等信息,如果配置类型为枚举还可以实现选择性配置

SpringBoot提供了一个依赖,它的主要任务就是自动生成配置元数据,该依赖的名称为spring-boot-configuration-processor,在打包时会在META-INF目录生成一个名为spring-configuration-metadata.json的文件。

配置方式

虽然默认使用properties格式的配置文件,不过这种方式会导致配置的部分前缀冗余,可阅读性稍差,SpringBoot内部还支持使用yaml方式的配置文件,只需要在src/main/resources目录下创建一个名为application.yml文件即可,使用配置时同样也有提供功能。

项目内可以同时存在application.propertiesapplication.yml两个文件,经过测试发现,properties优先级会高一些,相同名称的配置,会将yml内的配置覆盖掉。

指定配置文件

如果你的应用程序配置文件的名称不是application,你想要进行自定义,可以通过--spring.config.name命令行参数进行指定,如下所示:

1
java -jar project-sample.jar --spring.config.name=custome

注意事项:我们只需要指定配置文件的名称即可,可以使用propertiesyaml文件格式,上面的配置会加载src/main/resources/custome.ymlsrc/main/resources/custome.properties

通过--spring.config.name仅仅是修改了配置文件的名称,那如果是修改配置文件所处的目录位置,我们需要怎么做呢?

SpringBoot已经给我们准备好了,通过--spring.config.location参数就可以指定配置文件的位置,如下所示:

1
java -jar project-sample.jar --spring.config.location=classpath:/configs/custome.yml

如果一个配置文件无法满足你的需求,那你看看下面这个方式:

1
java -jar project-sample.jar --spring.config.location=classpath:/configs/custome.yml,classpath:/configs/default.properties

注意事项:支持通过命令行参数的方式指定多个配置文件,使用英文半角 , 隔开即可。

如果你通过spring.config.location指定的不是一个文件而是一个目录,在路径最后务必添加一个”/“结束,然后结合spring.config.name进行组合配置文件,组合示例如下:

1
2
3
4
5
# 加载/configs/application.properties 或 /configs/application.yml(默认文件名)
java -jar project-sample.jar --spring.config.location=classpath:/configs/

# 加载/configs/custome.properties 或 /configs/custome.yml
java -jar project-sample.jar --spring.config.location=classpath:/configs/ --spring.config.name=custome

注意事项:spring.config.name该配置参数默认值为application,所以如果只是指定了spring.config.location并为目录形式,上面示例中会自动将spring.config.name追加到目录路径后,如果指定的spring.config.location并非是一个目录,这里会忽略spring.config.name的值。

加载顺序

SpringBoot应用程序在启动时会遵循下面的顺序进行加载配置文件:

  1. 类路径下的配置文件
  2. 类路径内config子目录的配置文件
  3. 当前项目根目录下的配置文件
  4. 当前项目根目录下config子目录的配置文件

示例项目配置文件存放结构如下所示:

1
2
3
4
5
6
7
8
. project-sample
├── config
│ ├── application.yml (4)
│ └── src/main/resources
| │ ├── application.yml (1)
| │ └── config
| | │ ├── application.yml (2)
├── application.yml (3)

启动时加载配置文件顺序:1 > 2 > 3 > 4

src/main/resources下的配置文件在项目编译时,会放在target/classes下。

优先级覆盖

SpringBoot配置文件存在一个特性,优先级较高的配置加载顺序比较靠后相同名称的配置优先级较高的会覆盖优先级较低的内容。

为了更好地解释这一点,我们根据对应的加载顺序分别创建一个application.yml配置文件,来验证根据优先级的不同是否存在覆盖问题,如下图所示:

在上面四个配置文件中都有一个名为name的配置,而红色字体标注的内容就是每个配置文件name的配置内容,下面我们来启动项目测试下输出内容。

运行测试

在测试之前我们让启动类实现CommandLineRunner接口,如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@SpringBootApplication
public class LoadOrderOfConfigFilesApplication implements CommandLineRunner {

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

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

@Override
public void run(String... args) throws Exception {
System.out.println("配置名称:" + name);
}
}

项目启动后通过run方法进行打印${name}配置的内容。

测试一:顺序覆盖

保留上面四个对应加载顺序的配置文件,启动项目,控制台输出内容:

1
配置名称:project/config

期望与实际输出是符合的,项目根下的config目录是最后加载的,所以它的优先级相对其他三个来说是最高的,覆盖顺序为:4 > 3 > 2 > 1

测试二:跨顺序覆盖

上一个测试点我们对每一个加载顺序都对应添加了一个配置文件,那如果我们只有两个project/configclasses/config两个目录的配置文件,是否按照优先级进行覆盖呢?

删除另外两个,只保留project/configclasses/config两个位置的配置文件,启动项目控制台输出如下所示:

1
配置名称:project/config

同样是输出了优先级最高的project/config配置文件的内容,覆盖顺序为:4 > 1

测试点:单顺序加载

平时在项目开发中一般都是将application.yml配置文件放在src/main/resources目录下,然而根据上面的加载顺序来看,我们可以将配置文件放置在任意一处,启动时都会进行加载。

仅保留classes/config位置的配置文件,启动项目控制台输出内容如下所示:

1
配置名称:classes/config

IDEASpringBoot的支持真的很强大, classes/config下的配置文件同样提供了关键字提醒功能。

总结

了解配置文件的加载顺序,才能得心应手的进行配置覆盖,完全控制在不同环境下使用不同的配置内容,要记住classes/application.yml优先级最低,project/config/application.yml优先级最高。

SpringBoot2.x基础篇:探索配置文件中随机数的实现方式

随机数的使用你是不是经常用到?我们在进行运行SpringBoot单元测试时一般不会指定应用程序启动时的端口号,可以在application.properties文件内配置server.port的值为${random.int(10000)},代表了随机使用0~10000的端口号。

既然这种方式使用这么方便,那你知道${random.int}是通过什么方式实现的吗?

推荐阅读

概述

配置文件方式

在我们分析源码之前,我们先来看看${random.xxx}具体提供了哪几种的随机配置。

int随机数

使用${random.int}方式配置,结果从int的最大值、最小值中间产生,int的最小值为-2147483648,最大值为2147483647,配置如下所示:

1
2
server:
port: ${random.int}

int范围随机数

使用${random.int(10000)}方式配置,这种方式我们可以指定随机数的最大值,当然不能超过2147483647,配置如下所示:

1
2
server:
port: ${random.int(10000)}

注意事项:${random.int(10000)}随机数的值将会在0~10000之间产生,配置的最大值必须为正整数

如果需要指定随机数的最小值,可以使用${random.int[100,200]}方式配置,这样只会从100~200之间产生随机数(包括最小值,不包括最大值)。

long随机数

使用${random.long}方式配置,结果会从long的最大值、最小值中间产生,long的最小值为-9223372036854775808,最大值为9223372036854775807,配置方式如下所示:

1
2
config:
longValue: ${random.long}

long范围随机数

使用${random.long(10000)}方式配置,我们可以指定0~9223372036854775807之间的任意数值作为随机的最大上限,配置方式如下所示:

1
2
config:
maxLongValue: ${random.long(102400)}

如果需要指定最小值,可以使用${random.long[1024,2048]}方式配置,这样只会从1024~2048中产生随机数(包括最小值,不包括最大值)。

uuid随机数

uuid因为它的唯一性,应该是我们平时开发中比较常用到的。

SpringBoot也为我们考虑到了这一点,我们只需要使用${random.uuid}就可以获得一个随机的uuid字符串,配置方式如下所示:

1
2
config:
uuid: ${random.uuid}

@Value方式

如果在我们在编码中需要用到随机数的生成,${random}是支持注入使用的,主要还是因为它的实现继承自PropertySource

我们可以在Spring IOC所管理的类内直接使用@Value注解进行注入使用,如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/**
* 随机生成uuid字符串
*/
@Value("${random.uuid}")
private String uuid;
/**
* 随机生成0~1000的正整数
*/
@Value("${random.int(1000)}")
private int maxInt;
/**
* 随机生成0~102400的long类型数值
*/
@Value("${random.long(102400)}")
private long maxLong;

源码解析

我们之所以可以这么方便的使用随机数,都归功于SpringBoot为我们提供了一个名为RandomValuePropertySourcePropertySource实现类,该实现类位于org.springframework.boot.env包内,该类部分源码如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
/**
* {@link PropertySource} that returns a random value for any property that starts with
* {@literal "random."}. Where the "unqualified property name" is the portion of the
* requested property name beyond the "random." prefix, this {@link PropertySource}
* ...
*/
public class RandomValuePropertySource extends PropertySource<Random> {

private static final String PREFIX = "random.";

private static final Log logger = LogFactory.getLog(RandomValuePropertySource.class);

@Override
public Object getProperty(String name) {
// 仅处理random.开头的配置
if (!name.startsWith(PREFIX)) {
return null;
}
if (logger.isTraceEnabled()) {
logger.trace("Generating random property for '" + name + "'");
}
// 获取数据数,将random.后的内容作为类型参数传递到getRandomValue方法
return getRandomValue(name.substring(PREFIX.length()));
}

private Object getRandomValue(String type) {
// 处理random.int类型的随机数
if (type.equals("int")) {
return getSource().nextInt();
}
// 处理random.long类型的随机数
if (type.equals("long")) {
return getSource().nextLong();
}
// 处理random.int(100)类型的随机数
String range = getRange(type, "int");
if (range != null) {
// 生成有范围的int类型随机数
return getNextIntInRange(range);
}
// 处理random.long(1024)类型的随机数
range = getRange(type, "long");
if (range != null) {
// 生成有范围的long类型随机数
return getNextLongInRange(range);
}
// 处理random.uuid类型的随机数
if (type.equals("uuid")) {
// 生成随机的uuid返回
return UUID.randomUUID().toString();
}
// 默认返回随机字节
return getRandomBytes();
}

private String getRange(String type, String prefix) {
if (type.startsWith(prefix)) {
int startIndex = prefix.length() + 1;
if (type.length() > startIndex) {
return type.substring(startIndex, type.length() - 1);
}
}
return null;
}

private int getNextIntInRange(String range) {
String[] tokens = StringUtils.commaDelimitedListToStringArray(range);
int start = Integer.parseInt(tokens[0]);
if (tokens.length == 1) {
return getSource().nextInt(start);
}
return start + getSource().nextInt(Integer.parseInt(tokens[1]) - start);
}

private long getNextLongInRange(String range) {
String[] tokens = StringUtils.commaDelimitedListToStringArray(range);
if (tokens.length == 1) {
return Math.abs(getSource().nextLong() % Long.parseLong(tokens[0]));
}
long lowerBound = Long.parseLong(tokens[0]);
long upperBound = Long.parseLong(tokens[1]) - lowerBound;
return lowerBound + Math.abs(getSource().nextLong() % upperBound);
}
}

当我们使用${random.xxx}这种方式获取随机数时,无论是配置文件方式还是@Value方式都会通过org.springframework.boot.env.RandomValuePropertySource#getProperty方法来获取对应类型的随机数。

注意事项:RandomValuePropertySource在继承PropertySource时泛型类型为Randomjava.util.Random类内包含了全部的随机生成逻辑,该类由java提供,有兴趣可以研究下源码。

总结

SpringBoot内的配置都是通过ConfigurablePropertyResolver属性配置解析器来获取的,而该类的实例化在AbstractEnvironment内,我们通过AbstractEnvironment#getProperty(java.lang.String)方法可以获取由多个PropertySource实现类提供的属性配置。

SpringBoot2.x基础篇:灵活的使用外部化配置信息

SpringBoot提供了内部配置application.yml文件的方式来进行全局配置,还支持使用profiles来激活不同环境下使用不同的配置文件,而这种方式毕竟是已经打包完成了,因此存在一定的局限性,像数据库特殊敏感配置也可能存在泄露的风险,如何解决这种问题呢?我们来看看本章要讲到的外部配置的方式吧!!!

SpringBoot2.x基础篇:应用程序在启动时访问启动项参数

SpringBoot应用程序在启动时,我们可以传递自定义的参数来进行动态控制逻辑,比如我们使用--debug启动参数时就会使用debug启动应用程序,在控制台打印一些调试日志信息。

推荐阅读

什么是启动项参数?

启动项参数的格式一般是--开头的,如:java -jar service.jar --debug --skip,启动时我们就可以获取[debug,skip]两个启动项参数。

SpringBoot 内部提供了一个接口org.springframework.boot.ApplicationArguments来接收应用程序在启动时所传递的选项参数(Option Args),源码如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
public interface ApplicationArguments {

/**
* 返回未处理的原始参数列表
* @return the arguments
*/
String[] getSourceArgs();

/**
* 返回所有选项参数的名称
* For example, if the arguments were
* "--foo=bar --debug" would return the values {@code ["foo", "debug"]}.
* @return the option names or an empty set
*/
Set<String> getOptionNames();

/**
* 根据选项参数名称判断是否在启动时传递
* option with the given name.
* @param name the name to check
* @return {@code true} if the arguments contain an option with the given name
*/
boolean containsOption(String name);

/**
* 返回与具有给定名称的arguments选项关联的值的集合。
* <ul>
* <li>if the option is present and has no argument (e.g.: "--foo"), return an empty
* collection ({@code []})</li>
* <li>if the option is present and has a single value (e.g. "--foo=bar"), return a
* collection having one element ({@code ["bar"]})</li>
* <li>if the option is present and has multiple values (e.g. "--foo=bar --foo=baz"),
* return a collection having elements for each value ({@code ["bar", "baz"]})</li>
* <li>if the option is not present, return {@code null}</li>
* </ul>
* @param name the name of the option
* @return a list of option values for the given name
*/
List<String> getOptionValues(String name);

/**
* 返回分析的非选项参数的集合。
* @return the non-option arguments or an empty list
*/
List<String> getNonOptionArgs();
}

该接口有一个默认的实现DefaultApplicationArguments,它实现了ApplicationArguments接口的全部定义方法。

DefaultApplicationArguments类在org.springframework.boot.SpringApplication#run(java.lang.String...)方法内通过new进行实例化,该对象实例主要用于启动时的相关配置。

而在启动过程中的org.springframework.boot.SpringApplication#prepareContext方法内通过ConfigurableListableBeanFactory进行注册到IOC容器,并且把springApplicationArguments作为唯一名称。

获取启动项参数

上面我们说道,在应用启动时会将ApplicationArguments接口的实现类实例注册到IOC容器,所以我们可以使用注入ApplicationArguments接口的形式来获取启动项参数,如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
/**
* 加载启动项参数
*
* @author 恒宇少年
*/
@Component
public class LoadArguments {
/**
* 构造函数注入{@link ApplicationArguments}
*
* @param applicationArguments
*/
@Autowired
public LoadArguments(ApplicationArguments applicationArguments) {
// 判断是否存在名为skip的启动项参数
boolean isHaveSkip = applicationArguments.containsOption("skip");
System.out.println("skip:" + isHaveSkip);
// 遍历输出全部的非启动项参数
List<String> arguments = applicationArguments.getNonOptionArgs();
for (int i = 0; i < arguments.size(); i++) {
System.out.println("非启动项参数:" + arguments.get(i));
}
}
}

我们把项目通过mvn package命令进行打包后,使用如下命令启动:

1
java -jar spring-boot-basic-accessing-application-arguments-0.0.1-SNAPSHOT.jar --skip noway

当我们启动后控制台会输出如下内容:

1
2
3
4
...
skip:true
非启动项参数:noway
...

其中--skip为启动项参数,而后面携带的noway其实是不属于skip启动参数,如果我们使用--skip=noway作为启动参数时,调用ApplicationArguments#getOptionValues("skip")方法获取到的值则是noway

ApplicationRunner

除了通过注入ApplicationArguments的方式获取启动参数外,通过实现ApplicationRunner接口也可以获取ApplicationArguments对象实例,使用方法如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/**
* {@link ApplicationRunner} 实现类
*
* @author 恒宇少年
*/
@Component
public class ApplicationRunnerSupport implements ApplicationRunner {
@Override
public void run(ApplicationArguments args) throws Exception {
boolean isHaveSkip = args.containsOption("skip");
System.out.println("skip:" + isHaveSkip);
System.out.println(args.getOptionValues("skip"));
}
}

注意事项:实现ApplicationRunner接口的类需要通过@Component标注,通过注解方式注册到IOC容器。

敲黑板,划重点

我们可以通过注入ApplicationRunner这两种方法来获取ApplicationArguments对象,那你知道这两种方法的执行先后顺序吗?带着这个疑问可以动手实验下。

代码示例

如果您喜欢本篇文章请为源码仓库点个Star,谢谢!!!
本篇文章示例源码可以通过以下途径获取,目录为spring-boot-basic-accessing-application-arguments

SpringBoot2.x基础篇:应用程序在启动时发布ApplicationEvents要怎么注册监听?

SpringFramework编写过程中使用了大量的Event/Listener来做一些解耦的任务工作,当然在SpringBoot内同样也沿用了这一点,如果你看过我写的 业务解耦利器Event/Listener ,你应该了解事件的发布都是由ApplicationContext进行控制,但是在SpringBoot启动过程中有一些Event是在ApplicationContext实例化之前发布的,那我们要怎么去监听这些Events呢?

SpringBoot2.x基础篇:带你了解扫描Package自动注册Bean

我们一直在使用SpringBoot来开发应用程序,但是为什么在项目启动时就会自动注册使用注解@Component@Service@RestController…标注的Bean呢?

SpringBoot2.x基础篇:Linux后台运行Jar以及Jvm参数调优

我们将编写的应用程序打包为Jar可执行文件后,如果在Linux服务器环境下,可直接使用java -jar xxx.jar命令运行应用程序,不过当我们关闭命令窗口后启动中的应用程序也会停止,那我们需要通过什么方式才可以成为后台服务方式运行呢?

推荐阅读

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×