深入理解 Spring Cloud 的配置抽象

Spring Cloud Config

目标

  • 在分布式系统中,提供一个机制,去管理外置配置,配置变更之后,进行生效,刷新配置

实现

  • 类似于 Spring 应用中的 Environment 与 PropertySource

  • 在上下文中增加 Spring Cloud Config 的 PropertySource

Spring Cloud Config 的 PropertySource

PropertySource

  • Spring Cloud Config Client - 使用 Spring Cloud 自己的 CompositePropertySource
  • Zookeeper - 有对应的 ZookeeperPropertySource
  • Consul - ConsulPropertySource / ConsulFilesPropertySource

这些 不同的 PropertySource 的优先级会高于本身的配置文件对应的PropertySource

PropertySourceLocator

  • 通过 PropertySourceLocator 找到提供 PropertySource,不同的配置中心有不同的 PropertySourceLocator

源码分析

ConfigServicePropertySourceLocator

@Order(0)
public class ConfigServicePropertySourceLocator implements PropertySourceLocator {
    private static Log logger = LogFactory.getLog(ConfigServicePropertySourceLocator.class);
    private RestTemplate restTemplate;
    private ConfigClientProperties defaultProperties;

    public ConfigServicePropertySourceLocator(ConfigClientProperties defaultProperties) {
        this.defaultProperties = defaultProperties;
    }

    @Retryable(
        interceptor = "configServerRetryInterceptor"
    )
    public PropertySource<?> locate(Environment environment) {
        ConfigClientProperties properties = this.defaultProperties.override(environment);
        CompositePropertySource composite = new CompositePropertySource("configService");
        RestTemplate restTemplate = this.restTemplate == null ? this.getSecureRestTemplate(properties) : this.restTemplate;
        Exception error = null;
        String errorBody = null;
   // 设置默认的属性 创建了一个 CompositePropertySource  检测是否存在 restTemplate   没有的话 自己创建一个  
        try {
            String[] labels = new String[]{""};
            if (StringUtils.hasText(properties.getLabel())) {
                labels = StringUtils.commaDelimitedListToStringArray(properties.getLabel());
            }
// 做一个 label 的设置 如果有就设置进去 如果 没有 设置为空
            String state = ConfigClientStateHolder.getState();
            String[] var9 = labels;
            int var10 = labels.length;

            for(int var11 = 0; var11 < var10; ++var11) {  //对不同的 label 做一个遍历   通过 getRemoteEnvironment  用 restTemplate 对应的 properties  去找一个 Environment  如果 找出来 有东西 就从中找出 对应的 PropertySources   这些  PropertySources   每个都取出来之后 加到composite 中 
                String label = var9[var11]; 
                org.springframework.cloud.config.environment.Environment result = this.getRemoteEnvironment(restTemplate, properties, label.trim(), state);
                if (result != null) {
                    this.log(result);
                    if (result.getPropertySources() != null) {
                        Iterator var14 = result.getPropertySources().iterator();

                        while(var14.hasNext()) {
                            org.springframework.cloud.config.environment.PropertySource source = (org.springframework.cloud.config.environment.PropertySource)var14.next();
                            Map<String, Object> map = source.getSource();
                            composite.addPropertySource(new MapPropertySource(source.getName(), map));
                        }
                    }

                    if (StringUtils.hasText(result.getState()) || StringUtils.hasText(result.getVersion())) {
                        HashMap<String, Object> map = new HashMap();
                        this.putValue(map, "config.client.state", result.getState());
                        this.putValue(map, "config.client.version", result.getVersion());
                        composite.addFirstPropertySource(new MapPropertySource("configClient", map));
                    }

                    return composite;
                }
            }
        } catch (HttpServerErrorException var17) {
            error = var17;
            if (MediaType.APPLICATION_JSON.includes(var17.getResponseHeaders().getContentType())) {
                errorBody = var17.getResponseBodyAsString();
            }
        } catch (Exception var18) {
            error = var18;
        }

        if (properties.isFailFast()) {
            throw new IllegalStateException("Could not locate PropertySource and the fail fast property is set, failing" + (errorBody == null ? "" : ": " + errorBody), (Throwable)error);
        } else {
            logger.warn("Could not locate PropertySource: " + (errorBody == null ? (error == null ? "label not found" : ((Exception)error).getMessage()) : errorBody));
            return null;
        }
    }

