热门IT资讯网

Spring Cloud 微服务公共配置处理

发表于:2024-11-23 作者:热门IT资讯网编辑
编辑最后更新 2024年11月23日,Spring Cloud Config Server提供了微服务获取配置的功能,这些配置文件(application.yml或者application.properties)通常维护在git或者数据库

Spring Cloud Config Server提供了微服务获取配置的功能,这些配置文件(application.yml或者application.properties)通常维护在git或者数据库中,而且支持通过RefreshScope动态刷新,使用起来还是比较灵活的。但是当微服务越来越多时,会遇到下面几个问题:

  1. 配置文件的敏感数如数据库地址和账号信息,据呈现在每个配置文件中,替换起来需要一个个配置文件进行修改。
  2. 各个微服务配置文件存在很多冗余配置(如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, 返回前进行替换或者拼接,即可实现上述目的。

代码示例:

  1. 拦截器实现

    @Component@Aspectpublic class ResourceLoaderInterceptor {private static Log logger = LogFactory.getLog(ResourceLoaderInterceptor.class);@ResourceExternalProperties 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) propertySource.getSource());                }            }        }    } catch (Throwable throwable) {        logger.error(ExceptionUtils.getStackTrace(throwable));    } finally {        stopWatch.stop();        System.out.println(stopWatch.getTotalTimeMillis());    }    return returnObj;}private void placeHolderResolve(Map source) {    Map 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 collectConfigSet() {    Map 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;}}
  2. 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;    }}
  1. 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;}}
  2. 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的配置中间中增加,调整下拦截器的实现逻辑即可。

0