栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

Spring Cloud Config Client工作原理

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

Spring Cloud Config Client工作原理

说明:本版本的Spring Cloud Config Client的版本为3.0.5

Spring Cloud Config Client实现的依赖关系图大致如下:

 

接下来我们一同探讨Spring Cloud Config Client的实现原理

  1. ConfigServiceBootstrapConfiguration类的自动装配

在spring-cloud-config-client-3.0.5.jar包中的meta-INF的spring.factories文件中包含

# Bootstrap components
org.springframework.cloud.bootstrap.BootstrapConfiguration=
org.springframework.cloud.config.client.ConfigServiceBootstrapConfiguration,
org.springframework.cloud.config.client.DiscoveryClientConfigServiceBootstrapConfiguration

        所以ConfigServiceBootstrapConfiguration类会被Spring Boot默认进行自动加载装配的,在该类中我们看到了按条件加载装配的ConfigServicePropertySourceLocator,代码如下:

@Bean
@ConditionalOnMissingBean(ConfigServicePropertySourceLocator.class)
@ConditionalOnProperty(name = ConfigClientProperties.PREFIX + ".enabled", matchIfMissing = true)
public ConfigServicePropertySourceLocator configServicePropertySource(ConfigClientProperties properties) {
   return new ConfigServicePropertySourceLocator(properties);
}

        当配置文件中配置了spring.cloud.config.enabled为true,或缺失时都会自动装配ConfigServicePropertySourceLocator。从远程配置中心获取配置信息的功能就是在ConfigServicePropertySourceLocator类中实现的。

2. ConfigServicePropertySourceLocator类的功能

ConfigServicePropertySourceLocator类实现了PropertySourceLocator接口,

PropertySourceLocator接口的定义如下:

public interface PropertySourceLocator {

   
   PropertySource locate(Environment environment);

   default Collection> locateCollection(Environment environment) {
      return locateCollection(this, environment);
   }

   static Collection> locateCollection(PropertySourceLocator locator, Environment environment) {
      PropertySource propertySource = locator.locate(environment);
      if (propertySource == null) {
         return Collections.emptyList();
      }
      if (CompositePropertySource.class.isInstance(propertySource)) {
         Collection> sources = ((CompositePropertySource) propertySource).getPropertySources();
         List> filteredSources = new ArrayList<>();
         for (PropertySource p : sources) {
            if (p != null) {
               filteredSources.add(p);
            }
         }
         return filteredSources;
      }
      else {
         return Arrays.asList(propertySource);
      }
   }

}

locate抽象方法用于实现从远程配置中心获取指定的配置信息,Spring Cloud Config中把配置信息抽象为应用(application)、环境(profile)和版本(label)这三个维度进行管理,通过这三个维度,我们就可以确定唯一的一份配置数据。

ConfigServicePropertySourceLocator类中locate方法的实现

public org.springframework.core.env.PropertySource locate(org.springframework.core.env.Environment environment) {
   ConfigClientProperties properties = this.defaultProperties.override(environment);
   CompositePropertySource composite = new OriginTrackedCompositePropertySource("configService");
   ConfigClientRequestTemplateFactory requestTemplateFactory = new ConfigClientRequestTemplateFactory(logger,
         properties);

   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(requestTemplateFactory, label.trim(), state);
         if (result != null) {
            log(result);

            // result.getPropertySources() can be null if using xml
            if (result.getPropertySources() != null) {
               for (PropertySource source : result.getPropertySources()) {
                  @SuppressWarnings("unchecked")
                  Map map = translateOrigins(source.getName(),
                        (Map) source.getSource());
                  composite.addPropertySource(new OriginTrackedMapPropertySource(source.getName(), map));
               }
            }

            HashMap map = new HashMap<>();
            if (StringUtils.hasText(result.getState())) {
               putValue(map, "config.client.state", result.getState());
            }
            if (StringUtils.hasText(result.getVersion())) {
               putValue(map, "config.client.version", result.getVersion());
            }
            // the existence of this property source confirms a successful
            // response from config server
            composite.addFirstPropertySource(new MapPropertySource("configClient", map));
            return composite;
         }
      }
      errorBody = String.format("None of labels %s found", Arrays.toString(labels));
   }
   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: " + (error != null ? error.getMessage() : errorBody));
   return null;

}

该方法的核心是从getRemoteEnvironment方法获取远程配置中心的指定配置内容。

