需求
现有一个时区配置集合(key=value 形式),类似如下:
1
2
|
BJS=Asia/Shanghai
TYO=Asia/Tokyo
|
需要将其保存在一个文件中并在程序启动时将其加载为 Map,时区映射为 ZoneId 类型。同时参考 Spring Boot 支持配置文件在 resources 目录下、jar 同级目录、config/ 目录下。
分析
配置文件选择
配置文件的选择,yaml、properties、ini、txt 等。Spring Boot 支持 yaml 和 properties,优选这两种类型,yaml 更适合嵌套层次的配置,所以我们选用 properties 保存时区配置。
文件加载
需要参考 Spring Boot 的加载顺序,classpath 目录、classpath:config/、jar 同级目录、jar 同级目录 config/ 子目录。 如果仅需以优先级为顺序,
加载一处的配置文件,那么在各个路径进行文件判断,以优先级最高的文件进行加载,其他文件则自动忽略。不过 Spring Boot 的配置是多个目录下配置文件的合集,
因此我们此处也以合集为例。
解析方式
默认情况下配置无公共前缀,所以我们无法使用 @PropertySource + @ConfigurationProperties 的组合。
当然,如果可以修改配置文件添加个公共前缀,也能使用这个组合。例如:
1
2
|
timezone.BJS=Asia/Shanghai
timezone.TYO=Asia/Tokyo
|
加载 properties 文件可使用工具类:
1
2
3
|
import org.springframework.core.io.support.PropertiesLoaderUtils;
PropertiesLoaderUtils.loadProperties(resource);
|
对于 classpath 路径,我们可以使用 ClassPathResource,对于 jar 同级目录,使用 FileSystemResource。
编码
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
113
114
115
116
117
118
119
120
121
122
123
124
125
|
import jakarta.annotation.PostConstruct;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.FileSystemResource;
import org.springframework.core.io.support.PropertiesLoaderUtils;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import java.io.IOException;
import java.time.ZoneId;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Properties;
@Component
@Slf4j
public class TimezonesProperties {
/**
* CLASSPATH 路径
*/
private static final String CLASSPATH_PREFIX = "classpath:";
/**
* 配置文件名
*/
private static final String CONFIG_FILENAME = "timezones.properties";
/**
* 配置文件 (按优先级 低到高 排序)
*/
private static final String[] TIME_ZONE_CONFIG_LOCATIONS = new String[]{
CLASSPATH_PREFIX + CONFIG_FILENAME, // 最低优先级 - classpath 目录
CLASSPATH_PREFIX + "config/" + CONFIG_FILENAME, // classpath 下 config 目录
"./" + CONFIG_FILENAME, // 次优先级 - 当前目录
"./config/" + CONFIG_FILENAME, // 最高优先级 - 当前目录下的 config 子目录
};
/**
* 时区 Map
*/
private final Map<String, ZoneId> timezones = new LinkedHashMap<>(16);
/**
* 初始化加载配置文件
*/
@PostConstruct
public void init() {
for (String configLocation : TIME_ZONE_CONFIG_LOCATIONS) {
Properties props;
try {
props = loadProperties(configLocation);
} catch (Exception ex) {
log.debug("Failed to load properties from: {}", configLocation);
continue;
}
if (props == null || props.isEmpty()) {
continue;
}
props.forEach((key, value) -> {
try {
ZoneId zoneId = ZoneId.of(value.toString().trim());
this.timezones.put(key.toString().toUpperCase(), zoneId);
} catch (Exception ex) {
throw new IllegalArgumentException("Invalid timezone '" + value + "'", ex);
}
});
}
if (CollectionUtils.isEmpty(this.timezones)) {
log.warn("No timezone properties files were found.");
}
}
/**
* 加载配置
* @param location 配置文件路径
* @return 配置文件内容
* @throws IOException if loading failed
*/
private Properties loadProperties(String location) throws IOException {
if (location.startsWith("classpath:")) {
String classpathLocation = location.substring(CLASSPATH_PREFIX.length());
ClassPathResource resource = new ClassPathResource(classpathLocation);
if (resource.exists()) {
return PropertiesLoaderUtils.loadProperties(resource);
}
} else {
FileSystemResource resource = new FileSystemResource(location);
if (resource.exists()) {
return PropertiesLoaderUtils.loadProperties(resource);
}
}
return null;
}
/**
* 获取全部时区
* @return 全部时区信息
*/
public Map<String, ZoneId> getAllTimezones() {
return Collections.unmodifiableMap(timezones);
}
/**
* 获取指定时区
* @param key key
* @return key 对应的时区
*/
public ZoneId getTimeZone(String key) {
return timezones.get(key);
}
/**
* 获取指定时区,如果没有则使用默认时区
* @param key key
* @param defaultZoneId 默认时区
* @return key 对应的时区
*/
public ZoneId getTimeZoneOrDefault(String key, ZoneId defaultZoneId) {
return timezones.getOrDefault(key, defaultZoneId);
}
}
|