Spring Cloud Config Server 源码分析 : 配置中心如何提供配置

本文主要从一些spring-cloud-config-server 包中的注解和类来分析配置中心是如何对外提供配置。

从@EnableConfigServer开始

为了让一个spring boot应用成为配置中心,我们需要使用@EnableConfigServer注解

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(ConfigServerConfiguration.class)
public @interface EnableConfigServer {

}

可以看出,它引入了ConfigServerConfiguration

@Configuration
public class ConfigServerConfiguration {
	class Marker {}

	@Bean
	public Marker enableConfigServerMarker() {
		return new Marker();
	}
}

ConfigServerConfiguration 装配了一个MarkerBean。这个bean则有开启了ConfigServerAutoConfiguration

@Configuration
@ConditionalOnBean(ConfigServerConfiguration.Marker.class)
@EnableConfigurationProperties(ConfigServerProperties.class)
@Import({ EnvironmentRepositoryConfiguration.class, CompositeConfiguration.class, ResourceRepositoryConfiguration.class,
		ConfigServerEncryptionConfiguration.class, ConfigServerMvcConfiguration.class, TransportConfiguration.class })
public class ConfigServerAutoConfiguration {

}

这里又引入了多个配置类,包括:

EnvironmentRepositoryConfiguration

CompositeConfiguration

ResourceRepositoryConfiguration

ConfigServerEncryptionConfiguration

ConfigServerMvcConfiguration

TransportConfiguration

接下来介绍EnvironmentRepositoryConfiguration

EnvironmentRepositoryConfiguration

EnvironmentRepositoryConfiguration是配置中心的关键Configuration类。这个配置类中包含很多实现了EnvironmentRepository接口的类,每个实现类都对应一种类型(git/svn/navtie/vault)的配置。 EnvironmentRepositoryConfiguration通过profile注解(对当前应用的环境)决定使用装配哪个EnvironmentRepository Bean。默认是MultipleJGitEnvironmentRepository

@Configuration
public class EnvironmentRepositoryConfiguration {

	@Bean
	@ConditionalOnProperty(value = "spring.cloud.config.server.health.enabled", matchIfMissing = true)
	public ConfigServerHealthIndicator configServerHealthIndicator(EnvironmentRepository repository) {
		return new ConfigServerHealthIndicator(repository);
	}

	@Configuration
	@ConditionalOnMissingBean(EnvironmentRepository.class)
	protected static class DefaultRepositoryConfiguration {

		@Autowired
		private ConfigurableEnvironment environment;

		@Autowired
		private ConfigServerProperties server;

		@Autowired(required = false)
		private TransportConfigCallback transportConfigCallback;

		@Bean
		public MultipleJGitEnvironmentRepository defaultEnvironmentRepository() {
			MultipleJGitEnvironmentRepository repository = new MultipleJGitEnvironmentRepository(this.environment);
			repository.setTransportConfigCallback(this.transportConfigCallback);
			if (this.server.getDefaultLabel()!=null) {
				repository.setDefaultLabel(this.server.getDefaultLabel());
			}
			return repository;
		}
	}

	@Configuration
	@Profile("native")
	protected static class NativeRepositoryConfiguration {

		@Autowired
		private ConfigurableEnvironment environment;

		@Bean
		public NativeEnvironmentRepository nativeEnvironmentRepository() {
			return new NativeEnvironmentRepository(this.environment);
		}
	}

	@Configuration
	@Profile("git")
	protected static class GitRepositoryConfiguration extends DefaultRepositoryConfiguration {}

	@Configuration
	@Profile("subversion")
	protected static class SvnRepositoryConfiguration {
		@Autowired
		private ConfigurableEnvironment environment;

		@Autowired
		private ConfigServerProperties server;

		@Bean
		public SvnKitEnvironmentRepository svnKitEnvironmentRepository() {
			SvnKitEnvironmentRepository repository = new SvnKitEnvironmentRepository(this.environment);
			if (this.server.getDefaultLabel()!=null) {
				repository.setDefaultLabel(this.server.getDefaultLabel());
			}
			return repository;
		}
	}

	@Configuration
	@Profile("vault")
	protected static class VaultConfiguration {
		@Bean
		public VaultEnvironmentRepository vaultEnvironmentRepository(HttpServletRequest request, EnvironmentWatch watch) {
			return new VaultEnvironmentRepository(request, watch, new RestTemplate());
		}
	}

	@Configuration
	@ConditionalOnProperty(value = "spring.cloud.config.server.consul.watch.enabled")
	protected static class ConsulEnvironmentWatchConfiguration {

		@Bean
		public EnvironmentWatch environmentWatch() {
			return new ConsulEnvironmentWatch();
		}
	}

	@Configuration
	@ConditionalOnMissingBean(EnvironmentWatch.class)
	protected static class DefaultEnvironmentWatch {

