Spring Cloud Config Server能夠統一管理配置,我們絕大多數情況都是基於git或者svn作為其配置倉庫,其實SpringCloud還可以把數據庫作為配置倉庫,今天我們就來了解一下。順便分析一下其實現原理。
一、PropertySourceLocator接口
1.1、代碼分析
這個接口的作用用於定制化引導配置,通過這個接口我們可以通過代碼動態的向Environment中添加PropertySource,該接口定義如下:

/* * Copyright 2013-2014 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.springframework.cloud.bootstrap.config; import org.springframework.core.env.Environment; import org.springframework.core.env.PropertySource; /** * Strategy for locating (possibly remote) property sources for the Environment. * Implementations should not fail unless they intend to prevent the application from * starting. * * @author Dave Syer * */ public interface PropertySourceLocator { /** * @param environment the current Environment * @return a PropertySource or null if there is none * * @throws IllegalStateException if there is a fail fast condition */ PropertySource<?> locate(Environment environment); }
那么此接口在SpringCloud類引導類PropertySourceBootstrapConfiguration里有處理,核心代碼如下:
@Configuration @EnableConfigurationProperties(PropertySourceBootstrapProperties.class) public class PropertySourceBootstrapConfiguration implements ApplicationContextInitializer<ConfigurableApplicationContext>, Ordered { @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); } } //..... private void insertPropertySources(MutablePropertySources propertySources, CompositePropertySource composite) { MutablePropertySources incoming = new MutablePropertySources(); incoming.addFirst(composite); PropertySourceBootstrapProperties remoteProperties = new PropertySourceBootstrapProperties(); new RelaxedDataBinder(remoteProperties, "spring.cloud.config") .bind(new PropertySourcesPropertyValues(incoming)); if (!remoteProperties.isAllowOverride() || (!remoteProperties.isOverrideNone() && remoteProperties.isOverrideSystemProperties())) { propertySources.addFirst(composite); return; } if (remoteProperties.isOverrideNone()) { propertySources.addLast(composite); return; } if (propertySources .contains(StandardEnvironment.SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME)) { if (!remoteProperties.isOverrideSystemProperties()) { propertySources.addAfter( StandardEnvironment.SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME, composite); } else { propertySources.addBefore( StandardEnvironment.SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME, composite); } } else { propertySources.addLast(composite); } } }
在這里我們可以清楚的看到,首先會獲取所有的PropertySourceLocator,並調用其locate方法,只有當propertySouceLocator有實現類時,它才會獲取當前引導上下文的Environment,並在 insertPropertySources方法里,把PropertySourceLocator的自定義屬性值添加到引導上下文的環境當中。
1.2、代碼示例
代碼目錄結構如下:
在這里注意,自定義實現的PropertySourceLocator是我們的引導程序,因此一定不能被主程序componentScan到
MyTestPropertySourceLocator代碼如下:

package com.bdqn.lyrk.config.bootstrap; import org.springframework.cloud.bootstrap.config.PropertySourceLocator; import org.springframework.context.annotation.Configuration; import org.springframework.core.env.Environment; import org.springframework.core.env.MapPropertySource; import org.springframework.core.env.PropertySource; import java.util.HashMap; import java.util.Map; @Configuration public class MyTestPropertySourceLocator implements PropertySourceLocator { @Override public PropertySource<?> locate(Environment environment) { Map<String, Object> propertySource = new HashMap<>(); propertySource.put("student.name", "admin"); MapPropertySource mapPropertySource = new MapPropertySource("customer", propertySource); return mapPropertySource; } }
spring.factories文件:

org.springframework.cloud.bootstrap.BootstrapConfiguration=\
com.bdqn.lyrk.config.bootstrap.MyTestPropertySourceLocator
ConfigServer:

