|
@@ -0,0 +1,370 @@
|
|
1
|
+package me.yuxiaoyao.config.loader;
|
|
2
|
+
|
|
3
|
+import com.fasterxml.jackson.annotation.JsonInclude;
|
|
4
|
+import com.fasterxml.jackson.core.JsonFactory;
|
|
5
|
+import com.fasterxml.jackson.core.type.TypeReference;
|
|
6
|
+import com.fasterxml.jackson.databind.DeserializationFeature;
|
|
7
|
+import com.fasterxml.jackson.databind.ObjectMapper;
|
|
8
|
+
|
|
9
|
+import java.io.*;
|
|
10
|
+import java.util.*;
|
|
11
|
+
|
|
12
|
+/**
|
|
13
|
+ * @author Kerry on 19/10/09
|
|
14
|
+ */
|
|
15
|
+
|
|
16
|
+public class ConfigLoader {
|
|
17
|
+
|
|
18
|
+ private static final String PROFILE = "profile";
|
|
19
|
+ private static final String ACTIVE = "active";
|
|
20
|
+
|
|
21
|
+ public static final String ACTIVE_PROFILE = PROFILE + "." + ACTIVE;
|
|
22
|
+
|
|
23
|
+ /**
|
|
24
|
+ * 如果想修改,可以通过反射修改
|
|
25
|
+ */
|
|
26
|
+ private static String DEFAULT_FILE_CONFIG_FOLDER = "config";
|
|
27
|
+ private static String DEFAULT_APPLICATION = "application";
|
|
28
|
+
|
|
29
|
+ private static String START_PLACEHOLDER = "${";
|
|
30
|
+ private static String END_PLACEHOLDER = "}";
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+ private enum ConfigProtocol {
|
|
34
|
+ /**
|
|
35
|
+ * 文件
|
|
36
|
+ */
|
|
37
|
+ FILE(1),
|
|
38
|
+ /**
|
|
39
|
+ * 类路径
|
|
40
|
+ */
|
|
41
|
+ CLASSPATH(0);
|
|
42
|
+
|
|
43
|
+ /**
|
|
44
|
+ * 配置文件优先级
|
|
45
|
+ */
|
|
46
|
+ private int order;
|
|
47
|
+
|
|
48
|
+ ConfigProtocol(int order) {
|
|
49
|
+ this.order = order;
|
|
50
|
+ }
|
|
51
|
+
|
|
52
|
+ public static List<ConfigProtocol> sortList() {
|
|
53
|
+ List<ConfigProtocol> configProtocols = new ArrayList<>(Arrays.asList(values()));
|
|
54
|
+ configProtocols.sort((o1, o2) -> o2.order - o1.order);
|
|
55
|
+ return configProtocols;
|
|
56
|
+ }
|
|
57
|
+ }
|
|
58
|
+
|
|
59
|
+ public interface ConfigListener {
|
|
60
|
+ /**
|
|
61
|
+ * 回调Profile
|
|
62
|
+ *
|
|
63
|
+ * @param profile
|
|
64
|
+ */
|
|
65
|
+ void activeProfile(String profile);
|
|
66
|
+ }
|
|
67
|
+
|
|
68
|
+ /**
|
|
69
|
+ * 返回数据流
|
|
70
|
+ *
|
|
71
|
+ * @param configProtocol
|
|
72
|
+ * @param path
|
|
73
|
+ * @return 结果可能为 null
|
|
74
|
+ */
|
|
75
|
+ private static InputStream getConfigFileAsStream(ConfigProtocol configProtocol, String path) {
|
|
76
|
+
|
|
77
|
+ switch (configProtocol) {
|
|
78
|
+ case FILE:
|
|
79
|
+ try {
|
|
80
|
+ return new FileInputStream(new File(DEFAULT_FILE_CONFIG_FOLDER + File.separator + path));
|
|
81
|
+ } catch (FileNotFoundException ignored) {
|
|
82
|
+ }
|
|
83
|
+ return null;
|
|
84
|
+ case CLASSPATH:
|
|
85
|
+ return Thread.currentThread().getContextClassLoader().getResourceAsStream(path);
|
|
86
|
+ default:
|
|
87
|
+ throw new Error("not support this path = " + path);
|
|
88
|
+ }
|
|
89
|
+ }
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+ public static <T> T parseConfig(Class<T> clazz) {
|
|
93
|
+ return parseConfig(null, clazz, null);
|
|
94
|
+ }
|
|
95
|
+
|
|
96
|
+ public static <T> T parseConfig(String[] args, Class<T> clazz, ConfigListener configListener) {
|
|
97
|
+ LinkedHashMap map = loaderConfig(args, configListener);
|
|
98
|
+ return getObjectMapper().convertValue(map, clazz);
|
|
99
|
+ }
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+ public static <T> T parseConfig(TypeReference<T> toValueTypeRef) {
|
|
103
|
+ return parseConfig(null, toValueTypeRef, null);
|
|
104
|
+ }
|
|
105
|
+
|
|
106
|
+ public static <T> T parseConfig(String[] args, TypeReference<T> toValueTypeRef, ConfigListener configListener) {
|
|
107
|
+ LinkedHashMap map = loaderConfig(args, configListener);
|
|
108
|
+ return getObjectMapper().convertValue(map, toValueTypeRef);
|
|
109
|
+ }
|
|
110
|
+
|
|
111
|
+ private static ObjectMapper getObjectMapper() {
|
|
112
|
+ ObjectMapper objectMapper = new ObjectMapper();
|
|
113
|
+ objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
|
|
114
|
+ objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
|
|
115
|
+ return objectMapper;
|
|
116
|
+ }
|
|
117
|
+
|
|
118
|
+ /**
|
|
119
|
+ * 加载默认配置文件
|
|
120
|
+ *
|
|
121
|
+ * @return
|
|
122
|
+ */
|
|
123
|
+ public static LinkedHashMap loaderConfig() {
|
|
124
|
+ return loaderConfig(null, null);
|
|
125
|
+ }
|
|
126
|
+
|
|
127
|
+ /**
|
|
128
|
+ * 加载指定 profile 的配置文件
|
|
129
|
+ *
|
|
130
|
+ * @param args
|
|
131
|
+ * @return
|
|
132
|
+ */
|
|
133
|
+ public static LinkedHashMap loaderConfig(String[] args, ConfigListener configListener) {
|
|
134
|
+
|
|
135
|
+ List<ConfigProtocol> configProtocols = ConfigProtocol.sortList();
|
|
136
|
+ LinkedHashMap defaultProperties = null;
|
|
137
|
+ ConfigProtocol matchProtocol = null;
|
|
138
|
+ for (ConfigProtocol protocol : configProtocols) {
|
|
139
|
+ LinkedHashMap temp = selectConfig(protocol, DEFAULT_APPLICATION);
|
|
140
|
+ if (temp != null) {
|
|
141
|
+ defaultProperties = temp;
|
|
142
|
+ matchProtocol = protocol;
|
|
143
|
+ break;
|
|
144
|
+ }
|
|
145
|
+ }
|
|
146
|
+ if (defaultProperties == null) {
|
|
147
|
+ return null;
|
|
148
|
+ }
|
|
149
|
+
|
|
150
|
+ String profile = getProfileByArgs(args);
|
|
151
|
+
|
|
152
|
+ if (profile == null || profile.length() == 0) {
|
|
153
|
+ profile = getActiveProfile(defaultProperties);
|
|
154
|
+ if (configListener != null) {
|
|
155
|
+ configListener.activeProfile(profile);
|
|
156
|
+ }
|
|
157
|
+ }
|
|
158
|
+ LinkedHashMap map = selectConfig(matchProtocol, DEFAULT_APPLICATION + "-" + profile);
|
|
159
|
+ if (map != null) {
|
|
160
|
+ removeActiveProfile(map);
|
|
161
|
+ //noinspection unchecked
|
|
162
|
+ defaultProperties.putAll(map);
|
|
163
|
+ }
|
|
164
|
+ return propertiesHandler(defaultProperties);
|
|
165
|
+ }
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+ private static LinkedHashMap selectConfig(ConfigProtocol configProtocol, String filePrefix) {
|
|
169
|
+ InputStream configFileAsStream = getConfigFileAsStream(configProtocol, filePrefix + ".yml");
|
|
170
|
+ if (configFileAsStream == null) {
|
|
171
|
+ configFileAsStream = getConfigFileAsStream(configProtocol, filePrefix + ".yaml");
|
|
172
|
+ }
|
|
173
|
+ Object yamlFactory = null;
|
|
174
|
+ try {
|
|
175
|
+ Class<?> factory = Class.forName("com.fasterxml.jackson.dataformat.yaml.YAMLFactory");
|
|
176
|
+ yamlFactory = factory.newInstance();
|
|
177
|
+ } catch (InstantiationException | IllegalAccessException | ClassNotFoundException e) {
|
|
178
|
+ // e.printStackTrace();
|
|
179
|
+ }
|
|
180
|
+ ObjectMapper objectMapper = null;
|
|
181
|
+ if (configFileAsStream != null && yamlFactory != null) {
|
|
182
|
+ objectMapper = new ObjectMapper((JsonFactory) yamlFactory);
|
|
183
|
+ } else {
|
|
184
|
+ configFileAsStream = getConfigFileAsStream(configProtocol, filePrefix + ".properties");
|
|
185
|
+ try {
|
|
186
|
+ Class<?> javaProps = Class.forName("com.fasterxml.jackson.dataformat.javaprop.JavaPropsMapper");
|
|
187
|
+ objectMapper = (ObjectMapper) javaProps.newInstance();
|
|
188
|
+ } catch (ClassNotFoundException | IllegalAccessException | InstantiationException e) {
|
|
189
|
+ // e.printStackTrace();
|
|
190
|
+ }
|
|
191
|
+ }
|
|
192
|
+ if (objectMapper == null) {
|
|
193
|
+ throw new Error("jackson dependencies at least container [jackson-dataformat-yaml] or [jackson-dataformat-properties].");
|
|
194
|
+ }
|
|
195
|
+ if (configFileAsStream == null) {
|
|
196
|
+ return null;
|
|
197
|
+ }
|
|
198
|
+ try {
|
|
199
|
+ return objectMapper.readValue(configFileAsStream, LinkedHashMap.class);
|
|
200
|
+ } catch (IOException e) {
|
|
201
|
+ e.printStackTrace();
|
|
202
|
+ }
|
|
203
|
+ return null;
|
|
204
|
+ }
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+ private static String getActiveProfile(Map fileMap) {
|
|
208
|
+ Object profile = fileMap.get(PROFILE);
|
|
209
|
+ if (profile instanceof Map) {
|
|
210
|
+ final Object o = ((Map) profile).get(ACTIVE);
|
|
211
|
+ if (o != null) {
|
|
212
|
+ return (String) o;
|
|
213
|
+ }
|
|
214
|
+ }
|
|
215
|
+ return null;
|
|
216
|
+ }
|
|
217
|
+
|
|
218
|
+ private static void removeActiveProfile(Map map) {
|
|
219
|
+ Object profile = map.get(PROFILE);
|
|
220
|
+ if (profile instanceof Map) {
|
|
221
|
+ ((Map) profile).remove(ACTIVE);
|
|
222
|
+ }
|
|
223
|
+ }
|
|
224
|
+
|
|
225
|
+ /**
|
|
226
|
+ * 属性处理,目前暂时不处理,替换符号等操作
|
|
227
|
+ *
|
|
228
|
+ * @param map
|
|
229
|
+ * @return
|
|
230
|
+ */
|
|
231
|
+ private static LinkedHashMap propertiesHandler(LinkedHashMap map) {
|
|
232
|
+ Map<Object, String> preHandler = new LinkedHashMap<>();
|
|
233
|
+ // find placeholder value
|
|
234
|
+ findPreResolveProperties(map, preHandler, null);
|
|
235
|
+ // replace value
|
|
236
|
+ Map<Object, Object> objectObjectMap = ConfigLoader.resolveProperties(map, preHandler);
|
|
237
|
+ // replace to map
|
|
238
|
+ replaceProperties(map, objectObjectMap);
|
|
239
|
+ return map;
|
|
240
|
+ }
|
|
241
|
+
|
|
242
|
+
|
|
243
|
+ private static Object getProperties(Map map, String key) {
|
|
244
|
+ final int i = key.indexOf(".");
|
|
245
|
+ if (i == -1) {
|
|
246
|
+ return map.get(key);
|
|
247
|
+ }
|
|
248
|
+ String currentKey = key.substring(0, i);
|
|
249
|
+ Object o = map.get(currentKey);
|
|
250
|
+
|
|
251
|
+ if (o instanceof Map) {
|
|
252
|
+ return getProperties((Map) o, (key.substring(i + 1)));
|
|
253
|
+ }
|
|
254
|
+ return o;
|
|
255
|
+ }
|
|
256
|
+
|
|
257
|
+ @SuppressWarnings("unchecked")
|
|
258
|
+ private static void setProperties(Map map, String key, Object value) {
|
|
259
|
+ if (map == null) {
|
|
260
|
+ return;
|
|
261
|
+ }
|
|
262
|
+ final int i = key.indexOf(".");
|
|
263
|
+ if (i == -1) {
|
|
264
|
+ map.put(key, value);
|
|
265
|
+ return;
|
|
266
|
+ }
|
|
267
|
+ String currentKey = key.substring(0, i);
|
|
268
|
+ Object o = map.get(currentKey);
|
|
269
|
+ if (o instanceof Map) {
|
|
270
|
+ setProperties((Map) o, (key.substring(i + 1)), value);
|
|
271
|
+ } else {
|
|
272
|
+ map.put(key, value);
|
|
273
|
+ }
|
|
274
|
+ }
|
|
275
|
+
|
|
276
|
+
|
|
277
|
+ public static void replaceProperties(Map map, Map<Object, ?> handlerMap) {
|
|
278
|
+
|
|
279
|
+ for (Object o : handlerMap.keySet()) {
|
|
280
|
+ Object value = handlerMap.get(o);
|
|
281
|
+ setProperties(map, o.toString(), value);
|
|
282
|
+
|
|
283
|
+ }
|
|
284
|
+
|
|
285
|
+ }
|
|
286
|
+
|
|
287
|
+
|
|
288
|
+ /**
|
|
289
|
+ * 把带的占位符的值替换为真实值
|
|
290
|
+ *
|
|
291
|
+ * @param map
|
|
292
|
+ * @param preHandlerMap
|
|
293
|
+ * @return
|
|
294
|
+ */
|
|
295
|
+ public static Map<Object, Object> resolveProperties(Map map, Map<Object, ?> preHandlerMap) {
|
|
296
|
+ Map<Object, Object> processedMap = new LinkedHashMap<>(preHandlerMap.size());
|
|
297
|
+
|
|
298
|
+ for (Object o : preHandlerMap.keySet()) {
|
|
299
|
+ Object value = preHandlerMap.get(o);
|
|
300
|
+
|
|
301
|
+ if (value instanceof String) {
|
|
302
|
+ // source value
|
|
303
|
+ String str = ((String) value).trim();
|
|
304
|
+ str = str.substring(START_PLACEHOLDER.length(), str.length() - END_PLACEHOLDER.length());
|
|
305
|
+ String[] defaultValue = str.split(":");
|
|
306
|
+ Object properties;
|
|
307
|
+ if (defaultValue.length > 1) {
|
|
308
|
+ properties = getProperties(map, defaultValue[0]);
|
|
309
|
+ } else {
|
|
310
|
+ properties = getProperties(map, defaultValue[0]);
|
|
311
|
+ }
|
|
312
|
+ // set default value
|
|
313
|
+ if (properties == null) {
|
|
314
|
+ if (defaultValue.length > 1) {
|
|
315
|
+ properties = defaultValue[1];
|
|
316
|
+ } else {
|
|
317
|
+ properties = value;
|
|
318
|
+ }
|
|
319
|
+ }
|
|
320
|
+ /// System.out.println("key = [" + value + "] - value = [" + properties + "]");
|
|
321
|
+ processedMap.put(o, properties);
|
|
322
|
+ }
|
|
323
|
+ }
|
|
324
|
+ return processedMap;
|
|
325
|
+ }
|
|
326
|
+
|
|
327
|
+ /**
|
|
328
|
+ * 查找所有具有占位符号的值,目前不支持数组
|
|
329
|
+ * 起始符 {@link ConfigLoader#START_PLACEHOLDER}
|
|
330
|
+ * 结束符 {@link ConfigLoader#END_PLACEHOLDER}
|
|
331
|
+ *
|
|
332
|
+ * @param map
|
|
333
|
+ * @param result 查找结果接收
|
|
334
|
+ * @return
|
|
335
|
+ */
|
|
336
|
+ public static void findPreResolveProperties(Map map, Map<Object, String> result, String prefix) {
|
|
337
|
+
|
|
338
|
+ for (Object key : map.keySet()) {
|
|
339
|
+ Object value = map.get(key);
|
|
340
|
+ if (value instanceof Map) {
|
|
341
|
+ findPreResolveProperties((Map) value, result, prefix == null ? key.toString() : prefix + "." + key.toString());
|
|
342
|
+ } else if (value instanceof String) {
|
|
343
|
+ String str = ((String) value).trim();
|
|
344
|
+ if (str.startsWith(START_PLACEHOLDER) && str.endsWith(END_PLACEHOLDER)) {
|
|
345
|
+ if (prefix == null) {
|
|
346
|
+ result.put(key.toString(), str);
|
|
347
|
+ } else {
|
|
348
|
+ result.put(prefix + "." + key.toString(), str);
|
|
349
|
+ }
|
|
350
|
+ }
|
|
351
|
+ }
|
|
352
|
+ }
|
|
353
|
+ //prefix = "";
|
|
354
|
+ }
|
|
355
|
+
|
|
356
|
+
|
|
357
|
+ private static String getProfileByArgs(String[] args) {
|
|
358
|
+ if (args != null && args.length > 0) {
|
|
359
|
+ for (String arg : args) {
|
|
360
|
+ arg = arg.trim();
|
|
361
|
+ if (arg.startsWith("--profile=")) {
|
|
362
|
+ return arg.substring(10);
|
|
363
|
+ }
|
|
364
|
+ }
|
|
365
|
+ }
|
|
366
|
+ return null;
|
|
367
|
+ }
|
|
368
|
+
|
|
369
|
+
|
|
370
|
+}
|