通过前面几篇文章我们介绍完了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 Setrefresh() { Set 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") Mapmap = (Map ) source .getSource(); composite.addPropertySource( new MapPropertySource(source.getName(), map)); } } if (StringUtils.hasText(result.getState()) || StringUtils.hasText(result.getVersion())) { HashMap 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}"; } ResponseEntityresponse = 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 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
在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(); ListlistOfUrls = new ArrayList<>(); List 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()方法去修改内存中的一些属性值。