介绍
微服务化是的我们需要管理的服务增多。同时每一个服务有各自的配置,对于同一个配置来说,开发、应用不同时刻所用的文件不同,这样会导致配置的维护更加的复杂和困难。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内容:
#使用的配置设置 springDemoService-cache.@instanceList=springDemoService-cache springDemoService-db.@instanceList=springDemoService-db springDemoService-service.@instanceList=springDemoService-service springDemoService-consul.@instanceList=springDemoService-consul #服务配置 springDemoService-service.@context=/spring-demo springDemoService-service.@logLevel=DEBUG springDemoService-service.@indexCode=8109ea47-7386-4704-8dce-5a3443cc777f springDemoService-service.webPort=8081 springDemoService-service.@ip=10.16.65.13 #数据库配置 springDemoService-db.1.instance=test springDemoService-db.1.@dbname=test_db springDemoService-db.1.@version=1.1.1 springDemoService-db.1.@type=postgresql springDemoService-db.1.@indexCode=00753ba3-857c-490a-be01-376ac36a82cf springDemoService-db.1.@dbpassword=cG9zdGdyZXM= springDemoService-db.1.@dbusername=test springDemoService-db.1.port=5432 springDemoService-db.1.@ip=localhost #注册中心配置 springDemoService-consul.1.@context=/VIID springDemoService-consul.1.@logLevel=DEBUG springDemoService-consul.1.@indexCode=8109ea47-7386-4704-8dce-5a3443cc777f springDemoService-consul.1.webPort=8068 springDemoService-consul.1.@ip=10.19.155.2 #缓存配置 springDemoService-cache.1.@version=1.2.0 springDemoService-cache.1.@indexCode=11543d67-7824-4c57-b1b7-04d954919b89 springDemoService-cache.1.@type=redislinux64 springDemoService-cache.1.@password=MTIzNDU2 springDemoService-cache.1.@username= springDemoService-cache.1.@ip=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 + ".@instanceList", 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 + ".@context");
CommonTool.setLocalConfigAndMyServiceInstance("server.context-path", bicValue, myServiceInstance::setContext, localConfig, instanceValue + ".@context");
CommonTool.setLocalConfigAndMyServiceInstance("server.servlet.context-path", bicValue, myServiceInstance::setContext, localConfig, instanceValue + ".@context");
bicValue = bicConfigs.getProperty(instanceValue + ".@ip");
CommonTool.setLocalConfigAndMyServiceInstance("server.ip", bicValue, myServiceInstance::setAddress, localConfig, instanceValue + ".@ip");
myServiceInstance.setIndexCode(bicConfigs.getProperty(instanceValue + ".@indexCode"));
}
}
数据库信息解析
兼容了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 + ".@ip"));
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