SpringBoot 读取配置文件方式

2/27/2022 JavaSpringSpringBoot

摘要

JDK:1.8.0_202
SpringBoot Version:2.3.2.RELEASE
示例代码:configure-demo (opens new window)

# 一:前言

Spring Boot获取文件总的来说有三种方式,分别是 @Value 注解,@ConfigurationProperties 注解和 Environment 接口。这三种注解可以配合着 @PropertySource 来使用,@PropertySource主要是用来指定具体的配置文件

# 二:@PropertySource解析

package org.springframework.context.annotation;

/**
 * 用于将配置内容添加到Spring的环境中 与@Configuration一起使用
 * 
 * 例如:
 * 有个app.properties里有testbean.name=myTestBean 
 * 使用@Configuration和@PropertySource将app.properties提供给Environment
 * 
 * 	@Configuration
 * 	@PropertySource("classpath:/com/myco/app.properties")
 * 	public class AppConfig {
 * 
 * 		@Autowired
 * 		Environment env;
 * 
 * 		@Bean
 * 		public TestBean testBean() {
 * 			TestBean testBean = new TestBean();
 * 			testBean.setName(env.getProperty("testbean.name"));
 * 			return testBean;
 * 		}
 * 	}
 * 	
 * 解析${...}和@Value注释中的占位符
 * 为了使用PropertySource中的属性解析定义中的${...}占位符或@Value注释,
 * 必须确保在ApplicationContext使用的BeanFactory中注册了适当的嵌入值解析器,
 * 可以通过静态@Bean方法显式注册PropertySourcesPlaceholderConfigurer来实现,
 * 但是请注意,通常仅当您需要自定义配置(例如占位符语法等)时才需要通过静态
 *
 * 例如:
 * 	@Configuration
 * 	@PropertySource("classpath:/com/${my.placeholder:default/path}/app.properties")
 * 	public class AppConfig {
 * 		@Autowired
 * 		Environment env;
 * 		
 * 		@Bean
 * 		public TestBean testBean() {
 * 			TestBean testBean = new TestBean();
 * 			testBean.setName(env.getProperty("testbean.name"));
 * 			return testBean;
 * 		}
 * 	}
 *
 * 如果给定的属性键存在于多个 .properties文件中,
 * 则处理的最后一个 @PropertySource 注释并覆盖
 * 
 * 例如:
 * 	@Configuration
 * 	@PropertySource("classpath:/com/myco/a.properties")
 * 	public class ConfigA { }
 * 	
 * 	@Configuration
 * 	@PropertySource("classpath:/com/myco/b.properties")
 * 	public class ConfigB { }
 * 	
 * 覆盖顺序取决于这些类在应用程序上下文中注册的顺序。
 * 	AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
 * 	ctx.register(ConfigA.class);
 * 	ctx.register(ConfigB.class);
 * 	ctx.refresh();
 * 	
 * 在上面的场景中,b.properties 中的属性将覆盖 a.properties 中存在的任何重复项
 * 因为 ConfigB 是最后注册的。
 * 
 * 在某些情况下,在使用@PropertySource注释时严格控制属性源排序可能是不可能或不切实际的
 * 例如,如果上面的 @Configuration 类是通过组件扫描注册的,则很难预测其顺序
 * 在这种情况下-如果覆盖很重要建议ConfigurableEnvironment和MutablePropertySources
 */
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Repeatable(PropertySources.class)
public @interface PropertySource {

	/**
	 * 指明此属性源的名称
	 * 如果省略,factory()将根据底层资源生成一个名称
	 * (就org.springframework.core.io.support.DefaultPropertySourceFactory而言;
	 * 通过org.springframework.core.io.support.ResourcePropertySource无参构造函数)
	 */
	String name() default "";

	/**
	 * 指示要加载的属性文件的资源位置。
	 * 支持 properties 和 xml 文件格式 — 例如,“classpath:/com/myco/app.properties”或“file:/path/to/file.xml”
	 * 不允许使用资源位置通配符(例如 **/*.properties)
	 * 每个位置必须精确评估为一个 .properties 资源
	 * ${...} 占位符将针对已在环境中注册的任何/所有属性源进行解析
	 */
	String[] value();

	/**
	 * 是否忽略未找到的property资源
	 * 默认为false
	 * /
	boolean ignoreResourceNotFound() default false;

	/**
	 * 给定资源的特定字符编码,例如 "UTF-8"。
	 * properties文件的编码默认是ios8859-1
	 */
	String encoding() default "";

	/**
	 * 指定自定义的PropertySourceFactory
	 * 如果想要指定yml等其他格式的文件需要自定义实现
	 */
	Class<? extends PropertySourceFactory> factory() default PropertySourceFactory.class;

}
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
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112

# 三:自定义PropertySourceFactory

