深入理解SpringCloud之分布式配置
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); }
View Code
那么此接口在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; } }
View Code
spring.factories文件:
org.springframework.cloud.bootstrap.BootstrapConfiguration=\
com.bdqn.lyrk.config.bootstrap.MyTestPropertySourceLocator
View Code
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")); } }
View Code
运行结果如下:
我们可以看到,当我们把自定义的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); }
View Code
此接口主要是根据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 + "]"; } }
View Code
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
View Code
我们可以看到,此处配置了引导类有一个叫: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; } } }
View Code
该引导中装配了一个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; } }
View Code
这个类很明显实现了PropertySourceLocator接口,在locate方法里会调用EnvironmentRepository的findOne方法,此时会将SpringCloud的Environment类和Spring中的Environment相关联
三、config-client
当我们添加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
View Code
根据引导配置,我们去追溯一下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(); } } }
View Code
与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; } }
View Code
我们可以看到该类实现了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中