当 core 模块中 BootStra 启动,需要加载环境的核心配置信息,比如网关核心配置,包括配置类默认值,配置文件,环境变量,jvm 参数,运行参数,还需要加载配置中心的配置(Nacos)和注册中心相关配置
加载网关核心配置
初始化核心配置
config 类作为配置文件加载的核心配置类,后续想当于集成 Spring 环境作为加载 spring,yaml 的一些配置信息类
public class Config {
private int port = 8888;
private String applicationName = "api-gateway";
private String registryAddress = "127.0.0.1:8848";
private String env = "dev";
private int eventLoopGroupBossNum = 1;
private int eventLoopGroupWorkerNum = Runtime.getRuntime().availableProcessors();
private int maxContentLength = 64 * 1024 * 1024;
//默认单异步模式
private boolean whenComplete = true;
}
初始化配置加载器
**需要有一个配置类加载器来封装启动加载配置类,因为加载配置类会有一个顺序问题,优先级高的会覆盖优先级低的属性值,加载顺序为: **运行参数->jvm 参数-> 环境变量-> 配置文件-> 配置对象的默认值
package com.xiaohu.core;
import com.xiaohu.common.utils.PropertiesUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.InputStream;
import java.util.*;
import java.util.stream.Collectors;
/**
* @author xiaohugg
* @version 1.0
* @date 2024/4/18 21:28:36
*/
public class ConfigLoader {
private static final Logger log = LoggerFactory.getLogger(ConfigLoader.class);
private static final String CONFIG_FILE = "gateway.properties";
private static final String ENV_PREFIX = "GATEWAY_";
private static final String JVM_PREFIX = "gateway.";
private static ConfigLoader INSTANCE;
private Config config;
private ConfigLoader() {
}
public static synchronized ConfigLoader getInstance() {
if (INSTANCE == null) {
synchronized (ConfigLoader.class) {
INSTANCE = new ConfigLoader();
}
}
return INSTANCE;
}
/**
* 优先级高的会覆盖优先级低的
* 运行参数 -> jvm参数 -> 环境变量 -> 配置文件 -> 配置对象对默认值
*/
public Config load(String[] args) {
//配置对象对默认值
config = new Config();
//配置文件
loadFromConfigFile();
//环境变量
loadFromEnv();
//jvm参数
loadFromJvm();
//运行参数
loadFromArgs(args);
return config;
}
private void loadFromArgs(String[] args) {
//--port=1234
Map<String, Object> propertiesMap = new HashMap<>();
Optional.ofNullable(args)
.map(Arrays::stream)
.ifPresent(argsStream ->
argsStream.filter(arg -> arg.startsWith("--") && arg.contains("="))
.map(arg -> arg.split("=", 2))
.forEach(pair -> propertiesMap.put(pair[0].substring(2), pair[1]))
);
PropertiesUtils.properties2Object(propertiesMap, config);
}
private void loadFromJvm() {
Properties properties = System.getProperties();
PropertiesUtils.properties2Object(propertiesToMap(properties), config, JVM_PREFIX);
}
private void loadFromEnv() {
Map<String, String> env = System.getenv();
Map<String, Object> propertiesMap = new HashMap<>(env);
PropertiesUtils.properties2Object(propertiesMap, config, ENV_PREFIX);
}
private void loadFromConfigFile() {
try (InputStream inputStream = ConfigLoader.class.getClassLoader().getResourceAsStream(CONFIG_FILE)) {
if (Objects.isNull(inputStream)) {
return;
}
Properties properties = new Properties();
properties.load(inputStream);
PropertiesUtils.properties2Object(propertiesToMap(properties), config);
} catch (Exception e) {
log.error("Error loading configuration file: {}", CONFIG_FILE, e);
}
}
public Map<String, Object> propertiesToMap(Properties properties) {
return properties.entrySet().stream()
.collect(Collectors.toMap(
e -> String.valueOf(e.getKey()),
Map.Entry::getValue
));
}
}
因为考虑到核心配置类加载器一个只能启动一次,所以需要保证他为单例,采用单例模式,避免安全性问题
有个优化点,这里考虑到传统的 Properties 底层是 hashTab,每个方法都加了 synchronized,目前的环境是单例的,所以采用 hashMap 来优化
通过加载已知环境的属性参数,最后进行反射转为对应的配置类实体
package com.xiaohu.common.utils;
import lombok.extern.slf4j.Slf4j;
import java.beans.*;
import java.lang.reflect.Method;
import java.util.Map;
import java.util.Optional;
import java.util.Properties;
import java.util.stream.Stream;
@Slf4j
public class PropertiesUtils {
/**
* 将属性集合中的属性值批量设置到指定对象的对应属性上。
* @param properties 属性集合,键值对形式,其中键包含属性名可能的前缀。
* @param object 需要设置属性值的目标对象。
* @param prefix 属性名的前缀,用于匹配和从属性集合中选择正确的属性。
*/
public static void properties2Object(final Map<String,Object> properties, final Object object, String prefix) {
try {
BeanInfo beanInfo = Introspector.getBeanInfo(object.getClass());
Stream.of(beanInfo.getPropertyDescriptors())
.filter(pd -> hasProperty(properties, pd.getName(), prefix))
.forEach(pd -> setProperty(object, pd, properties.get(prefix + pd.getName())));
} catch (IntrospectionException e) {
log.warn("properties2Object error:{}", e.getMessage());
}
}
private static boolean hasProperty(Map<String,Object> properties, String propertyName, String prefix) {
return properties.containsKey(prefix + propertyName);
}
/**
* 设置对象的属性值。
*
* @param object 需要设置属性值的目标对象。
* @param pd 属性描述器,描述目标属性的信息。
* @param value 需要设置的属性值。
*/
private static void setProperty(Object object, PropertyDescriptor pd, Object value) {
Optional.ofNullable(PropertyEditorManager.findEditor(pd.getPropertyType()))
.ifPresent(editor -> {
editor.setAsText(value.toString());
try {
pd.getWriteMethod().invoke(object, editor.getValue());
} catch (ReflectiveOperationException e) {
log.warn("setProperty error:{}", e.getMessage());
}
});
}
public static void properties2Object(final Map<String,Object> propertyMap, final Object object) {
properties2Object(propertyMap, object, "");
}
}
运行测试
**可以看到默认的 config 对象里的 port=8888,applicationName=api-gateway,通过在 resurce 目录下创建一个前缀文件 gateway.properties**
gateway.properties
env=prod
registryAddress=124.223.222.174:8848
whenComplete=false
isxxx=xxxx
可以看到相同的属性,优先级高的会覆盖优先级低的,不属于该类的字段不会加载