Spring Cloud Config Server能够统一管理配置,我们绝大多数情况都是基于git或者svn作为其配置仓库,其实SpringCloud还可以把数据库作为配置仓库,今天我们就来了解一下。顺便分析一下其实现原理。
一、PropertySourceLocator接口
1.1、代码分析
这个接口的作用用于定制化引导配置,通过这个接口我们可以通过代码动态的向Environment中添加PropertySource,该接口定义如下:
/* *Copyright2013-2014theoriginalauthororauthors. * *LicensedundertheApacheLicense,Version2.0(the"License"); *youmaynotusethisfileexceptincompliancewiththeLicense. *YoumayobtainacopyoftheLicenseat * *http://www.apache.org/licenses/LICENSE-2.0 * *Unlessrequiredbyapplicablelaworagreedtoinwriting,software *distributedundertheLicenseisdistributedonan"ASIS"BASIS, *WITHOUTWARRANTIESORCONDITIONSOFANYKIND,eitherexpressorimplied. *SeetheLicenseforthespecificlanguagegoverningpermissionsand *limitationsundertheLicense. */ packageorg.springframework.cloud.bootstrap.config; importorg.springframework.core.env.Environment; importorg.springframework.core.env.PropertySource; /** *Strategyforlocating(possiblyremote)propertysourcesfortheEnvironment. *Implementationsshouldnotfailunlesstheyintendtopreventtheapplicationfrom *starting. * *@authorDaveSyer * */ publicinterfacePropertySourceLocator{ /** *@paramenvironmentthecurrentEnvironment *@returnaPropertySourceornullifthereisnone * *@throwsIllegalStateExceptionifthereisafailfastcondition */ PropertySource<?>locate(Environmentenvironment); } |
那么此接口在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 + "]"; } } |
上文内容不用于商业目的,如涉及知识产权问题,请权利人联系博为峰小编(021-64471599-8017),我们将立即处理。