spring cloud config源碼 client(一)

spring cloud config源碼 client(一)

整體架構模塊

在這裏插入圖片描述

client整體包大體類結構介紹

在這裏插入圖片描述

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<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;

	}

uml圖如下:
ConfigServerLocator uml:
在這裏插入圖片描述

接下來第三個引導類

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<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);
			}
		}
	}

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

}

主要配置說明:1.ConfigserverProvider 這裏通過DiscoveryClient封裝獲取到ConfigServer對應的實例列表。
2.refresh在啓動時被調用:調用的時候會將ConfigclientProperty這個對象裏面的uri轉換成現有的configserver實例對應的homepage “," 隔開的uri。這樣通過

configclient的代碼就上方這麼點,如果要獲取配置就可以通過ConfigServicePropertySourceLocator完成配置,本身這塊代碼也很簡單,後續我們補充下cloud context是怎麼通過這個類完成遠程配置拉取的。

發佈了37 篇原創文章 · 獲贊 12 · 訪問量 8466
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章