spring cloud config源码 client(一)

spring cloud config源码 client(一)

整体架构模块

spring cloud config源码 client(一)_第1张图片

client整体包大体类结构介绍

spring cloud config源码 client(一)_第2张图片

client入口我们先看spring.factories


# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.cloud.config.client.ConfigClientAutoConfiguration
# Bootstrap components
org.springframework.cloud.bootstrap.BootstrapConfiguration=\
org.springframework.cloud.config.client.ConfigServiceBootstrapConfiguration,\
org.springframework.cloud.config.client.DiscoveryClientConfigServiceBootstrapConfiguration


这个文件是自动配置和启动相关的配置文件,spring.factories 仿造的是java的spi机制(这个不是重点)。我们来关注下这两个入口类。先看ConfigClientAutoConfiguration.

ConfigClientAutoConfiguration

整体代码如下:

/**
 * 这个类的主要作用就是配置好自身要拉去远程resource的相关信息,是否开启心跳,及是否开启刷新监控
 **/
/**
 * Expose a ConfigClientProperties just so that there is a way to inspect the properties
 * bound to it. It won't be available in time for autowiring into the bootstrap context,
 * but the values in this properties object will be the same as the ones used to bind to
 * the config server, if there is one.
 *
 * @author Dave Syer
 * @author Marcos Barbero
 *
 */
@Configuration
public class ConfigClientAutoConfiguration {

	/**
	 * 第一个是获取连接到Configserver的相关配置  比如label profile applicaitonname这些属性都在ConfigClientProperties
	 * 如果bootstrap上下文有的话从 bootstrap上下文中获取到这个配置(涉及到bootstrap 和applicaiton区别 ps:bootstrap.yaml是applcation的父上下文)
	 * @param environment
	 * @param context
	 * @return
	 */
	@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;
	}

	/**
	 *  心跳检测相关的配置
	 * @return
	 */
	@Bean
	public ConfigClientHealthProperties configClientHealthProperties() {
		return new ConfigClientHealthProperties();
	}

	/**
	 *  如果引入了actuator的话这里会成立 心跳指示器
	 */
	@Configuration
	@ConditionalOnClass(HealthIndicator.class)
	@ConditionalOnBean(ConfigServicePropertySourceLocator.class)
	@ConditionalOnProperty(value = "health.config.enabled", matchIfMissing = true)
	protected static class ConfigServerHealthIndicatorConfiguration {

		@Bean
		public ConfigServerHealthIndicator configServerHealthIndicator(
				ConfigServicePropertySourceLocator locator,
				ConfigClientHealthProperties properties, Environment environment) {
			return new ConfigServerHealthIndicator(locator, environment, properties);
		}
	}

	@Configuration
	@ConditionalOnClass(ContextRefresher.class)
	@ConditionalOnBean(ContextRefresher.class)
	@ConditionalOnProperty(value = "spring.cloud.config.watch.enabled")
	protected static class ConfigClientWatchConfiguration {
		/**
		 * 起定时任务观察是否发生状态配置变化,如果发现了会调用RefreshScope.refreshAll 完成全量刷新 (带@RefreshScope注解的bean)
		 * config.client.state
		 * @param contextRefresher
		 * @return
		 */
		@Bean
		public ConfigClientWatch configClientWatch(ContextRefresher contextRefresher) {
			return new ConfigClientWatch(contextRefresher);
		}
	}

}

ConfigServiceBootstrapConfiguration

/**
 * 引导启动类
 * @author Dave Syer
 * @author Tristan Hanson
 *
 */
@Configuration
@EnableConfigurationProperties
public class ConfigServiceBootstrapConfiguration {

	@Autowired
	private ConfigurableEnvironment environment;

	/**
	 * 配置要获取到config的相关配置类 bootstrap对应的上下文配置爱configclientprop
	 * @return
	 */
	@Bean
	public ConfigClientProperties configClientProperties() {
		ConfigClientProperties client = new ConfigClientProperties(this.environment);
		return client;
	}

	/**
	 * configService ps定位类 重点拉取远程配置的类 我们在下方贴出重点代码
	 * @param properties
	 * @return
	 */
	@Bean
	@ConditionalOnMissingBean(ConfigServicePropertySourceLocator.class)
	@ConditionalOnProperty(value = "spring.cloud.config.enabled", matchIfMissing = true)
	public ConfigServicePropertySourceLocator configServicePropertySource(ConfigClientProperties properties) {
		//改类内部封装了重试获取远程配置的方法
		ConfigServicePropertySourceLocator locator = new ConfigServicePropertySourceLocator(
				properties);
		return locator;
	}

