通过前面几篇文章我们介绍完了spring cloud config server,我们接下来看看spring cloud config client的实现。从client模块中,我们首先在spring.factories中看到自动配置类ConfigClientAutoConfiguration的引入以及BootstrapConfiguration的2个配置类ConfigServiceBootstrapConfiguration和DiscoveryClientConfigServiceBootstrapConfiguration。
1.ConfigClientAutoConfiguration
进入这个类可以看到它定义了一系列的bean对象注入,我们来看看这些bean初始化都做了些什么.
1).ConfigClientProperties
@Bean public ConfigClientProperties configClientProperties(Environment environment, ApplicationContext context) { if (context.getParent() != null && BeanFactoryUtils.beanNamesForTypeIncludingAncestors( context.getParent(), ConfigClientProperties.class).length > 0) { return BeanFactoryUtils.beanOfTypeIncludingAncestors(context.getParent(), ConfigClientProperties.class); } ConfigClientProperties client = new ConfigClientProperties(environment); return client; }
从实现上可以看到首先是判断是否存在ConfigClientProperties了,如果存在,直接从bean工厂类获取对象,不存在则创建新的ConfigClientProperties对象。进入到ConfigClientProperties类中,可以看到该类是一个spring.cloud.config的prefix配置属性类,用于初始化在配置文件中的属性值。
2).ConfigClientHealthProperties
@Bean public ConfigClientHealthProperties configClientHealthProperties() { return new ConfigClientHealthProperties(); }
进入到ConfigClientHealthProperties类,可以看到该类是一个health.config的prefix配置属性类,用于初始化在配置文件中的属性值。
3).ConfigServerHealthIndicatorConfiguration
@Configuration @ConditionalOnClass(HealthIndicator.class) @ConditionalOnBean(ConfigServicePropertySourceLocator.class) @ConditionalOnProperty(value = "health.config.enabled", matchIfMissing = true) protected static class ConfigServerHealthIndicatorConfiguration { @Bean public ConfigServerHealthIndicator clientConfigServerHealthIndicator( ConfigServicePropertySourceLocator locator, ConfigClientHealthProperties properties, Environment environment) { return new ConfigServerHealthIndicator(locator, environment, properties); } }
从类的实现上首先是要有HealthIndicator类、ConfigServicePropertySourceLocator的bean以及在health.config.enabled为true的情况下才能注入ConfigServerHealthIndicator 的bean,ConfigServerHealthIndicator类继承了boot.actuate.health
的AbstractHealthIndicator抽象类,该类主要用于一些连接的健康监测,对整体的环境变量在超时时的重新缓存。
4).ConfigClientWatchConfiguration
@Configuration @ConditionalOnClass(ContextRefresher.class) @ConditionalOnBean(ContextRefresher.class) @ConditionalOnProperty(value = "spring.cloud.config.watch.enabled") protected static class ConfigClientWatchConfiguration { @Bean public ConfigClientWatch configClientWatch(ContextRefresher contextRefresher) { return new ConfigClientWatch(contextRefresher); } }
从类的实现上看是首先要有ContextRefresher类及它的bean,同时在有spring.cloud.config.watch.enabled配置情况下才能注入ConfigClientWatch 的bean,进入到ConfigClientWatch类,我们可以了解到它定义了一个定时任务watchConfigServer()用于本地刷新缓存
@Scheduled(initialDelayString = "${spring.cloud.config.watch.initialDelay:180000}", fixedDelayString = "${spring.cloud.config.watch.delay:500}") public void watchConfigServer() { if (this.running.get()) { String newState = this.environment.getProperty("config.client.state"); String oldState = ConfigClientStateHolder.getState(); // only refresh if state has changed if (stateChanged(oldState, newState)) { ConfigClientStateHolder.setState(newState); this.refresher.refresh(); } } }
在状态改变的情况下,就会进行refresh的刷新操作,进入到org.springframework.cloud.context.refresh.ContextRefresher
的refresh()方法
public synchronized Set<String> refresh() { Set<String> keys = this.refreshEnvironment(); this.scope.refreshAll(); return keys; }
在具体的可以参考https://www.jianshu.com/p/ed34bff7ca6b上面的一些依赖介绍。
2.ConfigServiceBootstrapConfiguration
该类也是主要用于定义bean的初始化信息,首先它注入了springframework的core包的ConfigurableEnvironment用于初始化环境配置变量,接下来开始定义以下bean
1).ConfigClientProperties
@Bean public ConfigClientProperties configClientProperties() { ConfigClientProperties client = new ConfigClientProperties(this.environment); return client; }
该bean的主要作用是用作获取spring.cloud.config的prefix配置属性类,用于初始化在配置文件中的属性值。
2).ConfigServicePropertySourceLocator
@Bean @ConditionalOnMissingBean(ConfigServicePropertySourceLocator.class) @ConditionalOnProperty(value = "spring.cloud.config.enabled", matchIfMissing = true) public ConfigServicePropertySourceLocator configServicePropertySource(ConfigClientProperties properties) { ConfigServicePropertySourceLocator locator = new ConfigServicePropertySourceLocator( properties); return locator; }
通过上面的代码我们可以看到该bean有效必须在ConfigServicePropertySourceLocator还未定义为bean且spring.cloud.config.enabled的prefix为true的情况下才行,进入到ConfigServicePropertySourceLocator类,它首先通过@Order注解把优先级提到了最高,从代码可以了解到该类主要是用于在连接失败是重新获取配置信息以及解析从配置文件来的信息。我们来具体分析下代码,主要看locate()方法
@Override @Retryable(interceptor = "configServerRetryInterceptor") public org.springframework.core.env.PropertySource<?> locate( org.springframework.core.env.Environment environment) { ConfigClientProperties properties = this.defaultProperties.override(environment); CompositePropertySource composite = new CompositePropertySource("configService"); RestTemplate restTemplate = this.restTemplate == null ? getSecureRestTemplate(properties) : this.restTemplate; Exception error = null; String errorBody = null; try { String[] labels = new String[] { "" }; if (StringUtils.hasText(properties.getLabel())) { labels = StringUtils .commaDelimitedListToStringArray(properties.getLabel()); } String state = ConfigClientStateHolder.getState(); // Try all the labels until one works for (String label : labels) { Environment result = getRemoteEnvironment(restTemplate, properties, label.trim(), state); if (result != null) { log(result); if (result.getPropertySources() != null) { // result.getPropertySources() // can be null if using // xml for (PropertySource source : result.getPropertySources()) { @SuppressWarnings("unchecked") Map<String, Object> map = (Map<String, Object>) source .getSource(); composite.addPropertySource( new MapPropertySource(source.getName(), map)); } } if (StringUtils.hasText(result.getState()) || StringUtils.hasText(result.getVersion())) { HashMap<String, Object> map = new HashMap<>(); putValue(map, "config.client.state", result.getState()); putValue(map, "config.client.version", result.getVersion()); composite.addFirstPropertySource( new MapPropertySource("configClient", map)); } return composite; } } } catch (HttpServerErrorException e) { error = e; if (MediaType.APPLICATION_JSON .includes(e.getResponseHeaders().getContentType())) { errorBody = e.getResponseBodyAsString(); } } catch (Exception e) { error = e; } if (properties.isFailFast()) { throw new IllegalStateException( "Could not locate PropertySource and the fail fast property is set, failing" + (errorBody == null ? "" : ": " + errorBody), error); } logger.warn("Could not locate PropertySource: " + (errorBody == null ? error == null ? "label not found" : error.getMessage() : errorBody)); return null; }
从代码可以看出主要是返回一个CompositePropertySource对象,该类是一个org.springframework.core.env的类,主要用于存储多环境的配置属性,首先是通过以下方法把配置中的变量替换为整体的环境变量
public ConfigClientProperties override( org.springframework.core.env.Environment environment) { ConfigClientProperties override = new ConfigClientProperties(); BeanUtils.copyProperties(this, override); override.setName( environment.resolvePlaceholders("${" + ConfigClientProperties.PREFIX + ".name:${spring.application.name:application}}")); if (environment.containsProperty(ConfigClientProperties.PREFIX + ".profile")) { override.setProfile( environment.getProperty(ConfigClientProperties.PREFIX + ".profile")); } if (environment.containsProperty(ConfigClientProperties.PREFIX + ".label")) { override.setLabel( environment.getProperty(ConfigClientProperties.PREFIX + ".label")); } return override; }
也就是说以整体的环境变量中存在的值为准,然后定义CompositePropertySource名字为configService的对象,之后定义RestTemplate对象,该类是一个URL请求处理类,然后通过String state = ConfigClientStateHolder.getState()获取当前线程的共享变量值信息用来传递给服务端的VaultEnvironmentRepository.findOne()方法里面的用作Environment 状态使用,具体细节不在分析。然后循环label分支,通过调用getRemoteEnvironment()方法获取Environment信息,我们看下getRemoteEnvironment()方法
private Environment getRemoteEnvironment(RestTemplate restTemplate, ConfigClientProperties properties, String label, String state) { 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)) { if (label.contains("/")) { label = label.replace("/", "(_)"); } args = new String[] { name, profile, label }; path = path + "/{label}"; } ResponseEntity<Environment> response = null; for (int i = 0; i < noOfUrls; i++) { 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(); addAuthorizationToken(properties, headers, username, password); if (StringUtils.hasText(token)) { headers.add(TOKEN_HEADER, token); } if (StringUtils.hasText(state) && properties.isSendState()) { headers.add(STATE_HEADER, state); } final HttpEntity<Void> entity = new HttpEntity<>((Void) null, headers); response = restTemplate.exchange(uri + path, HttpMethod.GET, entity, Environment.class, args); } catch (HttpClientErrorException e) { if (e.getStatusCode() != HttpStatus.NOT_FOUND) { throw e; } } catch (ResourceAccessException e) { logger.info("Connect Timeout Exception on Url - " + uri + ". Will be trying the next url if available"); if (i == noOfUrls - 1) throw e; else continue; } if (response == null || response.getStatusCode() != HttpStatus.OK) { return null; } Environment result = response.getBody(); return result; } return null; }
在这段代码中,首先看到的是进行path路径的拼接以及配置服务中心url地址个数的判断,这里只能配置一个url,然后在定义的HttpHeaders里面定义了一些变量信息,之后通过RestTemplate类的exchange()方法去调用服务端的/{name}/{profiles}/{label:.*}这个接口方法,因为这个地方的返回对象是Environment,这个接口通过在服务端里发现是到配置环境去获取配置信息的地方,所以在初始化时就进行了配置信息的获取。
退出getRemoteEnvironment()方法后,如果获取到有值,则循环获取出来存入CompositePropertySource对象中,获取到的值如下截图
然后判断是否存在state或version,如果存在则在CompositePropertySource对象中存入名称为configClient的map集合。
3).RetryOperationsInterceptor
@ConditionalOnProperty(value = "spring.cloud.config.fail-fast") @ConditionalOnClass({ Retryable.class, Aspect.class, AopAutoConfiguration.class }) @Configuration @EnableRetry(proxyTargetClass = true) @Import(AopAutoConfiguration.class) @EnableConfigurationProperties(RetryProperties.class) protected static class RetryConfiguration { @Bean @ConditionalOnMissingBean(name = "configServerRetryInterceptor") public RetryOperationsInterceptor configServerRetryInterceptor( RetryProperties properties) { return RetryInterceptorBuilder .stateless() .backOffOptions(properties.getInitialInterval(), properties.getMultiplier(), properties.getMaxInterval()) .maxAttempts(properties.getMaxAttempts()).build(); } }
从代码的实现来看首先是必须有spring.cloud.config.fail-fast的配置,然后依赖于一些类才能起作用,同时在要启用该bean还必须前面没有生成过该bean,该bean的作用主要就是起一个监控重试获取服务器数据次数的作用。
3.DiscoveryClientConfigServiceBootstrapConfiguration
这个类介绍是通过spring.cloud.config.discovery.enabled
来true时查找服务的,默认是false的,实现了SmartApplicationListener接口,我们看看具体实现。
1).ConfigServerInstanceProvider
@Bean public ConfigServerInstanceProvider configServerInstanceProvider( DiscoveryClient discoveryClient) { return new ConfigServerInstanceProvider(discoveryClient); }
进入ConfigServerInstanceProvider类可以了解到它为每一个DiscoveryClient提供为不可修改的final对象,它提供了一个getConfigServerInstances()方法,通过List<ServiceInstance> instances = this.client.getInstances(serviceId)方法,我们可以看到它是获取一个服务注册发现的实例的过程。
在ConfigServerInstanceProvider类中实现了SmartApplicationListener接口的supportsEventType()和onApplicationEvent()方法,supportsEventType()方法用于判断监听的ContextRefreshedEvent和HeartbeatEvent事件是否继承于ApplicationEvent的实现接口,我们看看onApplicationEvent()方法
public void onApplicationEvent(ApplicationEvent event) { if (event instanceof ContextRefreshedEvent) { startup((ContextRefreshedEvent) event); } else if (event instanceof HeartbeatEvent) { heartbeat((HeartbeatEvent) event); } }
如果发生了ContextRefreshedEvent事件,比如调用spring-cloud-bus里面的端点/actuator/bus-refresh就是产生了ContextRefreshedEvent事件,该事件直接调用startup()方法,而该方法则直接调用refresh()方法
private void refresh() { try { String serviceId = this.config.getDiscovery().getServiceId(); List<String> listOfUrls = new ArrayList<>(); List<ServiceInstance> serviceInstances = this.instanceProvider .getConfigServerInstances(serviceId); for (int i = 0; i < serviceInstances.size(); i++) { ServiceInstance server = serviceInstances.get(i); String url = getHomePage(server); if (server.getMetadata().containsKey("password")) { String user = server.getMetadata().get("user"); user = user == null ? "user" : user; this.config.setUsername(user); String password = server.getMetadata().get("password"); this.config.setPassword(password); } if (server.getMetadata().containsKey("configPath")) { String path = server.getMetadata().get("configPath"); if (url.endsWith("/") && path.startsWith("/")) { url = url.substring(0, url.length() - 1); } url = url + path; } listOfUrls.add(url); } String[] uri = new String[listOfUrls.size()]; uri = listOfUrls.toArray(uri); this.config.setUri(uri); } catch (Exception ex) { if (config.isFailFast()) { throw ex; } else { logger.warn("Could not locate configserver via discovery", ex); } } }
在refresh()方法中可以看到首先去获取配置在配置文件中spring.cloud.config.discovery.serviceId的服务实例id,根据serviceId去调用ConfigServerInstanceProvider提供的getConfigServerInstances()方法获取注册服务的实例对象集合serviceInstances ,最后循环serviceInstances来更新存储在内存中的配置文件中的一些属性值。
如果发生了HeartbeatEvent的心跳包事件,则调用heartbeat()方法,在heartbeat()方法中判断是否有事件改变的状态,有则和ContextRefreshedEvent事件一样调用refresh()方法去修改内存中的一些属性值。
相关推荐
《Spring Cloud项目源码深度解析》 在当前的微服务架构领域,Spring Cloud以其强大的功能和易用性,成为开发者构建分布式系统的重要选择。本文将深入探讨基于Spring Cloud的项目源码,帮助读者理解其核心原理,提升...
### 一、SpringCloud简介与核心组件 #### 1.1 SpringCloud概述 SpringCloud是一套基于Spring Boot实现的微服务云应用开发工具集,它提供了在分布式系统(如配置管理、服务发现、断路器、智能路由、微代理、控制总线...
《Spring Cloud配置源码解析与实战》 Spring Cloud作为微服务架构的重要组件,为开发者提供了在分布式系统(如配置管理、服务发现、断路器、智能路由、微代理、控制总线、一次性令牌、全局锁、领导选举、分布式会话...
本教程将从基础入手,逐步深入到源码解析,使读者能更深入地了解和运用Spring Cloud。 首先,我们从入门开始。Spring Cloud提供了一个快速构建分布式系统工具集,包括服务发现(Eureka)、配置中心(Config Server...
1. **SpringCloud源码解析**: SpringCloud的源码分析有助于开发者了解其实现机制,从而更好地定制和优化自己的服务。源码中包含了Eureka服务发现、Zuul边缘服务、Hystrix断路器、 Ribbon客户端负载均衡、Feign声明...
总的来说,通过尚硅谷的SpringCloud源码解析和思维导图,开发者不仅可以深入了解SpringCloud的运作原理,还能提升对微服务架构设计的理解,为实际项目开发提供有力的支持。同时,对源码的学习也有助于培养解决问题和...
《SpringCloud Config 源码解析与应用实践》 SpringCloud Config 是一个强大的微服务配置中心,它允许我们在运行时管理应用的配置,并且能够实时地推送到客户端。本篇文章将深入探讨 SpringCloud Config 的核心原理...
《SpringCloud源码解析——构建微服务架构的关键组件》 SpringCloud作为当今最热门的微服务框架之一,深受广大开发者喜爱。它集成了众多优秀的开源项目,为开发人员提供了便捷的微服务开发工具。本篇将围绕Spring...
《基于SpringCloud的电商平台源码解析》 在现代互联网应用开发中,微服务架构已经成为主流。Spring Cloud作为一套完整的微服务解决方案,为开发者提供了构建分布式系统所需的工具集合,包括服务发现、配置中心、...
本资源为新手提供了一个完整的SpringCloud入门项目,包括源码、SQL脚本和详细的开发文档,非常适合想要快速了解和学习SpringCloud的新手。 1. **SpringCloud简介** SpringCloud是基于SpringBoot构建的服务治理框架...
《基于SpringCloud的分布式网上商城系统源码解析》 在当今互联网时代,电子商务系统的复杂性和规模日益增大,传统的单体架构难以应对高并发、高可用的业务需求。为了解决这些问题,开发人员开始转向分布式系统架构...
本篇文章将深入探讨“SpringCloud黑马商城后端代码”,解析其中的关键技术和实现细节。 首先,Spring Cloud是基于Spring Boot的一套微服务解决方案,它提供了服务注册与发现、配置中心、API网关、负载均衡、熔断器...
本篇将深入探讨Spring Cloud的核心组件和原理,结合"springcloud学习源码-yuanma.zip"中的源码,为你带来一次全面的学习体验。 首先,我们来了解Spring Cloud的基础知识。Spring Cloud是基于Spring Boot的微服务...
《2020最新版SpringCloud(H版&alibaba)框架开发教程》是一套全面而深入的SpringCloud学习资源,涵盖了从基础到高级的各种技术点。这套教程旨在帮助开发者掌握SpringCloud的核心概念和实践技巧,尤其针对H版及阿里...
《SpringCloud微服务实战》这本书籍的源代码包含了大量的实践示例和配置,旨在帮助读者深入理解并掌握Spring Cloud在实际开发中的应用。Spring Cloud是一个基于Spring Boot实现的服务发现、配置管理和API网关等...
总的来说,这份"Spring Cloud.pdf"学习资料涵盖了Spring Cloud的基础知识、核心组件的使用、源码解析以及实战演示,对于想要深入学习和掌握Spring Cloud的开发者来说是一份宝贵的资源。通过系统学习并实践其中的示例...
源码解析可以帮助我们理解Zuul如何处理请求,以及过滤器链的执行流程。 3. **断路器** - Hystrix是Netflix开源的断路器库,用于防止服务雪崩。当服务调用失败或响应时间过长时,断路器会打开,避免后续请求继续失败...
1. 配置中心:可能使用了Spring Cloud Config来集中管理所有服务的配置,这样可以方便地在不同环境中切换配置。 2. 服务注册与发现:可能使用Eureka或Consul等服务注册与发现组件,确保服务之间的通信。 3. 安全配置...
《疯狂springCloud实战架构》是针对企业级分布式应用开发的一款强大框架——Spring Cloud的深度解析与实战指南。Spring Cloud作为微服务生态中的重要组件,它为开发者提供了在分布式系统(如配置管理、服务发现、...
断路器示意图 SpringCloud Netflix实现了断路器库的名字叫Hystrix. 在微服务架构下,通常会有多个层次的服务调用. 下面是微服架构下, 浏览器端通过API访问后台微服务的一个示意图: hystrix 1 一个微服务的超时...