Spring Cloud Config Server提供了微服務獲取配置的功能,這些配置文件(application.yml或者application.properties)通常維護在git或者數據庫中,而且支持通過RefreshScope動態刷新,使用起來還是比較靈活的。但是當微服務越來越多時,會遇到下面幾個問題:
- 配置文件的敏感數如數據庫地址和賬號信息,據呈現在每個配置文件中,替換起來需要一個個配置文件進行修改。
- 各個微服務配置文件存在很多冗餘配置(如Eureka,Feign),一旦這些部分調整,需要針對每個微服務進行調整,運維壓力大增。
爲了解決上述問題,我們可以從configServer服務着手進行改造,示意如下:
不同的服務ABC,不管是在配置中心倉庫端配置了多少個文件,從ConfigServer返回的,一定是服務最終應用的配置。獲取配置的方式,通常是調用ConfigServer的一個地址,如:
http://localhost:8021/common_rent/dev/aliyun_dev
common_rent是application name,dev是profile,aliyun_dev是label(git的分支)。這個地址的處理接口,是ConfigServer的EnvironmentController,所以通過攔截這個接口,將敏感信息或者公共配置抽取到configServer的application.yml, 返回前進行替換或者拼接,即可實現上述目的。
代碼示例:
-
攔截器實現
@Component @Aspect public class ResourceLoaderInterceptor { private static Log logger = LogFactory.getLog(ResourceLoaderInterceptor.class); @Resource ExternalProperties externalProperties; @Around("execution(* org.springframework.cloud.config.server..*Controller.*(..)) ") public Object commonPropertiesResolve(ProceedingJoinPoint joinPoint) throws Throwable { Object returnObj = null; Object[] args = joinPoint.getArgs(); StopWatch stopWatch = new StopWatch(); try { stopWatch.start(); returnObj = joinPoint.proceed(args); if (Environment.class.isInstance(returnObj)) { Environment environment = (Environment) returnObj; if (environment.getPropertySources() != null && environment.getPropertySources().size() > 0) { for (PropertySource propertySource : environment.getPropertySources()) { placeHolderResolve((Map<String, Object>) propertySource.getSource()); } } } } catch (Throwable throwable) { logger.error(ExceptionUtils.getStackTrace(throwable)); } finally { stopWatch.stop(); System.out.println(stopWatch.getTotalTimeMillis()); } return returnObj; } private void placeHolderResolve(Map<String, Object> source) { Map<String, Object> placeHolders = collectConfigSet(); for (String key : source.keySet()) { Object value = source.get(key); Object valueAfterReplace = null; if (value != null) { if (String.class.isInstance(value) && ((String) value).contains("${ext.")) { String varExp = (String) value; for (String variable : placeHolders.keySet()) { String vk = "${" + variable + "}"; if (varExp.contains(vk)) { Object replaceValue = placeHolders.get(variable); if (replaceValue != null) { if (varExp.equalsIgnoreCase(vk)) { valueAfterReplace = replaceValue; break; } else { varExp = StringUtils.replace(varExp, vk, "" + replaceValue); if (!varExp.contains("${")) { break; } } } else { logger.error("Property " + vk + " is not properly configured!"); } } } if (valueAfterReplace != null) { source.put(key, valueAfterReplace); } else if (varExp.contains("${")) { logger.error("Property " + varExp + " is not properly configured!"); } else { source.put(key, varExp); } } } } } private Map<String, Object> collectConfigSet() { Map<String, Object> placeHolders = new HashMap<>(); Field[] fields = ExternalProperties.class.getDeclaredFields(); for (int i = 0; i < fields.length; i++) { try { Field propertiesField = fields[i]; ResourcePrefix resourcePrefix = propertiesField.getAnnotation(ResourcePrefix.class); String prefix = resourcePrefix.value(); ExtDataSource extDataSource = (ExtDataSource) BeanUtils.getPropertyDescriptor(ExternalProperties.class, propertiesField.getName()).getReadMethod().invoke(externalProperties); if (extDataSource != null) { Field[] fields2 = ExtDataSource.class.getDeclaredFields(); for (Field datasourceField : fields2) { try { ResourcePrefix annotation = datasourceField.getAnnotation(ResourcePrefix.class); String suffix = annotation.value(); Object sourceFieldValue = BeanUtils.getPropertyDescriptor(ExtDataSource.class, datasourceField.getName()).getReadMethod().invoke(extDataSource); if (sourceFieldValue != null) { placeHolders.put(prefix + "." + suffix, sourceFieldValue); } } catch (Exception e) { logger.error(ExceptionUtils.getStackTrace(e)); } } } } catch (Exception e) { logger.error(ExceptionUtils.getStackTrace(e)); } } return placeHolders; } }
- ExternalProperites實現
@ConfigurationProperties(prefix = "external", ignoreUnknownFields = true)
public class ExternalProperties implements Serializable {
@ResourcePrefix(value = "ext.spring.datasource")
private ExtDataSource datasource;
@ResourcePrefix(value = "ext.spring.data.mongodb")
private ExtDataSource mongodb;
@ResourcePrefix(value = "ext.spring.redis")
private ExtDataSource redis;
@ResourcePrefix(value = "ext.spring.rabbitmq")
private ExtDataSource rabbitmq;
public ExtDataSource getDatasource() {
return datasource;
}
public void setDatasource(ExtDataSource datasource) {
this.datasource = datasource;
}
public ExtDataSource getRabbitmq() {
return rabbitmq;
}
public void setRabbitmq(ExtDataSource rabbitmq) {
this.rabbitmq = rabbitmq;
}
public ExtDataSource getMongodb() {
return mongodb;
}
public void setMongodb(ExtDataSource mongodb) {
this.mongodb = mongodb;
}
public ExtDataSource getRedis() {
return redis;
}
public void setRedis(ExtDataSource redis) {
this.redis = redis;
}
}
-
ExtDataSource實現
public class ExtDataSource { @ResourcePrefix(value = "host") private String host; @ResourcePrefix(value = "port") private Integer port; @ResourcePrefix(value = "url") private String url; @ResourcePrefix(value = "uri") private String uri; @ResourcePrefix(value = "username") private String userName; @ResourcePrefix(value = "password") private String password; public String getUrl() { return url; } public void setUrl(String url) { this.url = url; } public String getHost() { return host; } public void setHost(String host) { this.host = host; } public Integer getPort() { return port; } public void setPort(Integer port) { this.port = port; } public String getUri() { return uri; } public void setUri(String uri) { this.uri = uri; } public String getUserName() { return userName; } public void setUserName(String userName) { this.userName = userName; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } }
- ResourcePrefix實現
@Target({ElementType.FIELD, ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) public @interface ResourcePrefix { String value(); }
然後在configServer的application.yml中增加相關信息,如
external:
datasource:
host: 122.122.111.111
port: 3307
userName: usr
password: pwd
mongodb:
host: 122.122.111.111
port: 20467
uri: 122.122.111.111:20467,122.122.111.112:20467,122.122.111.112:20467
userName: usr
password: pwd
redis:
uri: 122.122.111.113:6379,122.122.112.113:6379,122.122.111.113:6379
password: redispassword
rabbitmq:
host: 122.122.111.113
port: 20467
userName: usr
password: pwd
將ServiceA的配置文件serviceA_dev.yml中的數據庫相關信息替換成變量,以mysql爲例
spring.datasource.uri: url: jdbc:mysql://#{ext.spring.datasource.host}:#{ext.spring.datasource.port}/dbName?useUnicode=true&characterEncoding=utf8,
serviceB和serviceC配置文件做同樣處理,即可實現一次性替換。
後續如果需要增加公共配置,可以直接在ConfigServer的配置中間中增加,調整下攔截器的實現邏輯即可。