    private void log(org.springframework.cloud.config.environment.Environment result) {
        if (logger.isInfoEnabled()) {
            logger.info(String.format("Located environment: name=%s, profiles=%s, label=%s, version=%s, state=%s", result.getName(), result.getProfiles() == null ? "" : Arrays.asList(result.getProfiles()), result.getLabel(), result.getVersion(), result.getState()));
        }

        if (logger.isDebugEnabled()) {
            List<org.springframework.cloud.config.environment.PropertySource> propertySourceList = result.getPropertySources();
            if (propertySourceList != null) {
                int propertyCount = 0;

                org.springframework.cloud.config.environment.PropertySource propertySource;
                for(Iterator var4 = propertySourceList.iterator(); var4.hasNext(); propertyCount += propertySource.getSource().size()) {
                    propertySource = (org.springframework.cloud.config.environment.PropertySource)var4.next();
                }

                logger.debug(String.format("Environment %s has %d property sources with %d properties.", result.getName(), result.getPropertySources().size(), propertyCount));
            }
        }

    }

    private void putValue(HashMap<String, Object> map, String key, String value) {
        if (StringUtils.hasText(value)) {
            map.put(key, value);
        }

    }

    private org.springframework.cloud.config.environment.Environment getRemoteEnvironment(RestTemplate restTemplate, ConfigClientProperties properties, String label, String state) {//去拼了一个uri 做了一个调用
        String path = "/{name}/{profile}";
        String name = properties.getName();
        String profile = properties.getProfile();
        String token = properties.getToken();
        int noOfUrls = properties.getUri().length;
        if (noOfUrls > 1) {
            logger.info("Multiple Config Server Urls found listed.");
        }

        Object[] args = new String[]{name, profile};
        if (StringUtils.hasText(label)) { //如果有 label 将 label 带上去
            if (label.contains("/")) {
                label = label.replace("/", "(_)");
            }

            args = new String[]{name, profile, label};
            path = path + "/{label}";
        }

        ResponseEntity<org.springframework.cloud.config.environment.Environment> response = null;
        int i = 0;

        while(true) {
            if (i < noOfUrls) {//用户名密码处理
                Credentials credentials = properties.getCredentials(i);
                String uri = credentials.getUri();
                String username = credentials.getUsername();
                String password = credentials.getPassword();
                logger.info("Fetching config from server at : " + uri);

                try {
                    HttpHeaders headers = new HttpHeaders();
                    this.addAuthorizationToken(properties, headers, username, password);
                    if (StringUtils.hasText(token)) {
                        headers.add("X-Config-Token", token);
                    }

                    if (StringUtils.hasText(state) && properties.isSendState()) {
                        headers.add("X-Config-State", state);
                    }

                    headers.setAccept(Collections.singletonList(MediaType.APPLICATION_JSON));
                    HttpEntity<Void> entity = new HttpEntity((Void)null, headers);
                    response = restTemplate.exchange(uri + path, HttpMethod.GET, entity, org.springframework.cloud.config.environment.Environment.class, args);//做一个http的get请求 取回来的是一个environment对象
                } catch (HttpClientErrorException var19) {
                    if (var19.getStatusCode() != HttpStatus.NOT_FOUND) {
                        throw var19;
                    }
                } catch (ResourceAccessException var20) {
                    logger.info("Connect Timeout Exception on Url - " + uri + ". Will be trying the next url if available");
                    if (i == noOfUrls - 1) {
                        throw var20;
                    }

                    ++i;
                    continue;
                }

                if (response != null && response.getStatusCode() == HttpStatus.OK) {
                    org.springframework.cloud.config.environment.Environment result = (org.springframework.cloud.config.environment.Environment)response.getBody();
                    return result;
                }

                return null;
            }

            return null;
        }
    }