		@Bean
		public EnvironmentWatch environmentWatch() {
			return new EnvironmentWatch.Default();
		}
	}
}

EnvironmentRepository

EnvironmentRepository是一个配置管理仓库接口,抽象了获取配置的方法:

Environment findOne(String application, String profile, String label);

它的实现类有很多,如下图所示:

Spring Cloud Config Server 源码分析 : 配置中心如何提供配置
从名字中大概可以看出,这些类应该是用于加载不同类型的配置(后面会再介绍)。
有了获取配置的类,还差对外提供接口的类,就是EnvironmentController

入口:EnvironmentController

EnvironmentControllerspring-cloud-config-server包的一个controller,其他服务一般是通过这个controller获取相应配置。

@RestController
@RequestMapping(method = RequestMethod.GET, path = "${spring.cloud.config.server.prefix:}")
public class EnvironmentController {
    
	private EnvironmentRepository repository;
	private ObjectMapper objectMapper;
    
    public EnvironmentController(EnvironmentRepository repository,
			ObjectMapper objectMapper) {
		this.repository = repository;
		this.objectMapper = objectMapper;
	}
    
    // 获取配置的接口
	...

}

它的关键成员变量有两个:
一般情况SpringEnvironmentController注入的类是EnvironmentEncryptorEnvironmentRepository
ObjectMapper用于当请求json格式的配置时的序列化。

EnvironmentController提供了多种获取配置的方法,这些方法主要接受application profile label这三个(或者更少)的参数,这三个参数的具体含义可以参考官网的说明,下面列举了部分方法:

@RequestMapping("/{name}/{profiles:.*[^-].*}")
public Environment defaultLabel(@PathVariable String name,
                                @PathVariable String profiles) {
    return labelled(name, profiles, null);
}

@RequestMapping("/{name}/{profiles}/{label:.*}")
public Environment labelled(@PathVariable String name, @PathVariable String profiles,
                            @PathVariable String label) {
    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;
}

我们访问http://localhost:8081/config/mysql/dev(这是作者的配置,每个人可能不一样), 进入defaultLabel方法,它会再调用labelled方法(由于没有制定label参数,所以label传了个null)。

@RequestMapping("/{name}/{profiles:.*[^-].*}")
public Environment defaultLabel(@PathVariable String name,
                                @PathVariable String profiles) {
    return labelled(name, profiles, null);
}

@RequestMapping("/{name}/{profiles}/{label:.*}")
public Environment labelled(@PathVariable String name, @PathVariable String profiles,
                            @PathVariable String label) {
    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;
}

labelled方法中,会调用repository的findOne()来加载配置,然后返回给配置获取方。

各式各样的配置仓库类

EnvironmentEncryptorEnvironmentRepository

前面提到spring config 通过EnvironmentEncryptorEnvironmentRepository加载配置

public class EnvironmentEncryptorEnvironmentRepository implements EnvironmentRepository {
    private EnvironmentRepository delegate;
	private EnvironmentEncryptor environmentEncryptor;

	public EnvironmentEncryptorEnvironmentRepository(EnvironmentRepository delegate,
			EnvironmentEncryptor environmentEncryptor) {
		this.delegate = delegate;
		this.environmentEncryptor = environmentEncryptor;
	}
    
    @Override
    public Environment findOne(String name, String profiles, String label) {
        Environment environment = this.delegate.findOne(name, profiles, label);
        if (this.environmentEncryptor != null) {
            environment = this.environmentEncryptor.decrypt(environment);
        }
        if (!this.overrides.isEmpty()) {
            environment.addFirst(new PropertySource("overrides", this.overrides));
        }
        return environment;
    }
}

它有一个解密器environmentEncryptor用于对加密存放的配置进行解密,另外包含一个EnvironmentRepository的实现类delegate,这里注入的类是SearchPathCompositeEnvironmentRepository

SearchPathCompositeEnvironmentRepository

SearchPathCompositeEnvironmentRepository本身并没有findOne()方法,由它的父类CompositeEnvironmentRepository实现。

public class SearchPathCompositeEnvironmentRepository extends CompositeEnvironmentRepository implements SearchPathLocator {
	public SearchPathCompositeEnvironmentRepository(List<EnvironmentRepository> environmentRepositories) {
		super(environmentRepositories);
	}
}

SearchPathCompositeEnvironmentRepository

CompositeEnvironmentRepository有一个EnvironmentRepository的列表。从它的findOne()方法可以看出:当有多个配置存放方式时,CompositeEnvironmentRepository会遍历所有EnvironmentRepository来获取所有配置。

public class CompositeEnvironmentRepository implements EnvironmentRepository {
   protected List<EnvironmentRepository> environmentRepositories;

   public CompositeEnvironmentRepository(List<EnvironmentRepository> environmentRepositories) {
      //Sort the environment repositories by the priority
      Collections.sort(environmentRepositories, OrderComparator.INSTANCE);
      this.environmentRepositories = environmentRepositories;
   }