package com.bdqn.lyrk.config.server; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.config.server.EnableConfigServer; import org.springframework.context.ConfigurableApplicationContext; import org.springframework.core.env.Environment; @SpringBootApplication @EnableConfigServer public class ConfigServer { public static void main(String[] args) { ConfigurableApplicationContext applicationContext = SpringApplication.run(ConfigServer.class, args); Environment environment = applicationContext.getBean(Environment.class); System.out.println(environment); System.out.println(environment.getProperty("student.name")); } }
運行結果如下:
我們可以看到,當我們把自定義的PropertySourceLocator作為引導程序配置時,該接口的locate方法返回值會添加到Environment當中
二、ConfigServer
ConfigServer是配置中心的服務端,它負責統一管理配置,當我們以http://地址:端口號/{application}-{profile}.properties發送請求時會被EnvironmentController處理,我們來看一下EnvironmentController的源碼:
@RestController @RequestMapping(method = RequestMethod.GET, path = "${spring.cloud.config.server.prefix:}") public class EnvironmentController { public EnvironmentController(EnvironmentRepository repository) { this(repository, new ObjectMapper()); } public EnvironmentController(EnvironmentRepository repository, ObjectMapper objectMapper) { this.repository = repository; this.objectMapper = objectMapper; } @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("(_)", "/"); } Environment environment = this.repository.findOne(name, profiles, label); return environment; } @RequestMapping("/{name}-{profiles}.properties") public ResponseEntity<String> properties(@PathVariable String name, @PathVariable String profiles, @RequestParam(defaultValue = "true") boolean resolvePlaceholders) throws IOException { return labelledProperties(name, profiles, null, resolvePlaceholders); } @RequestMapping("/{label}/{name}-{profiles}.properties") public ResponseEntity<String> labelledProperties(@PathVariable String name, @PathVariable String profiles, @PathVariable String label, @RequestParam(defaultValue = "true") boolean resolvePlaceholders) throws IOException { validateProfiles(profiles); Environment environment = labelled(name, profiles, label); Map<String, Object> properties = convertToProperties(environment); String propertiesString = getPropertiesString(properties); if (resolvePlaceholders) { propertiesString = resolvePlaceholders(prepareEnvironment(environment), propertiesString); } return getSuccess(propertiesString); } // .....省略其他代碼 }
在這里的核心代碼是labelled,該方法首先會解析(_)將其替換為/ ,然后調用的EnvironmentRepository的findOne方法。

/* * Copyright 2013-2015 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.springframework.cloud.config.server.environment; import org.springframework.cloud.config.environment.Environment; /** * @author Dave Syer * @author Roy Clarkson */ public interface EnvironmentRepository { Environment findOne(String application, String profile, String label); }
此接口主要是根據application profiles label這三個參數拿到對應的Environment 注意這里的Environment不是Springframework下的Environment接口:

/* * Copyright 2013-2015 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.springframework.cloud.config.environment; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonProperty; /** * Simple plain text serializable encapsulation of a list of property sources. Basically a * DTO for {@link org.springframework.core.env.Environment}, but also applicable outside * the domain of a Spring application. * * @author Dave Syer * @author Spencer Gibb * */ public class 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; public Environment(String name, String... profiles) { this(name, profiles, "master", null, null); } /** * Copies all fields except propertySources * @param env */ public Environment(Environment env) { this(env.getName(), env.getProfiles(), env.getLabel(), env.getVersion(), env.getState()); } @JsonCreator public Environment(@JsonProperty("name") String name, @JsonProperty("profiles") String[] profiles, @JsonProperty("label") String label, @JsonProperty("version") String version, @JsonProperty("state") String state) { super(); this.name = name; this.profiles = profiles; this.label = label; this.version = version; this.state = state; } public void add(PropertySource propertySource) { this.propertySources.add(propertySource); } public void addAll(List<PropertySource> propertySources) { this.propertySources.addAll(propertySources); } public void addFirst(PropertySource propertySource) { this.propertySources.add(0, propertySource); } public List<PropertySource> getPropertySources() { return propertySources; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getLabel() { return label; } public void setLabel(String label) { this.label = label; } public String[] getProfiles() { return profiles; } public void setProfiles(String[] profiles) { this.profiles = profiles; } public String getVersion() { return version; } public void setVersion(String version) { this.version = version; } public String getState() { return state; } public void setState(String state) { this.state = state; } @Override public String toString() { return "Environment [name=" + name + ", profiles=" + Arrays.asList(profiles) + ", label=" + label + ", propertySources=" + propertySources + ", version=" + version + ", state=" + state + "]"; } }
SpringCloud中的Environment類與Springframework的Environment接口相仿,前者中的屬性終將會添加至后者當中,下面我們可以看一下它是怎么實現的
首先我們下找到spring-cloud-config-server-xxx.jar下的spring.factories文件:

# Bootstrap components org.springframework.cloud.bootstrap.BootstrapConfiguration=\ org.springframework.cloud.config.server.bootstrap.ConfigServerBootstrapConfiguration,\ org.springframework.cloud.config.server.config.EncryptionAutoConfiguration # Application listeners org.springframework.context.ApplicationListener=\ org.springframework.cloud.config.server.bootstrap.ConfigServerBootstrapApplicationListener # Autoconfiguration org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ org.springframework.cloud.config.server.config.ConfigServerAutoConfiguration,\ org.springframework.cloud.config.server.config.EncryptionAutoConfiguration
我們可以看到,此處配置了引導類有一個叫:ConfigServerBootstrapConfiguration,我們不妨看看這個引導類:

/* * Copyright 2013-2015 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.springframework.cloud.config.server.bootstrap; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.cloud.config.client.ConfigClientProperties; import org.springframework.cloud.config.server.config.ConfigServerProperties; import org.springframework.cloud.config.server.config.EnvironmentRepositoryConfiguration; import org.springframework.cloud.config.server.config.TransportConfiguration; import org.springframework.cloud.config.server.environment.EnvironmentRepository; import org.springframework.cloud.config.server.environment.EnvironmentRepositoryPropertySourceLocator; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; import org.springframework.util.StringUtils; /** * Bootstrap configuration to fetch external configuration from a (possibly * remote) {@link EnvironmentRepository}. Off by default because it can delay * startup, but can be enabled with * <code>spring.cloud.config.server.bootstrap=true</code>. This would be useful, * for example, if the config server were embedded in another app that wanted to * be configured from the same repository as all the other clients. * * @author Dave Syer * @author Roy Clarkson */ @Configuration @ConditionalOnProperty("spring.cloud.config.server.bootstrap") public class ConfigServerBootstrapConfiguration { @EnableConfigurationProperties(ConfigServerProperties.class) @Import({ EnvironmentRepositoryConfiguration.class, TransportConfiguration.class }) protected static class LocalPropertySourceLocatorConfiguration { @Autowired private EnvironmentRepository repository; @Autowired private ConfigClientProperties client; @Autowired private ConfigServerProperties server; @Bean public EnvironmentRepositoryPropertySourceLocator environmentRepositoryPropertySourceLocator() { return new EnvironmentRepositoryPropertySourceLocator(this.repository, this.client.getName(), this.client.getProfile(), getDefaultLabel()); } private String getDefaultLabel() { if (StringUtils.hasText(this.client.getLabel())) { return this.client.getLabel(); } else if (StringUtils.hasText(this.server.getDefaultLabel())) { return this.server.getDefaultLabel(); } return null; } } }
該引導中裝配了一個EnvironmentRepositoryPropertySourceLocator的類,我們繼續看看這個類:

/* * Copyright 2013-2014 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.springframework.cloud.config.server.environment; import java.util.Map; import org.springframework.cloud.bootstrap.config.PropertySourceLocator; import org.springframework.cloud.config.environment.PropertySource; import org.springframework.core.env.CompositePropertySource; import org.springframework.core.env.Environment; import org.springframework.core.env.MapPropertySource; /** * A PropertySourceLocator that reads from an EnvironmentRepository. * * @author Dave Syer * */ public class EnvironmentRepositoryPropertySourceLocator implements PropertySourceLocator { private EnvironmentRepository repository; private String name; private String profiles; private String label; public EnvironmentRepositoryPropertySourceLocator(EnvironmentRepository repository, String name, String profiles, String label) { this.repository = repository; this.name = name; this.profiles = profiles; this.label = label; } @Override public org.springframework.core.env.PropertySource<?> locate(Environment environment) { CompositePropertySource composite = new CompositePropertySource("configService"); for (PropertySource source : repository.findOne(name, profiles, label) .getPropertySources()) { @SuppressWarnings("unchecked") Map<String, Object> map = (Map<String, Object>) source.getSource(); composite.addPropertySource(new MapPropertySource(source.getName(), map)); } return composite; } }
這個類很明顯實現了PropertySourceLocator接口,在locate方法里會調用EnvironmentRepository的findOne方法,此時會將SpringCloud的Environment類和Spring中的Environment相關聯
三、ConfigClient
當我們添加config-client時,啟動時會去服務端請求遠程的配置進而加載至當前的Environment當中。我們先看一看它的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
根據引導配置,我們去追溯一下ConfigServiceBootstrapConfiguration的源代碼:

/* * Copyright 2013-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.springframework.cloud.config.client; import org.aspectj.lang.annotation.Aspect; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.aop.AopAutoConfiguration; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; import org.springframework.core.env.ConfigurableEnvironment; import org.springframework.retry.annotation.EnableRetry; import org.springframework.retry.annotation.Retryable; import org.springframework.retry.interceptor.RetryInterceptorBuilder; import org.springframework.retry.interceptor.RetryOperationsInterceptor; /** * @author Dave Syer * @author Tristan Hanson * */ @Configuration @EnableConfigurationProperties public class ConfigServiceBootstrapConfiguration { @Autowired private ConfigurableEnvironment environment; @Bean public ConfigClientProperties configClientProperties() { ConfigClientProperties client = new ConfigClientProperties(this.environment); return client; } @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.failFast", matchIfMissing=false) @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(); } } }
與config-server端類似,我們可以發現其裝配了一個ConfigServicePropertySourceLocator的Bean,這里我貼出關鍵代碼部分:
@Order(0) public class ConfigServicePropertySourceLocator implements PropertySourceLocator { private static Log logger = LogFactory .getLog(ConfigServicePropertySourceLocator.class); private RestTemplate restTemplate; private ConfigClientProperties defaultProperties; public ConfigServicePropertySourceLocator(ConfigClientProperties defaultProperties) { this.defaultProperties = defaultProperties; } @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; logger.info("Fetching config from server at: " + properties.getRawUri()); 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) { logger.info(String.format("Located environment: name=%s, profiles=%s, label=%s, version=%s, state=%s", result.getName(), result.getProfiles() == null ? "" : Arrays.asList(result.getProfiles()), result.getLabel(), result.getVersion(), result.getState())); 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", error); } logger.warn("Could not locate PropertySource: " + (errorBody == null ? error==null ? "label not found" : error.getMessage() : errorBody)); return null; } 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(); String uri = properties.getRawUri(); Object[] args = new String[] { name, profile }; if (StringUtils.hasText(label)) { args = new String[] { name, profile, label }; path = path + "/{label}"; } ResponseEntity<Environment> response = null; try { HttpHeaders headers = new HttpHeaders(); if (StringUtils.hasText(token)) { headers.add(TOKEN_HEADER, token); } if (StringUtils.hasText(state)) { //TODO: opt in to sending state? 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; } } if (response == null || response.getStatusCode() != HttpStatus.OK) { return null; } Environment result = response.getBody(); return result; } //。。。省略其他代碼 }
在這里我們可以發現,當client端啟動時,通過RestTemplate請求服務端的EnvironmentController進而添加至當前的Environment
四、使用數據庫作為配置中心的倉庫
我們先看一下自動裝配類:
@Configuration @Profile("jdbc") class JdbcRepositoryConfiguration { @Bean public JdbcEnvironmentRepository jdbcEnvironmentRepository(JdbcTemplate jdbc) { return new JdbcEnvironmentRepository(jdbc); } }
這里面創建了JdbcEnvironmentRepostiory,緊接着我們在看一下這個類的源碼:

/* * Copyright 2016-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.springframework.cloud.config.server.environment; import java.sql.ResultSet; import java.sql.SQLException; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.LinkedHashMap; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Properties; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.cloud.config.environment.Environment; import org.springframework.cloud.config.environment.PropertySource; import org.springframework.core.Ordered; import org.springframework.dao.DataAccessException; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.jdbc.core.ResultSetExtractor; import org.springframework.util.StringUtils; /** * An {@link EnvironmentRepository} that picks up data from a relational database. The * database should have a table called "PROPERTIES" with columns "APPLICATION", "PROFILE", * "LABEL" (with the usual {@link Environment} meaning), plus "KEY" and "VALUE" for the * key and value pairs in {@link Properties} style. Property values behave in the same way * as they would if they came from Spring Boot properties files named * <code>{application}-{profile}.properties</code>, including all the encryption and * decryption, which will be applied as post-processing steps (i.e. not in this repository * directly). * * @author Dave Syer * */ @ConfigurationProperties("spring.cloud.config.server.jdbc") public class JdbcEnvironmentRepository implements EnvironmentRepository, Ordered { private static final String DEFAULT_SQL = "SELECT KEY, VALUE from PROPERTIES where APPLICATION=? and PROFILE=? and LABEL=?"; private int order = Ordered.LOWEST_PRECEDENCE - 10; private final JdbcTemplate jdbc; private String sql = DEFAULT_SQL; private final PropertiesResultSetExtractor extractor = new PropertiesResultSetExtractor(); public JdbcEnvironmentRepository(JdbcTemplate jdbc) { this.jdbc = jdbc; } public void setSql(String sql) { this.sql = sql; } public String getSql() { return this.sql; } @Override public Environment findOne(String application, String profile, String label) { String config = application; if (StringUtils.isEmpty(label)) { label = "master"; } if (StringUtils.isEmpty(profile)) { profile = "default"; } if (!profile.startsWith("default")) { profile = "default," + profile; } String[] profiles = StringUtils.commaDelimitedListToStringArray(profile); Environment environment = new Environment(application, profiles, label, null, null); if (!config.startsWith("application")) { config = "application," + config; } List<String> applications = new ArrayList<String>(new LinkedHashSet<>( Arrays.asList(StringUtils.commaDelimitedListToStringArray(config)))); List<String> envs = new ArrayList<String>(new LinkedHashSet<>(Arrays.asList(profiles))); Collections.reverse(applications); Collections.reverse(envs); for (String app : applications) { for (String env : envs) { Map<String, String> next = (Map<String, String>) jdbc.query(this.sql, new Object[] { app, env, label }, this.extractor); if (!next.isEmpty()) { environment.add(new PropertySource(app + "-" + env, next)); } } } return environment; } @Override public int getOrder() { return order; } public void setOrder(int order) { this.order = order; } } class PropertiesResultSetExtractor implements ResultSetExtractor<Map<String, String>> { @Override public Map<String, String> extractData(ResultSet rs) throws SQLException, DataAccessException { Map<String, String> map = new LinkedHashMap<>(); while (rs.next()) { map.put(rs.getString(1), rs.getString(2)); } return map; } }
我們可以看到該類實現了EnvironmentRepository接口,在findone方法里通過JDBCTemplate來獲取數據庫中的配置信息。
下面我們來修改config-server端代碼
4.1、gradle配置
dependencies { compile('org.springframework.cloud:spring-cloud-config-server') compile('org.springframework.boot:spring-boot-starter-jdbc') compile group: 'mysql', name: 'mysql-connector-java' }
4.2、bootstrap.yml
spring: profiles: active: jdbc application: name: config-server cloud: config: server: jdbc: sql: SELECT `KEY`,`VALUE` FROM PROPERTIES where APPLICATION=? and PROFILE=? and LABEL=? profile: local label: master datasource: url: jdbc:mysql://localhost:3306/MySchool?characterEncoding=utf-8&useSSL=false username: root password: root server: port: 8888
3、DDL腳本
create table PROPERTIES ( ID int auto_increment primary key, `KEY` varchar(32) null, VALUE varchar(32) null, APPLICATION varchar(64) null, PROFILE varchar(32) null, LABEL varchar(16) null, CREATE_DATE datetime null ) CHARSET='utf8' ;
4、驗證結果
五、總結
1.ConfigServer利用了SpringCloud引導機制,當主程序啟動時,通過PropertySourceLocator的方法把相關配置讀到當前的Environment中,同時提供了EnvironmentController使外界能夠根據不同的請求獲取不同格式的配置結果,由於是引導程序是核心,因此務必使用bootstrap.yml(properties)進行配置操作。
2.SpringCloud的客戶端同樣利用引導,通過實現PropertySourceLocator接口在程序啟動前利用RestTemplate訪問ConfigServer獲取到配置並加載到當前Environment中