    public void setRestTemplate(RestTemplate restTemplate) {
        this.restTemplate = restTemplate;
    }

    private RestTemplate getSecureRestTemplate(ConfigClientProperties client) {
        SimpleClientHttpRequestFactory requestFactory = new SimpleClientHttpRequestFactory();
        if (client.getRequestReadTimeout() < 0) {
            throw new IllegalStateException("Invalid Value for Read Timeout set.");
        } else {
            requestFactory.setReadTimeout(client.getRequestReadTimeout());
            RestTemplate template = new RestTemplate(requestFactory);
            Map<String, String> headers = new HashMap(client.getHeaders());
            if (headers.containsKey("authorization")) {
                headers.remove("authorization");
            }

            if (!headers.isEmpty()) {
                template.setInterceptors(Arrays.asList(new ConfigServicePropertySourceLocator.GenericRequestHeaderInterceptor(headers)));
            }

            return template;
        }
    }

    private void addAuthorizationToken(ConfigClientProperties configClientProperties, HttpHeaders httpHeaders, String username, String password) {
        String authorization = (String)configClientProperties.getHeaders().get("authorization");
        if (password != null && authorization != null) {
            throw new IllegalStateException("You must set either 'password' or 'authorization'");
        } else {
            if (password != null) {
                byte[] token = Base64Utils.encode((username + ":" + password).getBytes());
                httpHeaders.add("Authorization", "Basic " + new String(token));
            } else if (authorization != null) {
                httpHeaders.add("Authorization", authorization);
            }

        }
    }

    public static class GenericRequestHeaderInterceptor implements ClientHttpRequestInterceptor {
        private final Map<String, String> headers;

        public GenericRequestHeaderInterceptor(Map<String, String> headers) {
            this.headers = headers;
        }

        public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException {
            Iterator var4 = this.headers.entrySet().iterator();

            while(var4.hasNext()) {
                Entry<String, String> header = (Entry)var4.next();
                request.getHeaders().add((String)header.getKey(), (String)header.getValue());
            }

            return execution.execute(request, body);
        }

        protected Map<String, String> getHeaders() {
            return this.headers;
        }
    }
}

PropertySourceLocator

public interface PropertySourceLocator {//实现了 这一个方法  从 Environment 中 找出 所有的 PropertySource
    PropertySource<?> locate(Environment environment);
}

Spring Cloud Config Server

EnvironmentRepository

  • Git / SVN / Vault (加密)/ JDBC …

功能特性

  • SSH、代理访问、配置加密 …

配置刷新

  • 访问 /actuator/refresh 这个 endpoint 进行 刷新
  • 通过了Spring Cloud Bus这个机制 - 使用RefreshRemoteApplicationEvent这个事件 对集群中的所有成员做刷新

Spring Cloud Config Zookeeper

ZookeeperConfigBootstrapConfiguration

  • 注册 ZookeeperPropertySourceLocator
    • 提供 ZookeeperPropertySource

  • ZookeeperConfigAutoConfiguration
    • 注册 ConfigWatcher 去监听配置的节点 如果 配置项发生变化 去刷新内存中的值

源码分析

ZookeeperConfigBootstrapConfiguration

@Configuration
@ConditionalOnZookeeperEnabled
@Import({ZookeeperAutoConfiguration.class})
public class ZookeeperConfigBootstrapConfiguration {
    public ZookeeperConfigBootstrapConfiguration() {
    }

    @Bean
    @ConditionalOnMissingBean
    public ZookeeperPropertySourceLocator zookeeperPropertySourceLocator(CuratorFramework curator, ZookeeperConfigProperties properties) {//注册了一个 zookeeperPropertySourceLocator
        return new ZookeeperPropertySourceLocator(curator, properties);
    }

    @Bean
    @ConditionalOnMissingBean
    public ZookeeperConfigProperties zookeeperConfigProperties() {
        return new ZookeeperConfigProperties();
    }
}

ZookeeperPropertySourceLocator

