Spring-Cloud-Config配置中心
Spring Cloud Config为分布式系统中的外部配置提供服务器和客户端支持。使用Config Server,您可以在所有环境中管理应用程序的外部属性。客户端和服务器上的概念映射与Spring Environment
和PropertySource
抽象相同,因此它们与Spring应用程序非常契合,但可以与任何以任何语言运行的应用程序一起使用。随着应用程序通过从开发人员到测试和生产的部署流程,您可以管理这些环境之间的配置,并确定应用程序具有迁移时需要运行的一切。服务器存储后端的默认实现使用git,因此它轻松支持标签版本的配置环境,以及可以访问用于管理内容的各种工具。可以轻松添加替代实现,并使用Spring配置将其插入。
spring cloud config基于http协议的远程配置。为多个文件提供配置服务.通过统一的配置管理服务器进行配置管理,客户端通过https协议主动的拉取服务的的配置信息,完成配置.
spring cloud config可以将配置文件存储在本地,也可以将配置文件存储在git仓库.本文以配置本地文件为例,通过创建Config Server,通过他管理所有配置文件.
本测试实例使用依赖:
Spring Cloud: Finchley.SR2
Spring Cloud Config: 2.0.2.RELEASE
Spring Cloud Bus: 2.0.0.RELEASE
Spring Boot: 2.0.8.RELEASE
注: 依赖版本不能瞎导,否则会有改不完的bug.
文章目录
Config Server 服务端
-
创建maven工程,pom.xml
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.config.demo</groupId> <artifactId>config-demo</artifactId> <packaging>pom</packaging> <version>1.0-SNAPSHOT</version> <modules> <module>config-server</module> <module>config-client</module> </modules> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.0.8.RELEASE</version> <relativePath/> <!-- lookup parent from repository --> </parent> <!-- 管理依赖 --> <dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>Finchley.SR2</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement> </project>
- configServer的配置文件application.yml
#服务端口
server.port=8091
#服务名称
spring.application.name=configServer
#服务注册中心
#eureka.client.service-url.defaultZone=http://localhost:8761/eureka/
#--------------------------------------------------------------------------------
#服务的git仓库地址
#spring.cloud.config.server.git.uri=https://github.com/huyilong1995/bdms-service.git
#配置文件拉去到本地的目录位置
#spring.cloud.config.server.git.basedir=target/config
#配置文件所在的目录
#spring.cloud.config.server.git.search-paths=/**
#git仓库的用户名
#spring.cloud.config.username=
#git仓库的密码
#spring.cloud.config.password=
#--------------------------------------------------------------------------------
#本地化配置file/classpath
spring.profiles.active=native
spring.cloud.config.server.native.search-locations=file:/C:/Users/HIISO/Desktop/config
#spring.cloud.config.server.native.search-locations=classpath:/config
#--------------------------------------------------------------------------------
#使用svn作为配置仓库,必须显示声明profiles.active=subversion
#spring.profiles.active=subversion
#spring.cloud.config.server.svn.uri=https://192.168.9.56/svn/Config_Files/
#spring.cloud.config.server.svn.username=
#spring.cloud.config.server.svn.password=
#spring.cloud.config.server.svn.search-paths=/**
#spring.cloud.config.server.svn.default-label=config
#--------------------------------------------------------------------------------
#rabbitmq配置,用于配置的动态刷新
spring.rabbitmq.host=127.0.0.1
spring.rabbitmq.port=5672
spring.rabbitmq.username=guest
spring.rabbitmq.password=guest
management.endpoints.web.exposure.include=bus-refresh
#是否开启基本的鉴权,默认为true
security.basic.enabled=false
management.security.enabled=false
service.config.ip=127.0.0.1
-
client端拿取得配置文件,本文放在C:/Users/HIISO/Desktop/config下,有两个一个application-test.properties,一个是serviceA-test.properties.
application-test.properties:
server.port=8081
service-test.properties:
age: 28
name: zhangsan
sex: women
- 创建后启动类ConfigServerApplication.java.服务器为外部配置(名称值对或等效的YAML内容)提供了基于资源的HTTP。服务器可以使用
@EnableConfigServer
注释轻松嵌入到Spring Boot应用程序中。
@SpringBootApplication
@EnableConfigServer
public class ConfigServerApplication {
public static void main(String[] args) {
SpringApplication.run(ConfigServerApplication.class,args);
}
}
启动configServer服务,访问http://localhost:8091/serviceA-test.properties,显示结果如下:
age: 28
name: zhangsan
sex: women
证明配置服务中心可以从远程程序获取配置信息.
http请求地址和资源文件映射如下:
/{application}/{profile}[/{label}]
/{application}-{profile}.yml
/{label}/{application}-{profile}.yml
/{application}-{profile}.properties
/{label}/{application}-{profile}.properties
{application} : 客户端properties文件中配置的spring.cloud.config.name,可配置多个,使用","隔开
{profile} : 客户端properties文件中配置的spring.cloud.config.profile
{label} : 客户端properties文件中配置的spring.cloud.config.label
Config Client 客户端
- 创建maven工程.pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>config-demo</artifactId>
<groupId>com.config.demo</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>config-client</artifactId>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-config</artifactId>
<version>2.0.2.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>2.0.8.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
<version>2.0.8.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-bus-amqp</artifactId>
<version>2.0.0.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<version>2.0.8.RELEASE</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
- 创建bootstrap.yml,配置读取本地配置中心的信息
#注意客户端里是没有配置服务的端口的,我们会从仓库中加载application.properties,那里配置了端口
spring.application.name=configClient
#对应前配置文件中的{application}部分,这样会加载多个配置文件,注意的是不同的配置文件里有相同的key会造成属性覆盖
spring.cloud.config.name=application,serviceA
#对应前配置文件中的{profile}部分
spring.cloud.config.enabled=true
spring.cloud.config.profile=test
#配置仓库的分支
#spring.cloud.config.label=config1
#配置中心服务端的地址
spring.cloud.config.uri=http://localhost:8091/
#rabbitmq配置
spring.rabbitmq.host=127.0.0.1
spring.rabbitmq.port=5672
spring.rabbitmq.username=guest
spring.rabbitmq.password=guest
#bus的刷新路径为/bus-refresh
management.endpoints.web.exposure.include=bus-refresh
#启动失败时能够快速响应
spring.cloud.config.fail-fast=true
- 创建启动类
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class ConfigClientApplication {
public static void main(String[] args) {
SpringApplication.run(ConfigClientApplication.class,args);
}
}
- 写一个handle读取配置信息
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RefreshScope
@RestController
public class TestController {
@Value("${name}")
private String name;
@Value("${age}")
private String age;
@Value("${sex}")
private String sex;
@RequestMapping("age")
public String age() {
return age;
}
@RequestMapping("sex")
public String sex() {
return sex;
}
@RequestMapping("name")
public String name() {
return name;
}
}
请求localhost:8081/sex
页面显示:women.
注意:
1、注意客户端里是没有配置服务的端口的,我们会从仓库中加载application.properties,那里配置了端口
2、可以这样spring.cloud.config.name=application,serviceA 加载多个配置文件,注意的是不同的配置文件里有相同的key会造成属性覆盖,后面会覆盖前面的配置
3、spring.cloud.config.label=config1 指定了配置仓库的分支,比如Git的默认分支伟master,这里是分文件夹管理配置
关于spring-cloud-config的刷新功能
- 导入spring-cloud-starter-bus-amqp和spring-boot-starter-actuator包(),具体版本上面已写出
- 本地配置rabbitMQ环境并启动
- 在我们需要刷新的配置类上加上@RefreshScope
- propereties配置文件中加上 management.endpoints.web.exposure.include=bus-refresh(spring-boot2.0之后)
- 调用服务端 http://localhost:8091/bus/refresh 接口刷新配置
这边以配置文件放在本地为例,SVN/本地化配置与Git不相同的是,Git可以配置webhook,当git端配置发生改变,自动调用/bus/refresh接口刷新配置,可以达到真正的自动化配置。完成上述五部即可半自动完成刷新配置
下面我们使SVN和本地化配置达到像Git一样的自动化配置,无需手动调用/bus/refresh接口.
思路: 可以利用commons-io,服务端一启动就开始监听配置仓库的文件,文件发生变化就自动发送一个post请求调用bus/refresh接口,达到自动刷新的效果
- 在服务端导入依赖
<!--commons-io包中有FileAlterationListenerAdaptor类可以监听文件的改动-->
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.4</version>
</dependency>
<!--发送http请求刷新配置文件-->
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>4.5.5</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.7</version>
</dependency>
- 创建ConfigurationListener继承FileAlterationListenerAdaptor
package com.config.server.listenner;
/**
* @author Shen TiePeng
* @description
* @Date 2019/11/12 23:53
*/
import java.io.File;
import javax.annotation.PostConstruct;
import com.config.server.config.ConfigService;
import org.apache.commons.io.monitor.FileAlterationListenerAdaptor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
@Component
public class ConfigurationListener extends FileAlterationListenerAdaptor {
private static ConfigurationListener configurationListener;
@Autowired
private ConfigService configService;
@PostConstruct
public void init() {
configurationListener = this;
configurationListener.configService = this.configService;
}
public File DirContext;
public ConfigurationListener() {
super();
}
public ConfigurationListener(File dirContext) {
super();
DirContext = dirContext;
}
//监听文件夹创建
@Override
public void onDirectoryCreate(File directory) {
configurationListener.configService.refreshConfig();
}
//监听文件夹改变
@Override
public void onDirectoryChange(File directory) {
configurationListener.configService.refreshConfig();
}
//监听文件夹删除
@Override
public void onDirectoryDelete(File directory) {
configurationListener.configService.refreshConfig();
}
//监听文件创建
@Override
public void onFileCreate(File file) {
configurationListener.configService.refreshConfig();
}
//监听文件改变
@Override
public void onFileChange(File file) {
configurationListener.configService.refreshConfig();
}
//监听文件删除
@Override
public void onFileDelete(File file) {
configurationListener.configService.refreshConfig();
}
}
- 创建DsServiceInit实现ApplicationRunner(一启动就开始监听配置仓库的文件)
package com.config.server.listenner;
/**
* @author Shen TiePeng
* @description
* @Date 2019/11/12 23:48
*/
import java.io.File;
import org.apache.commons.io.filefilter.FileFilterUtils;
import org.apache.commons.io.filefilter.IOFileFilter;
import org.apache.commons.io.monitor.FileAlterationMonitor;
import org.apache.commons.io.monitor.FileAlterationObserver;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.stereotype.Component;
@Component
public class DsServiceInit implements ApplicationRunner {
@Value("${spring.cloud.config.server.native.search-locations}")
private String monitorPath;
@Override
public void run(ApplicationArguments args) throws Exception {
//截取要监听的仓库路径
String path = StringUtils.substring(monitorPath, 6);
File dir = new File(path);
FileAlterationMonitor monitor = new FileAlterationMonitor();
IOFileFilter filter = FileFilterUtils.or(FileFilterUtils.directoryFileFilter(),
FileFilterUtils.fileFileFilter());
FileAlterationObserver observer = new FileAlterationObserver(dir, filter);
observer.addListener(new ConfigurationListener(dir));
monitor.addObserver(observer);
try {
monitor.start();
System.out.println("启动配置文件监听……");
} catch (Exception e) {
e.printStackTrace();
}
}
}
- 创建ConfigService(发送http请求)
package com.config.server.config;
/**
* @author Shen TiePeng
* @description
* @Date 2019/11/12 23:54
*/
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
@Component
public class ConfigService {
//这里是自己定义的服务端的ip地址
@Value("${service.config.ip}")
private String serverIP;
@Value("${server.port}")
private String port;
public void refreshConfig() {
CloseableHttpClient httpClient = getHttpClient();
try {
String url = "http://" + serverIP + ":" + port + "/actuator/bus-refresh";
HttpPost post = new HttpPost(url);
post.setHeader("Content-type", "application/json");
post.setHeader("User-Agent", "Mozilla/4.0 (compatible; MSIE 5.0; Windows NT; DigExt)");
System.out.println("send POST 请求...." + post.getURI());
CloseableHttpResponse httpResponse = httpClient.execute(post);
try {
org.apache.http.HttpEntity entity = httpResponse.getEntity();
if (null != entity) {
System.out.println("-------------------------------------------------------");
System.out.println(EntityUtils.toString(entity, "UTF-8"));
}
} finally {
httpResponse.close();
}
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
closeHttpClient(httpClient);
} catch (Exception e) {
e.printStackTrace();
}
}
}
public static CloseableHttpClient getHttpClient() {
return HttpClients.createDefault();
}
public static void closeHttpClient(CloseableHttpClient client) throws IOException {
if (client != null) {
client.close();
}
}
}
这样就完成了配置仓库的监听以及自动刷新的功能,当有配置文件发生变化,服务端会自动发生post请求刷新配置而不需要我们再去手动刷新了
spring-cloud-config server的源码分析
- 首先,查看@EnableConfigServer注解,看到源码里装配了ConfigServerConfiguration的bean.源码如下:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(ConfigServerConfiguration.class)
public @interface EnableConfigServer {
}
- 看下ConfigServerConfiguration类,引入了Maker类,Marker唯一被引用的地方在ConfigServerAutoConfiguration类
@Configuration
public class ConfigServerConfiguration {
class Marker {}
@Bean
public Marker enableConfigServerMarker() {
return new Marker();
}
}
- ConfigServerAutoConfiguration类如下,@ConditionalOnBean(ConfigServerConfiguration.Marker.class)表示当装配了ConfigServerConfiguration.Marker的实例时才会执行ConfigServerAutoConfiguration的处理。这边主要看EnvironmentRepositoryConfiguration类.
@Configuration
@ConditionalOnBean(ConfigServerConfiguration.Marker.class)
@EnableConfigurationProperties(ConfigServerProperties.class)
@Import({ EnvironmentRepositoryConfiguration.class, CompositeConfiguration.class, ResourceRepositoryConfiguration.class,
ConfigServerEncryptionConfiguration.class, ConfigServerMvcConfiguration.class })
public class ConfigServerAutoConfiguration {
}
- EnvironmentRepositoryConfiguration类如下,@Import又引入了7种配置类,刚好对应config server的几种实现方式git的实现方式使用的配置类就是GitRepositoryConfiguration。
@Configuration
@EnableConfigurationProperties({ SvnKitEnvironmentProperties.class,
JdbcEnvironmentProperties.class, NativeEnvironmentProperties.class, VaultEnvironmentProperties.class })
@Import({ CompositeRepositoryConfiguration.class, JdbcRepositoryConfiguration.class, VaultRepositoryConfiguration.class,
SvnRepositoryConfiguration.class, NativeRepositoryConfiguration.class, GitRepositoryConfiguration.class,
DefaultRepositoryConfiguration.class })
public class EnvironmentRepositoryConfiguration {
}
- GitRepositoryConfiguration继承DefaultRepositoryConfiguration ,可以看出GitRepositoryConfiguration是默认的实现方式,DefaultRepositoryConfiguration代码如下,最终是装配一个MultipleJGitEnvironmentRepository的bean,都实现了EnvironmentRepository类,使用findOne方法来查询配置。
@Configuration
@ConditionalOnMissingBean(value = EnvironmentRepository.class, search = SearchStrategy.CURRENT)
class DefaultRepositoryConfiguration {
@Autowired
private ConfigurableEnvironment environment;
@Autowired
private ConfigServerProperties server;
@Autowired(required = false)
private TransportConfigCallback customTransportConfigCallback;
@Bean
public MultipleJGitEnvironmentRepository defaultEnvironmentRepository(
MultipleJGitEnvironmentRepositoryFactory gitEnvironmentRepositoryFactory,
MultipleJGitEnvironmentProperties environmentProperties) throws Exception {
return gitEnvironmentRepositoryFactory.build(environmentProperties);
}
}
- findOne方法在EnvironmentController中的labelled方法中用到,这里的EnvironmentController#repository属性就是GitRepositoryConfiguration实例化的MultipleJGitEnvironmentRepository,如果是别的实现方式就是别的EnvironmentRepository。可以看出”/{name}/{profiles}/{label:.*}”路径参数正好与我们的请求方式相对应,因此Config Server是通过建立一个RestController来接收读取配置请求的,然后使用EnvironmentRepository来进行配置查询,返回一个org.springframework.cloud.config.environment.Environment对象的json串,推测客户端接收时也应该是反序列化为org.springframework.cloud.config.environment.Environment的一个实例。可以看一下Environment的属性定义。
@RequestMapping("/{name}/{profiles}/{label:.*}")
public Environment labelled(@PathVariable String name, @PathVariable String profiles,
@PathVariable String label) {
if (name != null && name.contains("(_)")) {
// "(_)" is uncommon in a git repo name, but "/" cannot be matched
// by Spring MVC
name = name.replace("(_)", "/");
}
if (label != null && label.contains("(_)")) {
// "(_)" is uncommon in a git branch name, but "/" cannot be matched
// by Spring MVC
label = label.replace("(_)", "/");
}
//findOne方法
Environment environment = this.repository.findOne(name, profiles, label);
if(!acceptEmpty && (environment == null || environment.getPropertySources().isEmpty())){
throw new EnvironmentNotFoundException("Profile Not found");
}
return environment;
}
- Environment属性如下:
private String name;
private String[] profiles = new String[0];
private String label;
private List<PropertySource> propertySources = new ArrayList<>();
private String version;
private String state;
spring-cloud-config client的源码分析
- server源码中配置服务器rest接口返回的是Environment的json串,那么client这边反序列化应该也是Environment,搜索spring-cloud-config-client包使用Environment的地方,发现getRemoteEnvironment方法,getRemoteEnvironment的代码主要操作就是拼接一个请求配置地址串,获取所需的ApplicationName,profile,label参数,利用RestTemplate执行http请求,返回的json反序列化为Environment,从而获得所需要的配置信息
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;
}
-
client是在什么时候调用getRemoteEnvironment方法的?在getRemoteEnvironment方法上打断点,调用链路如下
org.springframework.boot.SpringApplication#run(java.lang.String…)
org.springframework.boot.SpringApplication#prepareContext
org.springframework.boot.SpringApplication#applyInitializers
org.springframework.cloud.bootstrap.config.PropertySourceBootstrapConfiguration#initialize
org.springframework.cloud.config.client.ConfigServicePropertySourceLocator#locate
org.springframework.cloud.config.client.ConfigServicePropertySourceLocator#getRemoteEnvironment
-
由调用链路可以知道在spring启动时就会远程加载配置信息,会遍历所有initializer进行一遍操作,PropertySourceBootstrapConfiguration就是其中之一的initializer
protected void applyInitializers(ConfigurableApplicationContext context) {
for (ApplicationContextInitializer initializer : getInitializers()) {
Class<?> requiredType = GenericTypeResolver.resolveTypeArgument(
initializer.getClass(), ApplicationContextInitializer.class);
Assert.isInstanceOf(requiredType, context, "Unable to call initializer.");
initializer.initialize(context);
}
}
- 当引入了spring-cloud-config后PropertySourceBootstrapConfiguration#propertySourceLocators中会新增一个ConfigServicePropertySourceLocator实例。在PropertySourceBootstrapConfiguration#initialize中遍历propertySourceLocators的locate方法,然后读取远程服务配置信息;如果没有引入了spring-cloud-config,那么propertySourceLocators将会是空集合。
@Override
public void initialize(ConfigurableApplicationContext applicationContext) {
CompositePropertySource composite = new CompositePropertySource(
BOOTSTRAP_PROPERTY_SOURCE_NAME);
AnnotationAwareOrderComparator.sort(this.propertySourceLocators);
boolean empty = true;
ConfigurableEnvironment environment = applicationContext.getEnvironment();
for (PropertySourceLocator locator : this.propertySourceLocators) {
PropertySource<?> source = null;
source = locator.locate(environment);
if (source == null) {
continue;
}
logger.info("Located property source: " + source);
composite.addPropertySource(source);
empty = false;
}
if (!empty) {
MutablePropertySources propertySources = environment.getPropertySources();
String logConfig = environment.resolvePlaceholders("${logging.config:}");
LogFile logFile = LogFile.get(environment);
if (propertySources.contains(BOOTSTRAP_PROPERTY_SOURCE_NAME)) {
propertySources.remove(BOOTSTRAP_PROPERTY_SOURCE_NAME);
}
insertPropertySources(propertySources, composite);
reinitializeLoggingSystem(environment, logConfig, logFile);
setLogLevels(applicationContext, environment);
handleIncludedProfiles(environment);
}
}
- propertySourceLocators是直接注入上下文中管理的PropertySourceLocator实例,搜索ConfigServicePropertySourceLocator,发现configServicePropertySource方法装配了一个ConfigServicePropertySourceLocator的bean
@Autowired(required = false)
private List<PropertySourceLocator> propertySourceLocators = new ArrayList<>();
@Configuration
@EnableConfigurationProperties
public class ConfigServiceBootstrapConfiguration {
@Bean
@ConditionalOnMissingBean(ConfigServicePropertySourceLocator.class)
@ConditionalOnProperty(value = "spring.cloud.config.enabled", matchIfMissing = true)
public ConfigServicePropertySourceLocator configServicePropertySource(ConfigClientProperties properties) {
ConfigServicePropertySourceLocator locator = new ConfigServicePropertySourceLocator(
properties);
return locator;
}
//........
}
- spring cloud config client包里面的spring.factories里面引入了ConfigServiceBootstrapConfiguration,就想sprintboot的自动装配一样,程序会自动加载spring.factories里面的配置类.自动实例化一个ConfigServicePropertySourceLocator.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