private Environment getRemoteEnvironment(ConfigClientRequestTemplateFactory requestTemplateFactory, String label,
			String state) {
		//获取RestTemplate对象,该对象用于调用Restful接口
		RestTemplate restTemplate = this.restTemplate == null ? requestTemplateFactory.create() : this.restTemplate;
		//获取配置属性
		ConfigClientProperties properties = requestTemplateFactory.getProperties();
		String path = "/{name}/{profile}";
		String name = properties.getName();  //配置文件中的spring.application.name
		String profile = properties.getProfile();  //默认是default
		String token = properties.getToken();
		int noOfUrls = properties.getUri().length;  //获取配置的uri的数量
		if (noOfUrls > 1) {
			logger.info("Multiple Config Server Urls found listed.");
		}
		//构建请求路径
		Object[] args = new String[] { name, profile };
		if (StringUtils.hasText(label)) {
			// workaround for Spring MVC matching / in paths
			label = Environment.denormalize(label);
			args = new String[] { name, profile, label };
			path = path + "/{label}";
		}
		ResponseEntity response = null;
		List acceptHeader = Collections.singletonList(MediaType.parseMediaType(properties.getMediaType()));

		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();
				headers.setAccept(acceptHeader);
				requestTemplateFactory.addAuthorizationToken(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;
	}

上述代码的主要流程就是获取访问配置服务器所需的 application、profile、label 等参数,然后利用 RestTemplate 工具类执行 HTTP 请求。客户端从这个请求所返回的 Environment 对象中获得所需要的各项配置信息。

3.  PropertySourceBootstrapConfiguration类的自动装配

@Configuration(proxyBeanMethods = false)
@EnableConfigurationProperties(PropertySourceBootstrapProperties.class)
public class PropertySourceBootstrapConfiguration
      implements ApplicationContextInitializer, Ordered 

ropertySourceBootstrapProperties类的定义

@ConfigurationProperties("spring.cloud.config")
public class PropertySourceBootstrapProperties

我们从以上类的定义中我们看到当引入spring.cloud.config配置就会自动装配PropertySourceBootstrapConfiguration类,并且该类实现了ApplicationContextInitializer接口。

PropertySourceBootstrapConfiguration加载时会自动获取Spring 容器中的PropertySourceLocator Bean。

@Autowired(required = false)
private List propertySourceLocators = new ArrayList<>();

PropertySourceBootstrapConfiguration 实现了 ApplicationContextInitializer 接口中的 initialize 方法,而所有的 ApplicationContextInitializer 都会在 Spring Boot 应用程序启动时进行加载。这样,当类路径中引入了 Spring Cloud Config 之后,一个 ConfigServicePropertySourceLocator 实例就会被构建并保存在 PropertySourceBootstrapConfiguration 的 propertySources中。然后,我们会遍历所有 propertySourceLocators 的 locate 方法,从而完成对远程服务配置信息的读取。

initialize方法定义如下:

@Override
public void initialize(ConfigurableApplicationContext applicationContext) {
   List> composite = new ArrayList<>();
   AnnotationAwareOrderComparator.sort(this.propertySourceLocators);
   boolean empty = true;
   ConfigurableEnvironment environment = applicationContext.getEnvironment();
   for (PropertySourceLocator locator : this.propertySourceLocators) {
      Collection> source = locator.locateCollection(environment);
      if (source == null || source.size() == 0) {
         continue;
      }
      List> sourceList = new ArrayList<>();
      for (PropertySource p : source) {
         if (p instanceof EnumerablePropertySource) {
            EnumerablePropertySource enumerable = (EnumerablePropertySource) p;
            sourceList.add(new BootstrapPropertySource<>(enumerable));
         }
         else {
            sourceList.add(new SimpleBootstrapPropertySource(p));
         }
      }
      logger.info("Located property source: " + sourceList);
      composite.addAll(sourceList);
      empty = false;
   }
   if (!empty) {
      MutablePropertySources propertySources = environment.getPropertySources();
      String logConfig = environment.resolvePlaceholders("${logging.config:}");
      LogFile logFile = LogFile.get(environment);
      for (PropertySource p : environment.getPropertySources()) {
         if (p.getName().startsWith(BOOTSTRAP_PROPERTY_SOURCE_NAME)) {
            propertySources.remove(p.getName());
         }
      }
      insertPropertySources(propertySources, composite);
      reinitializeLoggingSystem(environment, logConfig, logFile);
      setLogLevels(applicationContext, environment);
      handleIncludedProfiles(environment);
   }
}

        看到调用locateCollection方法获取远程配置中心数据,如果获取的数据不为空,则会调用insertPropertySources方法来注入获取到的配置信息。

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/445560.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号