public class ZookeeperPropertySourceLocator implements PropertySourceLocator {
    private ZookeeperConfigProperties properties;
    private CuratorFramework curator;
    private List<String> contexts;
    private static final Log log = LogFactory.getLog(ZookeeperPropertySourceLocator.class);

    public ZookeeperPropertySourceLocator(CuratorFramework curator, ZookeeperConfigProperties properties) {
        this.curator = curator;
        this.properties = properties;
    }

    public List<String> getContexts() {
        return this.contexts;
    }

    public PropertySource<?> locate(Environment environment) {
        if (!(environment instanceof ConfigurableEnvironment)) {
            return null;
        } else {
            ConfigurableEnvironment env = (ConfigurableEnvironment)environment;
            String appName = env.getProperty("spring.application.name"); //从Zookeeper中 取出应用名 profiles 
            if (appName == null) {
                appName = "application";
                log.warn("spring.application.name is not set. Using default of 'application'");
            }

            List<String> profiles = Arrays.asList(env.getActiveProfiles());
            String root = this.properties.getRoot();
            this.contexts = new ArrayList();
            String defaultContext = root + "/" + this.properties.getDefaultContext();//取出相关信息
            this.contexts.add(defaultContext);
            this.addProfiles(this.contexts, defaultContext, profiles);
            StringBuilder baseContext = new StringBuilder(root);
            if (!appName.startsWith("/")) {
                baseContext.append("/");
            }

            baseContext.append(appName);
            this.contexts.add(baseContext.toString());
            this.addProfiles(this.contexts, baseContext.toString(), profiles);
            CompositePropertySource composite = new CompositePropertySource("zookeeper");
            Collections.reverse(this.contexts);
            Iterator var9 = this.contexts.iterator();

            while(var9.hasNext()) {
                String propertySourceContext = (String)var9.next();

                try { //遍历 取出之后 创建PropertySource 
                    PropertySource propertySource = this.create(propertySourceContext);
                    composite.addPropertySource(propertySource);//创建完了之后 加到composite中 	 
                } catch (Exception var12) {
                    if (this.properties.isFailFast()) {
                        ReflectionUtils.rethrowRuntimeException(var12);
                    } else {
                        log.warn("Unable to load zookeeper config from " + propertySourceContext, var12);
                    }
                }
            }

            return composite;
        }
    }

    @PreDestroy
    public void destroy() {
    }

    private PropertySource<CuratorFramework> create(String context) {//使用传进来的context创建
        return new ZookeeperPropertySource(context, this.curator);
    }

    private void addProfiles(List<String> contexts, String baseContext, List<String> profiles) {
        Iterator var4 = profiles.iterator();

        while(var4.hasNext()) {
            String profile = (String)var4.next();
            contexts.add(baseContext + this.properties.getProfileSeparator() + profile);
        }

    }
}

ZookeeperConfigAutoConfiguration

@Configuration
@ConditionalOnZookeeperEnabled
@ConditionalOnProperty(
    value = {"spring.cloud.zookeeper.config.enabled"},
    matchIfMissing = true
)
public class ZookeeperConfigAutoConfiguration {
    public ZookeeperConfigAutoConfiguration() {
    }

    @Configuration
    @ConditionalOnClass({RefreshEndpoint.class})
    protected static class ZkRefreshConfiguration {
        protected ZkRefreshConfiguration() {
        }

        @Bean
        @ConditionalOnProperty(
            name = {"spring.cloud.zookeeper.config.watcher.enabled"},
            matchIfMissing = true
        )//默认是开启的
        public ConfigWatcher configWatcher(ZookeeperPropertySourceLocator locator, CuratorFramework curator) {
            return new ConfigWatcher(locator.getContexts(), curator);
        }
    }
}

ConfigWatcher

public class ConfigWatcher implements Closeable, TreeCacheListener, ApplicationEventPublisherAware {
    private static final Log log = LogFactory.getLog(ConfigWatcher.class);
    private AtomicBoolean running = new AtomicBoolean(false);
    private List<String> contexts;
    private CuratorFramework source;
    private ApplicationEventPublisher publisher;
    private HashMap<String, TreeCache> caches;

