介紹
微服務化是的我們需要管理的服務增多。同時每一個服務有各自的配置,對于同一個配置來說,開發、應用不同時刻所用的檔案不同,這樣會導緻配置的維護更加的複雜和困難。spring cloud config可以實作統一的配置管理,但是在很多情況下不是很友善。我們需要根據自己的需要來定義的自己的啟動配置。參照spring-cloud的方式,在這裡實作一種自定義的服務啟動配置管理。差別是加入自定義的配置解析方式。
記錄好公共的配置資訊(consul、mq等),以及各個服務私有配置定資訊。公共的配置資訊是不變的, 私有配置定信可以在各個微服務安裝之前就定義好。安裝的時候就告訴配置管理服務,配置管理服務使用界面展示這些資訊,是使用者可配置。在配置成功之後重新開機這些服務即可。
原理
使用PropertiesPropertySource類,在啟動時更改環境變量,并不修改application.*(properties/yml)中的配置。
啟動順序:bootstrap.* > propertiesPropertySource > application.*
優先級:propertiesPropertySource > bootstrap.* > application.*
即同樣的配置項,在propertiesPropertySource和application.properties都配置過的,會優先使用propertiesPropertySource中的配置。
實作
采用spring.factories配置PropertySourceLocator的配置實作類。在程式啟動的時候解析和擷取自定義的配置資訊。首先startupConfiig.xml中定義了項目依賴的配置項,例如資料配置資訊、consul、redis等。而ConfigLocalPropertySourceLocator 中定義了解析startupConfiig.xml内容根據内容的定義去config.properties中擷取相應的配置資訊(上下文路徑、端口、資料配置資訊、consul、redis等)并将資訊加載到項目中。config.properties的配置是預先設定好的,由統一配置管理按照一定的規則生成。當這裡我們也不一定要解析config.properties的内容擷取資訊,也可以通過遠端調用的方式來擷取配置資訊。總之就看實際需要實作即可。
startupConfiig.xml檔案内容:
<?xml version="1.0" encoding="UTF-8"?> <config> <!--實際應用中使用的配置 config.properties中的配置項是 [instances.service].[instance.service].[instance.order] 擷取--> <instances service="springDemoService"> <!-- 服務執行個體相關設定 --> <instance type="service" name="service" order="1"/> <!-- 資料庫相關設定 使用資料庫配置為 db--> <instance type="database" name="db" order="1" dbType=""/> <!-- mq相關設定--> <instance type="rabbitmq" name="rabbitmq" order="1"/> <!-- consul相關設定--> <instance type="consul" name="consul" order="1"/> <!-- 緩存配置--> <instance type="cache" name="cache" order="1"/> </instances> <!--實際使用中的一些參數--> <parameters service="springDemoService"> <!-- envKey:加載到環境中的資料的key值 key:config配置檔案中的字尾 defaultValues:預設值,以“,”隔開, order:取值的順序 --> <parameter envKey="testKey1" key="testKey1" defaultValues="testValue1" order="1"/> <parameter envKey="testKey1" key="testKey2" defaultValues="testValue2" order="1"/> <parameter envKey="testKey1" key="testKey3" defaultValues="testValue3" order="1"/> </parameters> </config>
config.properties内容:
#使用的配置設定 [email protected]=springDemoService-cache [email protected]=springDemoService-db [email protected]=springDemoService-service [email protected]=springDemoService-consul #服務配置 [email protected]=/spring-demo [email protected]=DEBUG [email protected]=8109ea47-7386-4704-8dce-5a3443cc777f springDemoService-service.webPort=8081 [email protected]=10.16.65.13 #資料庫配置 springDemoService-db.1.instance=test [email protected]=test_db [email protected]=1.1.1 [email protected]=postgresql [email protected]=00753ba3-857c-490a-be01-376ac36a82cf [email protected]=cG9zdGdyZXM= [email protected]=test springDemoService-db.1.port=5432 [email protected]=localhost #注冊中心配置 [email protected]=/VIID [email protected]=DEBUG [email protected]=8109ea47-7386-4704-8dce-5a3443cc777f springDemoService-consul.1.webPort=8068 [email protected]=10.19.155.2 #緩存配置 [email protected]=1.2.0 [email protected]=11543d67-7824-4c57-b1b7-04d954919b89 [email protected]=redislinux64 [email protected]=MTIzNDU2 [email protected]= springDemoSe[email protected]=127.0.0.1 springDemoService-cache.1.port=6379
spring.factories配置:
# Bootstrap components org.springframework.cloud.bootstrap.BootstrapConfiguration=\ zhong.test.springbootdemo.usultestdemo.configration.propertiespropertysource.ConfigLocalConfiguration
配置加載:
@Configuration
public class ConfigLocalConfiguration {
public ConfigLocalConfiguration() {
}
@Bean
@ConditionalOnMissingBean({ConfigLocalPropertySourceLocator.class})
public ConfigLocalPropertySourceLocator configLocalPropertySourceLocator() {
return new ConfigLocalPropertySourceLocator();
}
}
實作了程式中依賴的各種配置的擷取方式。
配置是否生效判斷
ConfigLocalPropertySourceLocator的locate(Environment environment)方法中我們需要實作自定義的配置邏輯,
@SneakyThrows
@Override
public PropertySource<?> locate(Environment environment) {
//記錄最終加載到運作環境中的資料
Properties localConfig = new Properties();
if (init()) {
propertyConvertor(localConfig);
} else {
LOGGER.warn("ConfigLocalPropertySourceLocator init failed!");
}
decryConfig(localConfig);
//記錄最終加載到運作環境中的資料
return new PropertiesPropertySource("localConfig", localConfig);
}
其中init()方法是是擷取是否存在startupCoonfig.xml和config.properties檔案存在,如果存在startupCoonfig.xml則繼續解析congig.properties中的内容
/**
* 初始化startupConfig.xml的配置資訊
* 将config.properties的配置資訊加載到變量中。
* @return
*/
boolean init() {
File configDir = null;
if (null == this.startupConfigFile) {
try {
//擷取 startupConfig.xml 的位置
setStartupConfigXml();
//如果配置存在就加載config.properties. 相容本地開發環境,開發時不需要使用 startupConfig.xml 和 config.properties
if(startupConfigFile != null){
//擷取 config.properties 的位置
configDir = getConfigPropertyPath();
//加載config.properties中的資料
loadConfig(configDir);
}
} catch (FileNotFoundException var3) {
LOGGER.error("file don't exists", var3);
System.exit(-1);
} catch (Exception var4) {
LOGGER.error("startup-config init failed", var4);
}
}
return null != this.configroperties;
}
/**
* 加載指定路徑的 config.properties
* @param configDir 檔案
*/
private void loadConfig(File configDir) {
File configFile = null;
configFile = new File(configDir.getAbsolutePath() + "/" + "config.properties");
if (!configFile.exists()) {
try {
configFile = ResourceUtils.getFile("classpath:config.properties");
} catch (IOException e) {
LOGGER.error("config.properties don't exists", e);
System.exit(-1);
}
}
try {
this.propertiesLoad(configFile, "config.properties", this.configroperties);
} catch (Exception e) {
LOGGER.error("load config.properties failed, target key don't exists", e);
System.exit(-1);
}
}
/**
* 擷取 startupConfig.xml 的位置
* @throws Exception 異常
*/
private void setStartupConfigXml() throws Exception {
ClassPathResource startupConfigXmlRes = new ClassPathResource("startupConfig.xml");
try {
this.startupConfigFile = startupConfigXmlRes.getFile();
} catch (IOException var3) {
LOGGER.error("get startupConfig.xml error. program will exit", var3);
throw new Exception("startupConfig.xml does not exists, configuration of application.properties be used");
}
if (!this.startupConfigFile.exists()) {
LOGGER.error("startupConfig.xml does not exist in classpath, configuration of application.properties be used");
throw new Exception("startupConfig.xml does not exists");
} else {
LOGGER.info(String.format("StartupConfig.xml File exists, path=[%s]: ", this.startupConfigFile));
}
}
/**
* 指定 config.properties檔案的位置 , 項目的安裝位置的 /conf/config.properties 開發時可為空
* @return
*/
private File getConfigPropertyPath(){
String userDir = System.getProperty("user.dir");
File serviceDir = new File(userDir);
LOGGER.info(String.format("userDir=[%s]",serviceDir.getAbsolutePath()));
if (serviceDir.exists() && serviceDir.isDirectory()) {
File componentDir;
try {
componentDir = this.getComponentDir(serviceDir);
} catch (NullPointerException var6) {
componentDir = new File(System.getProperty("user.dir"));
}
//生産環境下面的路徑是需要有的。應該預設建立或者安裝的時候建立
if (componentDir.exists() && componentDir.isDirectory()) {
String componentConfigDir = StringUtils.replace(componentDir.getAbsolutePath() + "/conf", "//", "/");
LOGGER.info(String.format("componentConfigDir=[]", componentConfigDir));
File componentConfigFile = new File(componentConfigDir);
if (componentConfigFile.exists() && componentConfigFile.isDirectory()) {
return componentConfigFile;
}
LOGGER.error("componentInstallPath/conf/ don't exists");
} else {
LOGGER.error("componentInstallPath/conf/ don't exists");
}
} else {
LOGGER.error("user.dir don't exists");
}
//走到這裡說明是開發環境
return new File(Thread.currentThread().getContextClassLoader().getResource(".").getPath());
}
/**
* 擷取項目的安裝路徑
* @param serviceDir 服務路徑
* @return
* @throws NullPointerException
*/
private File getInstallDir(File serviceDir) throws NullPointerException {
File componentDir = new File(System.getProperty("user.dir"));
try {
componentDir = serviceDir.getParentFile().getParentFile().getParentFile();
} catch (Exception var4) {
LOGGER.error("get component path error, but the program go on", var4);
}
LOGGER.info(String.format("componentDir=[]", componentDir.getAbsolutePath()));
return componentDir;
}
propertyConverter負責解析startupCoonfig.xml中的内容,包括項目使用配置(上下文路徑、端口、資料配置資訊、consul、redis等),以及自定義的變量内容。
/**
* 解析 startupConfig.xml中的項
* @param localConfig
*/
void propertyConverter(Properties localConfig) {
try {
SAXReader sax = new SAXReader();
Document dom = sax.read(this.startupConfigFile);
Element configRoot = dom.getRootElement();
Iterator iterator = configRoot.elements().iterator();
//解析所有節點
while (iterator.hasNext()) {
Element element = (Element) iterator.next();
String eleName = element.getName();
//各種應用執行個體配置
if (eleName.equals("instances")) {
//依賴配置解析實作
InstanceHandle.handle(element, localConfig, this.configroperties, this.myServiceInstance);
}
//動态參數配置
if(eleName.equals("parameters")){
//配置參數解析實作
ParameterHandle.handle(element, localConfig, this.configroperties);
}
//其他需要可以自定義
}
} catch (DocumentException e) {
LOGGER.error("Fail to parse file config.properties or startupConfig.xml exception", e);
}
}
依賴的服務解析
InstanceHandle 是對項目 上下文路徑、端口、資料配置資訊、consul、redis 的資訊的解析。
/**
* 服務依賴配置資訊解析
* @date 2020/12/16 - 16:23
*/
public class InstanceHandle {
private static final Logger LOGGER = LoggerFactory.getLogger(InstanceHandle.class);
public static void handle(Element insNode, Properties localConfig, Properties configs, ServiceInstance myServiceInstance) {
String servicePrefix = insNode.attributeValue("service");
if (StringUtils.isNotEmpty(servicePrefix)) {
Iterator iterator = insNode.elements().iterator();
while (iterator.hasNext()) {
Element node = (Element) iterator.next();
//擷取 每個 <instance>節點的資料
String insType = node.attributeValue("type");
String insName = node.attributeValue("name");
String keyOrder = node.attributeValue("order");
//配置項
String instancePrefix = servicePrefix + "-" + insName;
//通過配置項擷取配置的字首
String instanceValue = StringUtils.isNotBlank(insName) ? CommonTool.selectInstance(instancePrefix + "[email protected]", keyOrder, configs) : null;
//通過配置項解析每一個種類的配置資料
switch (insType) {
case "consul":
//consul位址配置
TypeConsulHandle.handle(instanceValue, myServiceInstance, keyOrder, localConfig, configs);
break;
case "rabbitmq":
//rabbitmq位址配置
TypeRabbitmqHandle.handle(instanceValue, keyOrder, localConfig, configs);
break;
case "cache":
//redis配置
if (null != instanceValue) {
TypeCacheHandle.handle(instanceValue, configs, keyOrder, localConfig);
} else {
LOGGER.warn(String.format("Lack of instance list name of type=[%s] in config.properties!", insType));
}
break;
case "database":
//資料庫配置
String plugin = node.attributeValue("plugin");
String pgdialect = node.attributeValue("pgdialect");
String oracledialect = node.attributeValue("oracledialect");
if (null != instanceValue) {
TypeDataBaseHandle.handle(localConfig, configs, plugin, instanceValue, keyOrder, pgdialect, oracledialect);
} else {
LOGGER.warn(String.format("Lack of instance list name of type=[%s] in config.properties!", insType));
}
break;
case "service":
//服務配置
System.setProperty("spring.session.redis.namespace", insName);
myServiceInstance.setServiceId(instancePrefix);
if (null != instanceValue) {
TypeServiceHandle.handle(instanceValue, myServiceInstance, localConfig, configs);
} else {
LOGGER.warn(String.format("Lack of instance list name of type=[%s] in config.properties! ", insType));
}
break;
default:
LOGGER.warn(String.format("can't support instance type,type=[%s]: ", insType));
}
}
} else {
LOGGER.error(String.format("cannot loading the service prefix from the node [instances]"));
System.exit(-1);
}
}
}
下面展示項目的配置(端口、上下文路徑,端口等)和資料庫配置
服務配置資訊解析:
public class TypeServiceHandle {
private static final Logger LOGGER = LoggerFactory.getLogger(TypeServiceHandle.class);
public TypeServiceHandle() {
}
public static void handle(String instanceValue, ServiceInstance myServiceInstance, Properties localConfig, Properties bicConfigs) {
String bicValue = null;
myServiceInstance.setInstanceValue(instanceValue);
bicValue = bicConfigs.getProperty(instanceValue + ".webPort");
CommonTool.setLocalConfigAndMyServiceInstance("server.port", bicValue, myServiceInstance::setWebPort, localConfig, instanceValue + ".webPort");
bicValue = bicConfigs.getProperty(instanceValue + "[email protected]");
CommonTool.setLocalConfigAndMyServiceInstance("server.context-path", bicValue, myServiceInstance::setContext, localConfig, instanceValue + "[email protected]");
CommonTool.setLocalConfigAndMyServiceInstance("server.servlet.context-path", bicValue, myServiceInstance::setContext, localConfig, instanceValue + "[email protected]");
bicValue = bicConfigs.getProperty(instanceValue + "[email protected]");
CommonTool.setLocalConfigAndMyServiceInstance("server.ip", bicValue, myServiceInstance::setAddress, localConfig, instanceValue + "[email protected]");
myServiceInstance.setIndexCode(bicConfigs.getProperty(instanceValue + "[email protected]"));
}
}
資料庫資訊解析
相容了oracle和pg的配置,也可以加入mysql等
public class TypeDataBaseHandle {
private static final Logger LOGGER = LoggerFactory.getLogger(TypeDataBaseHandle.class);
public static void handle(Properties localConfig, Properties bicConfigs, String plugin, String dbInstance, String keyOrder, String pgDialect, String oracleDialect) {
String keyPrefix = dbInstance + "." + keyOrder + ".";
String dbtype = bicConfigs.getProperty(keyPrefix + "@type");
String dbIp = CommonTool.getFirstIp(bicConfigs.getProperty(keyPrefix + "@ip"));
String dbPort = bicConfigs.getProperty(keyPrefix + "port");
String dbUsername = bicConfigs.getProperty(keyPrefix + "@dbusername");
String dbPassword = bicConfigs.getProperty(keyPrefix + "@dbpassword");
String dbName = bicConfigs.getProperty(keyPrefix + "@dbname");
//oracle使用
String dbInstanceName = bicConfigs.getProperty(keyPrefix + "instance");
//測試是否可用
CommonTool.portIsAvailableOrExit(dbIp, dbPort, "db");
if (StringUtils.isNoneEmpty(new CharSequence[]{dbtype, dbPort, dbName, dbUsername, dbPassword})) {
localConfig.setProperty("spring.datasource.username", dbUsername);
localConfig.setProperty("spring.datasource.password", CommonTool.decode(dbPassword));
byte dbPlugins;
if (StringUtils.contains(dbtype, "postgresql")) {
//使用pg 資料庫
localConfig.setProperty("spring.datasource.name", "postgresql");
if (StringUtils.isNotBlank(plugin)) {
if (plugin.hashCode() == 102353 && plugin.equals("gis")) {
localConfig.setProperty("spring.jpa.properties.hibernate.dialect", "org.hibernate.spatial.dialect.postgis.PostgisDialect");
} else {
localConfig.setProperty("spring.jpa.properties.hibernate.dialect", "org.hibernate.dialect.PostgreSQL9Dialect");
}
} else {
localConfig.setProperty("spring.jpa.properties.hibernate.dialect", "org.hibernate.dialect.PostgreSQL9Dialect");
}
if (StringUtils.isNotBlank(pgDialect)) {
localConfig.setProperty("spring.jpa.properties.hibernate.dialect", pgDialect);
}
localConfig.setProperty("spring.datasource.url", "jdbc:postgresql://" + dbIp + ":" + dbPort + "/" + dbName);
localConfig.setProperty("spring.datasource.driver-class-name", "org.postgresql.Driver");
localConfig.setProperty("spring.datasource.validationQuery", "SELECT 1");
} else if (StringUtils.contains(dbtype, "oracle")) {
//使用oracle 方式連接配接資料庫
localConfig.setProperty("spring.datasource.name", "oracle");
if (StringUtils.isNotBlank(plugin)) {
if (plugin.equals("gis")) {
localConfig.setProperty("spring.jpa.properties.hibernate.dialect", "org.hibernate.spatial.dialect.oracle.OracleSpatial11gDialect");
} else {
localConfig.setProperty("spring.jpa.properties.hibernate.dialect", "org.hibernate.dialect.Oracle10gDialect");
}
} else {
localConfig.setProperty("spring.jpa.properties.hibernate.dialect", "org.hibernate.dialect.Oracle10gDialect");
}
if (StringUtils.isNotBlank(oracleDialect)) {
localConfig.setProperty("spring.jpa.properties.hibernate.dialect", oracleDialect);
}
localConfig.setProperty("spring.datasource.url", "jdbc:oracle:thin:@" + dbIp + ":" + dbPort + ":" + dbInstanceName);
localConfig.setProperty("spring.datasource.driver-class-name", "oracle.jdbc.OracleDriver");
localConfig.setProperty("spring.datasource.validationQuery", "select 'x' FROM DUAL");
} else {
LOGGER.warn("Error in database @type in config.properties");
}
} else {
LOGGER.warn("Lack of some database configurations in config.properties");
}
LOGGER.debug(String.format("Database configuration: dbUrl=[] : ", localConfig.getProperty("spring.datasource.url", "")));
}
}
consul資訊配置解析
public class TypeConsulHandle {
private static final Logger LOGGER = LoggerFactory.getLogger(TypeConsulHandle.class);
public static void handle(String instanceValue, ServiceInstance myServiceInstance, String keyOrder, Properties localConfig, Properties bicConfigs) {
LOGGER.debug(String.format("Loading consul configurations from config.properties, instance= [%s]", instanceValue));
localConfig.setProperty("spring.cloud.consul.host", bicConfigs.getProperty(instanceValue + "." + keyOrder + "[email protected]"));
localConfig.setProperty("spring.cloud.consul.port", bicConfigs.getProperty(instanceValue + "." + keyOrder + ".webPort"));
localConfig.setProperty("spring.cloud.consul.discovery.preferIpAddress", "true");
if (StringUtils.isNotBlank(myServiceInstance.getAddress())) {
localConfig.setProperty("spring.cloud.consul.discovery.ip-address", CommonTool.getFirstIp(myServiceInstance.getAddress()));
if (StringUtils.isNotBlank(myServiceInstance.getContext())) {
localConfig.setProperty("spring.cloud.consul.discovery.health-check-path", myServiceInstance.getContext() + "/health");
} else {
localConfig.setProperty("spring.cloud.consul.discovery.health-check-path", "/health");
}
} else {
LOGGER.warn("Cannot set consul.discovery.ip-address, please set your service instance in startupConfig.xml");
}
localConfig.setProperty("spring.cloud.consul.discovery.health-check-interval", "20s");
Optional<String> instanceIndexCode = Optional.ofNullable(myServiceInstance.getIndexCode());
instanceIndexCode.ifPresent((s) -> {
localConfig.setProperty("spring.cloud.consul.discovery.instance-id", myServiceInstance.getServiceId() + "-" + s);
});
CommonTool.portIsAvailableOrExit(localConfig.getProperty("spring.cloud.consul.host", ""), localConfig.getProperty("spring.cloud.consul.port", ""), "consul");
}
}
redis資訊配置解析
public class TypeCacheHandle {
private static final Logger LOGGER = LoggerFactory.getLogger(TypeCacheHandle.class);
public TypeCacheHandle() {
}
/**
* 擷取緩存配置資訊
*
* @param instanceValue 緩存字首
* @param bicConfigs 所有配置資訊
* @param keyOrder 使用的配置順序
* @param localConfig 本地配置項
*/
public static void handle(String instanceValue, Properties bicConfigs, String keyOrder, Properties localConfig) {
String bicValue = null;
String keyPrefix = instanceValue + "." + keyOrder + ".";
String ipKey = keyPrefix + "@ip";
bicValue = bicConfigs.getProperty(ipKey);
CommonTool.setLocalConfig("spring.redis.host", CommonTool.getFirstIp(bicValue), localConfig, ipKey);
String portKey = keyPrefix + "port";
bicValue = bicConfigs.getProperty(portKey);
CommonTool.setLocalConfig("spring.redis.port", bicValue, localConfig, portKey);
String pwdKey = keyPrefix + "@password";
bicValue = bicConfigs.getProperty(pwdKey);
CommonTool.setLocalConfig("spring.redis.password", CommonTool.decode(bicValue), localConfig, pwdKey);
CommonTool.setLocalConfig("management.health.redis.enabled", "false", localConfig, "management.health.redis.enabled");
CommonTool.portIsAvailableOrExit(localConfig.getProperty("spring.redis.host", ""), localConfig.getProperty("spring.redis.port", ""), "cache");
}
}
在上面的各種配置中都是解析的是config中有的配置資訊。有時候我們完全不需要配置這些資訊。我們隻需要有一個配置中心提供這些資訊(資料庫、redis、consul、服務上下文和端口、mq)的查詢接口,同時config.properties中記錄了配置中心的ip和端口資訊。而我們需要實作的是根據startupConfi.xml的配置,從config.properties取出服務ip和端口然後調用接口查詢,最後加載到環境中。
依賴配置解析
參數的解析相對簡單,隻要對應解析出key、預設值、取值排序然後組裝。最後放到環境中也可以。這種方式也可以通過遠端從配置中心擷取的方式。也可以提供界面做配置修改,然後儲存到config.properties中,重新開機服務就可以加載到資訊。
/**
* 參數解析
* @date 2020/12/18 - 17:20
*/
public class ParameterHandle {
private static final Logger LOGGER = LoggerFactory.getLogger(InstanceHandle.class);
/**
* 解析各種參數配置
* @param element 參數配置節點
* @param localConfig 加載到環境中的配置項
* @param configs config.properties的值
*/
public static void handle(Element element, Properties localConfig, Properties configs){
Iterator<Element> iterator = element.elements().iterator();
String paramPrefix = element.attributeValue("service");
if(StringUtils.isNotEmpty(paramPrefix)) {
while (iterator.hasNext()) {
Element subElement = iterator.next();
String keyName = subElement.attributeValue("key");
String defaultValues = subElement.attributeValue("defaultValues");
String envKey = subElement.attributeValue("envKey");
int order = StringUtils.isEmpty(subElement.attributeValue("order")) ? 1 : Integer.valueOf(subElement.attributeValue("order"));
String[] values = defaultValues.split(",");
String defaultValue = values.length >= order ? values[order - 1] : null;
setValue(paramPrefix+ "."+ keyName, configs, localConfig, order, defaultValue, envKey);
}
}else {
LOGGER.warn("Fail to get the value of [service] from node [parameters]");
}
}
private static void setValue(String instanceName, Properties localConfig, Properties configs, int order, String defaultValue, String envKey){
String instanceNameValues = configs.getProperty(instanceName + "." + order);
if(StringUtils.isNotEmpty(instanceNameValues)) {
String[] values = instanceNameValues.split(",");
String value = values.length > order ? values[order - 1] : null;
if (StringUtils.isNotEmpty(value)) {
localConfig.setProperty(envKey, value);
} else if (StringUtils.isNotEmpty(defaultValue)) {
localConfig.setProperty(envKey, defaultValue);
} else {
LOGGER.warn(String.format("Value of [%s] order [%s] is null. setting of config.properties is [%s]. default value is [%s]", envKey, order, instanceNameValues, defaultValue));
}
} else if(StringUtils.isNotEmpty(defaultValue)){
localConfig.setProperty(envKey, defaultValue);
} else {
LOGGER.warn(String.format("Value of [%s] order [%s] is null. setting of config.properties is [%s]. default value is [%s]", envKey, order, instanceNameValues, defaultValue));
}
}
}
https://blog.csdn.net/f641385712/article/details/84401629
https://www.cnblogs.com/lizo/p/7683300.html
https://blog.csdn.net/qq_31086797/article/details/107500079
https://www.cnblogs.com/zhangjianbin/p/6322476.html
https://cloud.tencent.com/developer/article/1497673
https://www.imooc.com/article/299972