下面例子采用的都是 yml 格式,所以需要自定义 PropertySourceFactory 用来解析 yml

import org.springframework.beans.factory.config.YamlPropertiesFactoryBean;
import org.springframework.core.env.PropertiesPropertySource;
import org.springframework.core.env.PropertySource;
import org.springframework.core.io.support.DefaultPropertySourceFactory;
import org.springframework.core.io.support.EncodedResource;

import java.io.IOException;
import java.util.Properties;

public class YmlConfigFactory extends DefaultPropertySourceFactory {
    @Override
    public PropertySource<?> createPropertySource(String name, EncodedResource resource) throws IOException {
        String sourceName = name != null ? name : resource.getResource().getFilename();
        if (!resource.getResource().exists()) {
            return new PropertiesPropertySource(sourceName, new Properties());
        } else if (sourceName.endsWith(".yml") || sourceName.endsWith(".yaml")) {
            Properties propertiesFromYaml = loadYml(resource);
            return new PropertiesPropertySource(sourceName, propertiesFromYaml);
        } else {
            return super.createPropertySource(name, resource);
        }
    }

    private Properties loadYml(EncodedResource resource) throws IOException {
        YamlPropertiesFactoryBean factory = new YamlPropertiesFactoryBean();
        factory.setResources(resource.getResource());
        factory.afterPropertiesSet();
        return factory.getObject();
    }
}
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

# 四:@Value

三种用法:

  1. 直接赋值:@Value("ccj")
  2. 读取配置文件:@Value("${demo1.name}")
  3. 指定默认值:@Value("${demo1.name:ccj}") 表示如果没有demo1.name的配置,则赋值为ccj
  4. SPEL表达式:@Value("#{'${demo1.name}'?.toUpperCase()}") 表示将从配置文件读取的值转为大写,?可以不填,表示如果没有demo1.name的配置,则忽略

application.yml

## demo1
demo1:
  name: ccj
  address: guangzhou
  like: comic,coding,movie
1
2
3
4
5

TestController.java

import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.Arrays;

@RestController
public class TestController {

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

    @Value("${demo1.address}")
    private String demo1Address;

    @Value("#{'${demo1.like}'.split(',')}")
    private String demo1Like1;

    @Value("${demo1.like}")
    private String[] demo1Like2;

    @GetMapping("/demo1")
    public String demo1() {
        return String.format("name:%s<br/>address:%s<br/>like1:%s<br/>like2:%s", demo1Name, demo1Address, demo1Like1, Arrays.toString(demo1Like2));
    }
}
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

运行结果:

配置

不支持复杂类型,例如 List、Map

# 五:Environment

application.yml

## demo2
demo2:
  name: qform
  address: huizhou
  like: eating,running
1
2
3
4
5

TestController.java

@RestController
public class TestController {

    @Autowired
    Environment environment;