    public ConfigWatcher(List<String> contexts, CuratorFramework source) {
        this.contexts = contexts;
        this.source = source;
    }

    public void setApplicationEventPublisher(ApplicationEventPublisher publisher) {
        this.publisher = publisher;
    }

    @PostConstruct
    public void start() {  //在ConfigWatcher创建完之后  start做了个监听 
        if (this.running.compareAndSet(false, true)) {
            this.caches = new HashMap();
            Iterator var1 = this.contexts.iterator();

            while(var1.hasNext()) {
                String context = (String)var1.next();
                if (!context.startsWith("/")) {
                    context = "/" + context;
                }

                try {//创建一个 TreeCache 把自己作为一个监听器加入进去  
                    TreeCache cache = TreeCache.newBuilder(this.source, context).build();
                    cache.getListenable().addListener(this);
                    cache.start();
                    this.caches.put(context, cache);
                } catch (NoNodeException var4) {
                    ;
                } catch (Exception var5) {
                    log.error("Error initializing listener for context " + context, var5);
                }
            }
        }

    }

    public void close() {
        if (this.running.compareAndSet(true, false)) {
            Iterator var1 = this.caches.values().iterator();

            while(var1.hasNext()) {
                TreeCache cache = (TreeCache)var1.next();
                cache.close();
            }

            this.caches = null;
        }

    }

    public void childEvent(CuratorFramework client, TreeCacheEvent event) throws Exception {//收到 TreeCacheEvent 时 如果有增加删除更新节点的操作 它就会 使用publishEvent   创建一个RefreshEvent
        Type eventType = event.getType();
        if (eventType == Type.NODE_ADDED || eventType == Type.NODE_REMOVED || eventType == Type.NODE_UPDATED) {
            this.publisher.publishEvent(new RefreshEvent(this, event, this.getEventDesc(event)));
        }

    }

    public String getEventDesc(TreeCacheEvent event) {
        StringBuilder out = new StringBuilder();
        out.append("type=").append(event.getType());
        out.append(", path=").append(event.getData().getPath());
        byte[] data = event.getData().getData();
        if (data != null && data.length > 0) {
            out.append(", data=").append(new String(data, Charset.forName("UTF-8")));
        }

        return out.toString();
    }
}

RefreshEvent

public class RefreshEvent extends ApplicationEvent {
    private Object event;
    private String eventDesc;

    public RefreshEvent(Object source, Object event, String eventDesc) {
        super(source);
        this.event = event;
        this.eventDesc = eventDesc;
    }

    public Object getEvent() {
        return this.event;
    }

    public String getEventDesc() {
        return this.eventDesc;
    }
}

RefreshEventListener

public class RefreshEventListener implements SmartApplicationListener {
    private static Log log = LogFactory.getLog(RefreshEventListener.class);
    private ContextRefresher refresh;
    private AtomicBoolean ready = new AtomicBoolean(false);

    public RefreshEventListener(ContextRefresher refresh) {
        this.refresh = refresh;
    }

    public boolean supportsEventType(Class<? extends ApplicationEvent> eventType) {
        return ApplicationReadyEvent.class.isAssignableFrom(eventType) || RefreshEvent.class.isAssignableFrom(eventType);
    }

    public void onApplicationEvent(ApplicationEvent event) {
        if (event instanceof ApplicationReadyEvent) {
            this.handle((ApplicationReadyEvent)event);
        } else if (event instanceof RefreshEvent) {
            this.handle((RefreshEvent)event);
        }

    }

    public void handle(ApplicationReadyEvent event) {
        this.ready.compareAndSet(false, true);
    }

    public void handle(RefreshEvent event) {//在这里 就会去刷新RefreshEvent  和endpoint是一样的
        if (this.ready.get()) {
            log.debug("Event received " + event.getEventDesc());
            Set<String> keys = this.refresh.refresh();
            log.info("Refresh keys changed: " + keys);
        }

    }
}

配置的组合顺序

  • 应用名-profile.yml
  • 应用名.yml
  • application-profile.yml
  • application.yml
    优先级 从上到下 依次递减
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值