	@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 {

		/**
		 * 重试拦截器 这里暂时不是重点指导是什么就好
		 * @param properties
		 * @return
		 */
		@Bean
		@ConditionalOnMissingBean(name = "configServerRetryInterceptor")
		public RetryOperationsInterceptor configServerRetryInterceptor(
				RetryProperties properties) {
			return RetryInterceptorBuilder
					.stateless()
					.backOffOptions(properties.getInitialInterval(),
							properties.getMultiplier(), properties.getMaxInterval())
					.maxAttempts(properties.getMaxAttempts()).build();
		}
	}

}



ConfigServicePropertySourceLocator相关的重点代码及注释 这个方法locate获取到配置数据:
大体逻辑是通过configclientprop组装 /{applicaiton}/{profile}/{label} +上configclientprop对应的configserver的uri,然后请求获取配置,拉取配置后存入CompositePropertySource名字是"configService")。(重点这里通过ConfgiClientProperties里面的uir作为homepage,在启用discoveryclient的时候会被替换);

@Override
	@Retryable(interceptor = "configServerRetryInterceptor")
	public org.springframework.core.env.PropertySource locate(
			org.springframework.core.env.Environment environment) {
		//从环境中重新获取覆盖
		ConfigClientProperties properties = this.defaultProperties.override(environment);
		//创建composite 这个类持有一个链表的PropertySource 本身也是PropertySource的一个实现
		CompositePropertySource composite = new CompositePropertySource("configService");
		//请求模板 用于请求Remote
		RestTemplate restTemplate = this.restTemplate == null
				? getSecureRestTemplate(properties)
				: this.restTemplate;
		Exception error = null;
		String errorBody = null;
		try {
			String[] labels = new String[] { "" };
			//把lables 拆出来成数组
			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(springcloud封装的) 从configserver获取到
				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 map = (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;

	}

uml图如下:
ConfigServerLocator uml:
spring cloud config源码 client(一)_第3张图片

接下来第三个引导类

DiscoveryClientConfigServiceBootstrapConfiguration

我们来看下面的注释及对应的代码

**
 * 配置客户端引导配置通过discovery来查找configserver,
 * Bootstrap configuration for a config client that wants to lookup the config server via
 * discovery.
 *
 * @author Dave Syer
 */
@ConditionalOnProperty(value = "spring.cloud.config.discovery.enabled", matchIfMissing = false)
@Configuration
@Import({ UtilAutoConfiguration.class })//主要引入两个类本地网络配置的两个类
@EnableDiscoveryClient
public class DiscoveryClientConfigServiceBootstrapConfiguration {

	private static Log logger = LogFactory
			.getLog(DiscoveryClientConfigServiceBootstrapConfiguration.class);

	@Autowired
	private ConfigClientProperties config; 

	@Autowired
	private ConfigServerInstanceProvider instanceProvider;

	private HeartbeatMonitor monitor = new HeartbeatMonitor();
	//通过注册中心 获取到configserver列表
	@Bean
	public ConfigServerInstanceProvider configServerInstanceProvider(  <1>
			DiscoveryClient discoveryClient) {
		return new ConfigServerInstanceProvider(discoveryClient);
	}

	/**
	 * 监听上下文刷新事件,远程通知的时候会被调用这个事件
	 * @param event
	 */
	@EventListener(ContextRefreshedEvent.class)
	public void startup(ContextRefreshedEvent event) {
		refresh();
	}

	/**
	 * 心跳检测事件
	 * @param event
	 */
	@EventListener(HeartbeatEvent.class)
	public void heartbeat(HeartbeatEvent event) {
		if (monitor.update(event.getValue())) {
			refresh();
		}
	}

	/**
	 * 刷新ConfigClientProperties对应的uri(configserver)  <2>
	 */
	private void refresh() {
		try {
			String serviceId = this.config.getDiscovery().getServiceId();
			List listOfUrls = 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);
			}
		}
	}

	private String getHomePage(ServiceInstance server) {
		return server.getUri().toString() + "/";
	}

}

主要配置说明:1.ConfigserverProvider 这里通过DiscoveryClient封装获取到ConfigServer对应的实例列表。
2.refresh在启动时被调用:调用的时候会将ConfigclientProperty这个对象里面的uri转换成现有的configserver实例对应的homepage “," 隔开的uri。这样通过

configclient的代码就上方这么点,如果要获取配置就可以通过ConfigServicePropertySourceLocator完成配置,本身这块代码也很简单,后续我们补充下cloud context是怎么通过这个类完成远程配置拉取的。

你可能感兴趣的:(springcloud,config)