    @GetMapping("/demo2")
    public String demo2() {
        String name = environment.getProperty("demo2.name");
        String address = environment.getProperty("demo2.address");
        List like = environment.getProperty("demo2.like", List.class);
        return String.format("name:%s<br/>address:%s<br/>like:%s", name, address, like);
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14

运行结果:

结果

不支持复杂类型,例如 Map

# 六:@ConfigurationProperties

类的字段必须有public setter方法

根据 Spring Boot 宽松的绑定规则,类的属性名称必须与外部属性的名称匹配

demo3.yml

## demo3
demo3:
  name: ccj
  address: jieyang
  like:
    - comic
    - eating
    - coding
    - movie
    - 佛系
  map1: { key1: v1, key2: v2 }
  map2:
    key3: v3
    key4: v4
1
2
3
4
5
6
7
8
9
10
11
12
13
14

Demo3.java

import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;

import java.util.List;

@Configuration
@PropertySource(value = {"classpath:demo3.yml"}, encoding = "UTF-8", factory = YmlConfigFactory.class)
@ConfigurationProperties(prefix = "demo3")
public class Demo3 {

    private String name;

    private String address;

    private List<String> like;

    private Map<String, String> map1;

    private Map<String, String> map2;

    // Getter 和 Setter忽略
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

TestController.java

@RestController
public class TestController {

    @Autowired
    Demo3 demo3;
    
    @GetMapping("/demo3")
    public String demo3() {
        return String.format("name:%s<br/>address:%s<br/>like:%s<br/>map1:%s<br/>map2:%s", demo3.getName(), demo3.getAddress(), demo3.getLike(), demo3.getMap1(), demo3.getMap2());
    }
}
1
2
3
4
5
6
7
8
9
10
11

运行结果:

结果

# 七:PropertiesLoaderUtils

demo4.properties

## demo4
demo4.name: demo4Name
demo4.address: demo4Address
1
2
3

TestController.java

@RestController
public class TestController {

    @GetMapping("/demo4")
    public String demo4() throws IOException {
        Properties properties = PropertiesLoaderUtils.loadAllProperties("demo4.properties");
        return String.format("name:%s<br/>address:%s", properties.getProperty("demo4.name"), properties.getProperty("demo4.address"));
    }
}
1
2
3
4
5
6
7
8
9

运行结果:

结果

如果Map类型的key包含非字母数字和-的字符,需要用单/双引号括起来

# 八:Configuration Processor

在使用 @ConfigurationProperties 时,idea一般会提示如下警告

错误提示

原因:SpringBoot1.5 以上版本 @ConfigurationProperties 取消 location 注解

解决方案:使用 spring-boot-configuration-processor 在编译时自动产生 @ConfigurationProperties 标记的类的Metadata,用于提供给IDE在自动提示。

添加依赖

<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-configuration-processor</artifactId>
	<optional>true</optional>
</dependency>
1
2
3
4
5

重新 build 项目之后,configuration processor 会创建一个 JSON 文件:

json

由于 spring-boot-configuration-processor 程序执行时用不到,所以从 spring-boot-maven-plugin 中排除,这样构建时才不会被打包。

<plugin>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-maven-plugin</artifactId>
    <configuration>
        <excludes>
            <exclude>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-configuration-processor</artifactId>
            </exclude>
    	</excludes>
    </configuration>
</plugin>
1
2
3
4
5
6
7
8
9
10
11
12

# 九:拓展

# 9.1 @DurationUnit

从配置参数中解析 durations(持续时间),既可以配置毫秒数数值,也可配置带有单位的文本。

常用单位如下:

  • ns for nanoseconds (纳秒)
  • us for microseconds (微秒)
  • ms for milliseconds (毫秒)
  • s for seconds (秒)
  • m for minutes (分)
  • h for hours (时)
  • d for days (天)

# 9.2 @DataSize

与 Duration 的用法一样,默认单位是 byte (字节),可以通过 @DataSizeUnit 单位指定。

  • B for bytes
  • KB for kilobytes
  • MB for megabytes
  • GB for gigabytes
  • TB for terabytes

# 9.3 示例

demo5.yml

## demo5
demo5:
  duration1: 2s
  duration2: 5
  dataSize1: 2MB
  dataSize2: 5
1
2
3
4
5
6

TestController.java

@RestController
public class TestController {

    @Autowired
    Demo5 demo5;
    
    @GetMapping("/demo5")
    public String demo5() {
        return String.format("duration1:%s<br/>duration2:%s<br/>dataSize1:%s<br/>dataSize2:%s", demo5.getDuration1(), demo5.getDuration2(), demo5.getDataSize1(), demo5.getDataSize2());
    }
    
}
1
2
3
4
5
6
7
8
9
10
11
12

运行结果:

结果

# 9.4 @DeprecatedConfigurationProperty

标记某个属性为 deprecated

可以通过添加 @DeprecatedConfigurationProperty 注解到字段的 getter 方法上,来标示该字段为 deprecated,重新 build 项目,看看 JSON 文件发生了什么?

废弃

# 十:自定义

解析配置参数到自定义的对象类型上

例如:新增一个 Weight 类来设置最大包裹重量

Weight.java

public class Weight {

    private String value;

    public Weight(String value) {
        this.value = value;
    }

    public String getValue() {
        return value;
    }

    public void setValue(String value) {
        this.value = value;
    }

    @Override
    public String toString() {
        return "Weight{" +
                "value='" + value + '\'' +
                '}';
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

WeightConverter.java(转换器)

public class WeightConverter implements Converter<String, Weight> {

    @Override
    public Weight convert(String s) {
        return new Weight("Max:" + s);
    }
}
1
2
3
4
5
6
7

PropertiesConfig.java(注入到Spring上下文)

@Configuration
public class PropertiesConfig {

    @Bean
    @ConfigurationPropertiesBinding
    public WeightConverter getWeightConverter() {
        return new WeightConverter();
    }

}
1
2
3
4
5
6
7
8
9
10

TestController.java

@RestController
public class TestController {

    @Autowired
    Demo6 demo6;
    
        @GetMapping("/demo6")
    public String demo6() {
        return String.format("weight:%s", demo6.getWeight());
    }

}
1
2
3
4
5
6
7
8
9
10
11
12

运行结果:

结果

# 十一:总结:

  • properties文件默认使用的是iso8859-1,并且不可修改
  • yml文件的加载顺序高于properties,但是读取配置信息的时候会读取后加载的
  • @PropertySource注解默认只会加载properties文件
  • @PropertySource注解可以与任何一种方式联合使用
  • 简单值推荐使用@Value,复杂对象推荐使用@ConfigurationProperties
  • 示例代码:configure-demo (opens new window)

# 十二:参考文献

最后更新: 2/28/2022, 4:08:57 PM