   @Override
   public Environment findOne(String application, String profile, String label) {
      Environment env = new Environment(application, new String[]{profile}, label, null, null);
      if(environmentRepositories.size() == 1) {
         Environment envRepo = environmentRepositories.get(0).findOne(application, profile, label);
         env.addAll(envRepo.getPropertySources());
         env.setVersion(envRepo.getVersion());
         env.setState(envRepo.getState());
      } else {
         for (EnvironmentRepository repo : environmentRepositories) {
            env.addAll(repo.findOne(application, profile, label).getPropertySources());
         }
      }
      return env;
   }
}

小结一下:虽然实现了EnvironmentRepository接口。但EnvironmentEncryptorEnvironmentRepository只是一个代理, SearchPathCompositeEnvironmentRepository/CompositeEnvironmentRepository也没有具体加载配置的逻辑。
而真正加载配置的类存放在CompositeEnvironmentRepositoryenvironmentRepositories列表。
包括:
NativeEnvironmentRepository: 获取本地配置;
SvnRepositoryConfiguration: 获取存放在svn中的配置;
VaultEnvironmentRepository: 获取存放在vault中的配置;
GitRepositoryConfiguration:获取存放在git中的配置;
接下来介绍NativeEnvironmentRepository

NativeEnvironmentRepository

NativeEnvironmentRepository 用于加载本地(native)配置。它加载配置时,其实是以特定环境(传入的profile)启动了另外一个微型spring boot应用,通过这个应用获取所有的配置,然后调用clean过滤,得到所需配置。

@ConfigurationProperties("spring.cloud.config.server.native")
public class NativeEnvironmentRepository implements EnvironmentRepository, SearchPathLocator, Ordered {
	@Override
	public Environment findOne(String config, String profile, String label) {
		SpringApplicationBuilder builder = new SpringApplicationBuilder(
				PropertyPlaceholderAutoConfiguration.class);
		ConfigurableEnvironment environment = getEnvironment(profile);
		builder.environment(environment);
		builder.web(false).bannerMode(Mode.OFF);
		if (!logger.isDebugEnabled()) {
			// Make the mini-application startup less verbose
			builder.logStartupInfo(false);
		}
		String[] args = getArgs(config, profile, label);
		// Explicitly set the listeners (to exclude logging listener which would change
		// log levels in the caller)
		builder.application()
				.setListeners(Arrays.asList(new ConfigFileApplicationListener()));
		ConfigurableApplicationContext context = builder.run(args);
		environment.getPropertySources().remove("profiles");
		try {
			return clean(new PassthruEnvironmentRepository(environment).findOne(config,
					profile, label));
		}
		finally {
			context.close();
		}
	}
    
    private ConfigurableEnvironment getEnvironment(String profile) {
		ConfigurableEnvironment environment = new StandardEnvironment();
		environment.getPropertySources()
				.addFirst(new MapPropertySource("profiles",
						Collections.<String, Object>singletonMap("spring.profiles.active",
								profile)));
		return environment;
	}
    
    protected Environment clean(Environment value) {
		Environment result = new Environment(value.getName(), value.getProfiles(),
				value.getLabel(), this.version, value.getState());
		for (PropertySource source : value.getPropertySources()) {
			String name = source.getName();
			if (this.environment.getPropertySources().contains(name)) {
				continue;
			}
			name = name.replace("applicationConfig: [", "");
			name = name.replace("]", "");
			if (this.searchLocations != null) {
				boolean matches = false;
				String normal = name;
				if (normal.startsWith("file:")) {
					normal = StringUtils
							.cleanPath(new File(normal.substring("file:".length()))
									.getAbsolutePath());
				}
				String profile = result.getProfiles() == null ? null
						: StringUtils.arrayToCommaDelimitedString(result.getProfiles());
				for (String pattern : getLocations(result.getName(), profile,
						result.getLabel()).getLocations()) {
					if (!pattern.contains(":")) {
						pattern = "file:" + pattern;
					}
					if (pattern.startsWith("file:")) {
						pattern = StringUtils
								.cleanPath(new File(pattern.substring("file:".length()))
										.getAbsolutePath())
								+ "/";
					}
					if (logger.isTraceEnabled()) {
						logger.trace("Testing pattern: " + pattern
								+ " with property source: " + name);
					}
					if (normal.startsWith(pattern)
							&& !normal.substring(pattern.length()).contains("/")) {
						matches = true;
						break;
					}
				}
				if (!matches) {
					// Don't include this one: it wasn't matched by our search locations
					if (logger.isDebugEnabled()) {
						logger.debug("Not adding property source: " + name);
					}
					continue;
				}
			}
			logger.info("Adding property source: " + name);
			result.add(new PropertySource(name, source.getSource()));
		}
		return result;
	}
}