阅读前须知:
Spring Cloud 构建于 Spring Boot 之上,在 Spring Boot 中有两种上下文,一种是 bootstrap, 另外一种是 application, bootstrap 是应用程序的父上下文,也就是说 bootstrap 加载优先于 applicaton。bootstrap 主要用于从额外的资源来加载配置信息,还可以在本地外部配置文件中解密属性。这两个上下文共用一个环境,它是任何Spring应用程序的外部属性的来源。bootstrap 里面的属性会优先加载,它们默认也不能被本地相同配置覆盖。
SpringApplication.run
public ConfigurableApplicationContext run(String... args) {
// 计时工具
StopWatch stopWatch = new StopWatch();
stopWatch.start();
ConfigurableApplicationContext context = null;
Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList();
this.configureHeadlessProperty();
//获取并启动监听器
SpringApplicationRunListeners listeners = this.getRunListeners(args);
listeners.starting();
Collection exceptionReporters;
try {
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
// 根据SpringApplicationRunListeners以及参数来准备环境
ConfigurableEnvironment environment = this.prepareEnvironment(listeners, applicationArguments);
this.configureIgnoreBeanInfo(environment);
// 准备Banner打印器 - 就是启动Spring Boot的时候打印在console上的ASCII艺术字体
Banner printedBanner = this.printBanner(environment);
// 创建Spring容器
context = this.createApplicationContext();
exceptionReporters = this.getSpringFactoriesInstances(SpringBootExceptionReporter.class, new Class[]{ConfigurableApplicationContext.class}, context);
// Spring容器前置处理
this.prepareContext(context, environment, listeners, applicationArguments, printedBanner);
// 刷新容器
this.refreshContext(context);
// Spring容器后置处理
this.afterRefresh(context, applicationArguments);
stopWatch.stop();
if (this.logStartupInfo) {
(new StartupInfoLogger(this.mainApplicationClass)).logStarted(this.getApplicationLog(), stopWatch);
}
// 发出结束执行的事件
listeners.started(context);
this.callRunners(context, applicationArguments);
} catch (Throwable var10) {
this.handleRunFailure(context, var10, exceptionReporters, listeners);
throw new IllegalStateException(var10);
}
try {
// 执行Runners
listeners.running(context);
// 返回容器
return context;
} catch (Throwable var9) {
this.handleRunFailure(context, var9, exceptionReporters, (SpringApplicationRunListeners)null);
throw new IllegalStateException(var9);
}
}
见名知意,prepareEnvironment是准备environment配置源信息。
prepareEnvironment
springApplication.run -> prepareEnvironment
private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners,
ApplicationArguments applicationArguments) {
// 根据上下文,创建一个合适的Environment对象
ConfigurableEnvironment environment = getOrCreateEnvironment();
//配置Environment的propertySource、以及profile
configureEnvironment(environment, applicationArguments.getSourceArgs());
ConfigurationPropertySources.attach(environment);
// 通知监听器,加载配置文件(重要)
listeners.environmentPrepared(environment);
bindToSpringApplication(environment);
if (!this.isCustomEnvironment) {
environment = new EnvironmentConverter(getClassLoader()).convertEnvironmentIfNecessary(environment,
deduceEnvironmentClass());
}
ConfigurationPropertySources.attach(environment);
return environment;
}
getOrCreateEnvironment
这个方法,就是根据当前的webApplication类型匹配对应的environment,如果是servlet,则是StandardServletEnvironment ,如果是spring webflux,则是 StandardReactiveWebEnvironment ,否则是StandardEnvironment。
此处是StandardServletEnvironment。
private ConfigurableEnvironment getOrCreateEnvironment() {
if (this.environment != null) {
return this.environment;
}
switch (this.webApplicationType) {
case SERVLET:
return new StandardServletEnvironment();
case REACTIVE:
return new StandardReactiveWebEnvironment();
default:
return new StandardEnvironment();
}
}
StandardServletEnvironment是整个spring boot应用运行环境的实现类,后面所有的关于环境相关 的配置操作都是基于这个类,它的类的结构图如下:
StandardServletEnvironment初始化
spring经常会在类初始化时做一些小动作,StandardServletEnvironment也不例外。
StandardServletEnvironment 会初始化父类 AbstractEnvironment ,在这个类的构造方法中,会 调用一个自定义配置文件的方法customizePropertySources。
customizePropertySources 这个方法被 StandardServletEnvironment 重写了,所以会调用StandardServletEnvironment 中的customizePropertySources 方法。
这里是将几个不同的配置源封装成 StubPropertySource 添加到 MutablePropertySources 中,调用 addLast 是表示一直往最后的位置添加。
- SERVLET_CONFIG_PROPERTY_SOURCE_NAME:servlet的配置信息,也就是在web.xml中servlet子标签init-param配置的信息。
- SERVLET_CONTEXT_PROPERTY_SOURCE_NAME: 这个是servlet初始化的上下文,也就是以前我们
在web.xml中配置的 context-param 。 - JNDI_PROPERTY_SOURCE_NAME: 加载jndi.properties配置信息。
StandardServletEnvironment.customizePropertySources
protected void customizePropertySources(MutablePropertySources propertySources) {
propertySources.addLast(new StubPropertySource(SERVLET_CONFIG_PROPERTY_SOURCE_NAME));
propertySources.addLast(new StubPropertySource(SERVLET_CONTEXT_PROPERTY_SOURCE_NAME));
if (JndiLocatorDelegate.isDefaultJndiEnvironmentAvailable()) {
propertySources.addLast(new JndiPropertySource(JNDI_PROPERTY_SOURCE_NAME));
}
super.customizePropertySources(propertySources);
}
继续调用父类,也就是 StandardEnvironment 类中的 customizePropertySources 方法。
- SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME: 系统变量,通过System.setProperty设置的变
量,默认可以看到 java.version 、 os.name 等。 - SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME: 系统环境变量,也就是我们配置JAVA_HOME
的地方。
protected void customizePropertySources(MutablePropertySources propertySources) {
propertySources.addLast(
new PropertiesPropertySource(SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME, getSystemProperties()));
propertySources.addLast(
new SystemEnvironmentPropertySource(SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME, getSystemEnvironment()));
}
这里我们要明确一点,就是添加PropertySource的目的其实就是要告诉Environment,解析哪些位置的 属性文件进行加载。而在这个添加过程中,所有的添加都是基于 addLast ,也就是最早添加的 PropertySource会放在最前面。 systemProperties 是在 systemEnvironment 前面,这点很重要。因 为前面的配置会覆盖后面的配置,也就是说系统变量中的配置比系统环境变量中的配置优先级更高。
MutablePropertySources
在上面的代码中可以看到,所有的外部资源配置都是添加到了一个MutablePropertySources对象中, 这个对象封装了属性资源的集合。
而从 MutablePropertySources 命名来说,Mutable是一个可变的意思,也就是意味着它动态的管理了 PropertySource的集合。
public class MutablePropertySources implements PropertySources {
private final List<PropertySource<?>> propertySourceList = new CopyOnWriteArrayList<>();
//.......
}
接着,我们来看一下它怎么用的,找到 这个类,在这里定义了 MutablePropertySources。并且把这个MutablePropertySources作为参数传递给了
ConfigurablePropertyResolver 配置解析器中,而这个配置解析器是一个 PropertySourcesPropertyResolver 实例。
AbstractEnvironment
private final MutablePropertySources propertySources = new MutablePropertySources();
private final ConfigurablePropertyResolver propertyResolver =
new PropertySourcesPropertyResolver(this.propertySources);
我们来看一下这个类关系图, AbstractEnvironment 实现了文件解析器 ConfigurablePropertyResolver ,而在上面这段代码中我们把 MutablePropertySources 传递到 PropertySourcesPropertyResolver 中。这样就可以让 AbstractEnvironment 具备文件解析的功能,只是这个功能,委托给了PropertySourcesPropertyResolver来实现。
configureEnvironment
SpringApplication
通过上面的代码,spring构造了一个 StandardServletEnvironment 对象并且初始化了一些需要解析的propertySource。我们继续来看 configureEnvironment 这个方法,这个方法有三个作用
- addConversionService 添加类型转化的服务,我们知道properties文件中配置的属性都是String 类型的,而转化为Java对象之后要根据合适的类型进行转化,而 ConversionService 是一套通用 的转化方案,这里把这个转化服务设置到当前的Environment,很显然,就是为Environment配置 解析时提供一个类型转化的解决方案
- configurePropertySources 配置Environment中的propertysources
- configureProfiles 配置profiles
protected void configureEnvironment(ConfigurableEnvironment environment, String[] args) {
if (this.addConversionService) {
ConversionService conversionService = ApplicationConversionService.getSharedInstance();
environment.setConversionService((ConfigurableConversionService) conversionService);
}
configurePropertySources(environment, args);
configureProfiles(environment, args);
}
configurePropertySources
- 设置 defaultProperties 属性来源
- 设置commandLineProperties来源,如果设置了命令行参数,则会加载SimpleCommandLinePropertySource 作为propertySource
protected void configurePropertySources(ConfigurableEnvironment environment, String[] args) {
MutablePropertySources sources = environment.getPropertySources();
if (this.defaultProperties != null && !this.defaultProperties.isEmpty()) {
sources.addLast(new MapPropertySource("defaultProperties", this.defaultProperties));
}
if (this.addCommandLineProperties && args.length > 0) {
String name = CommandLinePropertySource.COMMAND_LINE_PROPERTY_SOURCE_NAME;
if (sources.contains(name)) {
PropertySource<?> source = sources.get(name);
CompositePropertySource composite = new CompositePropertySource(name);
composite.addPropertySource(
new SimpleCommandLinePropertySource("springApplicationCommandLineArgs", args));
composite.addPropertySource(source);
sources.replace(name, composite);
}
else {
sources.addFirst(new SimpleCommandLinePropertySource(args));
}
}
}
这个defaultProperties是什么呢?给大家演示一个东西,就是说我们可以设置一个默认的属性, 如果设置过,则需要加载。否则就不需要。
public static void main(String[] args) {
SpringApplication springApplication=new SpringApplication(SpringCloudEurekaServerApplication.class);
Map<String, Object> pro = new HashMap<>();
pro.put("key", "value");
springApplication.setDefaultProperties(pro);
springApplication.run(args);
}
configureProfiles
这个方法就比较容易理解,就是配置当前激活的profiles,将当前的activeProfiles设置到enviroment 中。这样就能够使得我们完成不同环境下配置的获取问题。
protected void configureProfiles(ConfigurableEnvironment environment, String[] args) {
Set<String> profiles = new LinkedHashSet<>(this.additionalProfiles);
profiles.addAll(Arrays.asList(environment.getActiveProfiles()));
environment.setActiveProfiles(StringUtils.toStringArray(profiles));
}
listeners.environmentPrepared(environment)
上述工作完成之后,发布一个environmentPrepared环境准备就绪的通知,这里是重点,需要详细说明一下,应为此处涉及bootstrap上下文的加载。
在SpringApplication的run方法启动中有以下代码,要回过头说明一下
SpringApplicationRunListeners listeners = getRunListeners(args);
listeners.starting();
getRunListeners方法,主要是使用spring的spi扩展机制加载事件发布类EventPublishingRunListener,并且在构造方法中,将所有的消息监听器加入到属性中。
org.springframework.boot.SpringApplicationRunListener=\
org.springframework.boot.context.event.EventPublishingRunListener
private SpringApplicationRunListeners getRunListeners(String[] args) {
Class<?>[] types = new Class<?>[] { SpringApplication.class, String[].class };
return new SpringApplicationRunListeners(logger,
getSpringFactoriesInstances(SpringApplicationRunListener.class, types, this, args));
}
public EventPublishingRunListener(SpringApplication application, String[] args) {
this.application = application;
this.args = args;
this.initialMulticaster = new SimpleApplicationEventMulticaster();
for (ApplicationListener<?> listener : application.getListeners()) {
this.initialMulticaster.addApplicationListener(listener);
}
}
listeners.starting()就是发布一个ApplicationStartingEvent事件。
那么又有一个问题,SpringApplication的listeners在什么时候初始化的,看一下SpringApplication的构造方法。
public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
this.resourceLoader = resourceLoader;
Assert.notNull(primarySources, "PrimarySources must not be null");
this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
this.webApplicationType = WebApplicationType.deduceFromClasspath();
//spi初始化了ApplicationContextInitializer
setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
//spi初始化了ApplicationListener
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
this.mainApplicationClass = deduceMainApplicationClass();
}
有两个监听器比较重要,下面的代码是截取代码,不止此处的两个监听器。
org.springframework.context.ApplicationListener=\
org.springframework.cloud.bootstrap.BootstrapApplicationListener,\
org.springframework.boot.context.config.ConfigFileApplicationListener
并且BootstrapApplicationListener是第一个,ConfigFileApplicationListener一定是比BootstrapApplicationListener顺位低。
此时listeners.environmentPrepared(environment)发布一个ApplicationEnvironmentPreparedEvent事件,BootstrapApplicationListener和ConfigFileApplicationListener都有监听该事件。
BootstrapApplicationListener
onApplicationEvent
当监听事件触发后,构建bootstrap上下文。
public void onApplicationEvent(ApplicationEnvironmentPreparedEvent event) {
ConfigurableEnvironment environment = event.getEnvironment();
if (!environment.getProperty("spring.cloud.bootstrap.enabled", Boolean.class,
true)) {
return;
}
// don't listen to events in a bootstrap context
if (environment.getPropertySources().contains(BOOTSTRAP_PROPERTY_SOURCE_NAME)) {
return;
}
ConfigurableApplicationContext context = null;
String configName = environment
.resolvePlaceholders("${spring.cloud.bootstrap.name:bootstrap}");
for (ApplicationContextInitializer<?> initializer : event.getSpringApplication()
.getInitializers()) {
if (initializer instanceof ParentContextApplicationContextInitializer) {
context = findBootstrapContext(
(ParentContextApplicationContextInitializer) initializer,
configName);
}
}
if (context == null) {
//构建bootstrap上下文
context = bootstrapServiceContext(environment, event.getSpringApplication(),
configName);
event.getSpringApplication()
.addListeners(new CloseContextOnFailureApplicationListener(context));
}
//主要设置Initializers
apply(context, event.getSpringApplication(), environment);
}
bootstrapServiceContext
private ConfigurableApplicationContext bootstrapServiceContext(
ConfigurableEnvironment environment, final SpringApplication application,
String configName) {
StandardEnvironment bootstrapEnvironment = new StandardEnvironment();
MutablePropertySources bootstrapProperties = bootstrapEnvironment
.getPropertySources();
for (PropertySource<?> source : bootstrapProperties) {
bootstrapProperties.remove(source.getName());
}
String configLocation = environment
.resolvePlaceholders("${spring.cloud.bootstrap.location:}");
String configAdditionalLocation = environment
.resolvePlaceholders("${spring.cloud.bootstrap.additional-location:}");
Map<String, Object> bootstrapMap = new HashMap<>();
bootstrapMap.put("spring.config.name", configName);
// if an app (or test) uses spring.main.web-application-type=reactive, bootstrap
// will fail
// force the environment to use none, because if though it is set below in the
// builder
// the environment overrides it
bootstrapMap.put("spring.main.web-application-type", "none");
if (StringUtils.hasText(configLocation)) {
bootstrapMap.put("spring.config.location", configLocation);
}
if (StringUtils.hasText(configAdditionalLocation)) {
bootstrapMap.put("spring.config.additional-location",
configAdditionalLocation);
}
//加载一个bootstrap数据源,重点,此配置源有一个属性spring.config.name:bootstrap
bootstrapProperties.addFirst(
new MapPropertySource(BOOTSTRAP_PROPERTY_SOURCE_NAME, bootstrapMap));
for (PropertySource<?> source : environment.getPropertySources()) {
if (source instanceof StubPropertySource) {
continue;
}
bootstrapProperties.addLast(source);
}
// TODO: is it possible or sensible to share a ResourceLoader?
SpringApplicationBuilder builder = new SpringApplicationBuilder()
.profiles(environment.getActiveProfiles()).bannerMode(Mode.OFF)
.environment(bootstrapEnvironment)
// Don't use the default properties in this builder
.registerShutdownHook(false).logStartupInfo(false)
.web(WebApplicationType.NONE);
final SpringApplication builderApplication = builder.application();
if (builderApplication.getMainApplicationClass() == null) {
// gh_425:
// SpringApplication cannot deduce the MainApplicationClass here
// if it is booted from SpringBootServletInitializer due to the
// absense of the "main" method in stackTraces.
// But luckily this method's second parameter "application" here
// carries the real MainApplicationClass which has been explicitly
// set by SpringBootServletInitializer itself already.
builder.main(application.getMainApplicationClass());
}
if (environment.getPropertySources().contains("refreshArgs")) {
// If we are doing a context refresh, really we only want to refresh the
// Environment, and there are some toxic listeners (like the
// LoggingApplicationListener) that affect global static state, so we need a
// way to switch those off.
builderApplication
.setListeners(filterListeners(builderApplication.getListeners()));
}
//加载BootstrapImportSelectorConfiguration批量注册,即spi方式加载所有org.springframework.cloud.bootstrap.BootstrapConfiguration对应的类。
builder.sources(BootstrapImportSelectorConfiguration.class);
//SpringApplication的run方法
final ConfigurableApplicationContext context = builder.run();
// gh-214 using spring.application.name=bootstrap to set the context id via
// `ContextIdApplicationContextInitializer` prevents apps from getting the actual
// spring.application.name
// during the bootstrap phase.
context.setId("bootstrap");
// Make the bootstrap context a parent of the app context
addAncestorInitializer(application, context);
// It only has properties in it now that we don't want in the parent so remove
// it (and it will be added back later)
bootstrapProperties.remove(BOOTSTRAP_PROPERTY_SOURCE_NAME);
//enviorment的配置源合并
mergeDefaultProperties(environment.getPropertySources(), bootstrapProperties);
return context;
}
此时父容器开始加载,加载中会触发ApplicationEnvironmentPreparedEvent事件,此时因为BootstrapApplicationListener的onApplicationEvent中,发现envioronment中含有bootstrap,所以这个监听器跳过。
if (environment.getPropertySources().contains(BOOTSTRAP_PROPERTY_SOURCE_NAME)) {
return;
}
ConfigFileApplicationListener
onApplicationEvent
此时调用ConfigFileApplicationListener监听器,此监听器主要是从指定位置,加载指定名称的配置文件。
public void onApplicationEvent(ApplicationEvent event) {
if (event instanceof ApplicationEnvironmentPreparedEvent) {
onApplicationEnvironmentPreparedEvent((ApplicationEnvironmentPreparedEvent) event);
}
if (event instanceof ApplicationPreparedEvent) {
onApplicationPreparedEvent(event);
}
}
private void onApplicationEnvironmentPreparedEvent(ApplicationEnvironmentPreparedEvent event) {
List<EnvironmentPostProcessor> postProcessors = loadPostProcessors();
//将自身加入,自身也是一个拦截器
postProcessors.add(this);
AnnotationAwareOrderComparator.sort(postProcessors);
for (EnvironmentPostProcessor postProcessor : postProcessors) {
postProcessor.postProcessEnvironment(event.getEnvironment(), event.getSpringApplication());
}
}
public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) {
addPropertySources(environment, application.getResourceLoader());
}
addPropertySources
最终执行到 ConfigFileApplicationListener.addPropertySources 方法中,这个方法做两个事情
- 添加一个RandomValuePropertySource到Environment的MutablePropertySources中
- 加载spring boot中的配置信息,比如bootstrap.yml/application.yml或者bootstrap.properties/application.properties
protected void addPropertySources(ConfigurableEnvironment environment, ResourceLoader resourceLoader) {
RandomValuePropertySource.addToEnvironment(environment);
new Loader(environment, resourceLoader).load();
}
Load.load
这个方法比较复杂,总的来说,就是加载所有可能的profiles
首先我们来看,这里实际上是调用了方法。然后传递了一个lambda
表达式到apply方法中。
- 其中apply方法的主要逻辑是,判断如果当前如果有默认配置,则将默认配置员增加一个FilteredPropertySource.
- 执行匿名内部类
void load() {
FilteredPropertySource.apply(this.environment, DEFAULT_PROPERTIES, LOAD_FILTERED_PROPERTY,
(defaultProperties) -> {
this.profiles = new LinkedList<>();
this.processedProfiles = new LinkedList<>();
this.activatedProfiles = false;
this.loaded = new LinkedHashMap<>();
initializeProfiles();
while (!this.profiles.isEmpty()) {
Profile profile = this.profiles.poll();
if (isDefaultProfile(profile)) {
addProfileToEnvironment(profile.getName());
}
load(profile, this::getPositiveProfileFilter,
addToLoaded(MutablePropertySources::addLast, false));
this.processedProfiles.add(profile);
}
load(null, this::getNegativeProfileFilter, addToLoaded(MutablePropertySources::addFirst, true));
addLoadedPropertySources();
applyActiveProfiles(defaultProperties);
});
}
下面这个lambda表达式的主要逻辑是
- 调用initializeProfiles初始化默认的Profile,没有设置的话就用默认,初始化之后保存到private Deque<Profile> profiles中,它是一个LIFO队列。因为profiles采用了LIFO队列,后进先出。所以会优先加载profile为null的配置文件,也就是匹配bootstrap.yml/application.yml或者bootstrap.properties/application.properties
- 如果profiles不为空,则循环遍历每一个profiles,调用 load方法进行加载
(defaultProperties) -> {
// 未处理的数据集合
this.profiles = new LinkedList<>();
// 已处理的数据集合
this.processedProfiles = new LinkedList<>(); this.activatedProfiles = false;
this.loaded = new LinkedHashMap<>(); //加载存在已经激活的 profiles
initializeProfiles();
while (!this.profiles.isEmpty()) {//遍历所有profiles
Profile profile = this.profiles.poll();
if (isDefaultProfile(profile)) {
addProfileToEnvironment(profile.getName());
}
// 确定搜索范围,获取对应的配置文件名,并使用相应加载器加载 load(profile, this::getPositiveProfileFilter,
addToLoaded(MutablePropertySources::addLast, false));
// 将处理完的 profile添加到 processedProfiles列表当中,表示已经处理完成 this.processedProfiles.add(profile);
}
load(null, this::getNegativeProfileFilter,
addToLoaded(MutablePropertySources::addFirst, true));
addLoadedPropertySources(); applyActiveProfiles(defaultProperties);// 更新 activeProfiles列表
Load.initializeProfiles
该方法的作用是加载存在已经激活的 profiles
private void initializeProfiles() {
// The default profile for these purposes is represented as null. We add it
// first so that it is processed first and has lowest priority.
this.profiles.add(null);
Binder binder = Binder.get(this.environment);
//判断当前环境是否配置 spring.profiles.active属性
Set<Profile> activatedViaProperty = getProfiles(binder, ACTIVE_PROFILES_PROPERTY);
//判断当前环境是否配置 spring.profiles.include属性
Set<Profile> includedViaProperty = getProfiles(binder, INCLUDE_PROFILES_PROPERTY);
//如果没有特别指定的话,就是 application.properties 和 application-default.properties配置
List<Profile> otherActiveProfiles = getOtherActiveProfiles(activatedViaProperty, includedViaProperty);
this.profiles.addAll(otherActiveProfiles);
// Any pre-existing active profiles set via property sources (e.g.
// System properties) take precedence over those added in config files.
this.profiles.addAll(includedViaProperty);
addActiveProfiles(activatedViaProperty);
// 如果 profiles集仍然为null,即没有指定,就会创建默认的profile
if (this.profiles.size() == 1) { // only has null profile
for (String defaultProfileName : this.environment.getDefaultProfiles()) {
Profile defaultProfile = new Profile(defaultProfileName, true);
this.profiles.add(defaultProfile);
}
}
}
load
继续跟进load方法,通过 getSearchLoacations 进行搜索,并且进行迭代。
private void load(Profile profile, DocumentFilterFactory filterFactory, DocumentConsumer consumer) {
getSearchLocations().forEach((location) -> {
boolean isDirectory = location.endsWith("/");
Set<String> names = isDirectory ? getSearchNames() : NO_SEARCH_NAMES;
names.forEach((name) -> load(location, name, profile, filterFactory, consumer));
});
}
getSearchLocations的主要功能,就是获取需要遍历的目标路径,默认情况下,会去 DEFAULT_SEARCH_LOCATIONS中查找,也就是
private Set<String> getSearchLocations() {
Set<String> locations = getSearchLocations(CONFIG_ADDITIONAL_LOCATION_PROPERTY);
if (this.environment.containsProperty(CONFIG_LOCATION_PROPERTY)) {
locations.addAll(getSearchLocations(CONFIG_LOCATION_PROPERTY));
}
else {
locations.addAll(
asResolvedSet(ConfigFileApplicationListener.this.searchLocations, DEFAULT_SEARCH_LOCATIONS));
}
return locations;
}
private static final String DEFAULT_SEARCH_LOCATIONS = "classpath:/,classpath:/config/,file:./,file:./config/*/,file:./config/";
getSearchNames
还记得BootstrapApplicationListener设置在enviorment中的spring.config.name吗?当时设置了bootstrap,此处就是获取该值作为配置文件名!约定大于设置!如果没有该值才会去加载application配置文件。
private Set<String> getSearchNames() {
if (this.environment.containsProperty(CONFIG_NAME_PROPERTY)) {
String property = this.environment.getProperty(CONFIG_NAME_PROPERTY);
Set<String> names = asResolvedSet(property, null);
names.forEach(this::assertValidConfigName);
return names;
}
return asResolvedSet(ConfigFileApplicationListener.this.names, DEFAULT_NAMES);
}
拿到文件名和路径地址之后,再拼接对应路径,选择合适的yml或者properties解析器进行解析。
private void load(String location, String name, Profile profile, DocumentFilterFactory filterFactory,
DocumentConsumer consumer) {
if (!StringUtils.hasText(name)) {
for (PropertySourceLoader loader : this.propertySourceLoaders) {
if (canLoadFileExtension(loader, location)) {
load(loader, location, profile, filterFactory.getDocumentFilter(profile), consumer);
return;
}
}
throw new IllegalStateException("File extension of config file location '" + location
+ "' is not known to any PropertySourceLoader. If the location is meant to reference "
+ "a directory, it must end in '/'");
}
Set<String> processed = new HashSet<>();
for (PropertySourceLoader loader : this.propertySourceLoaders) {
for (String fileExtension : loader.getFileExtensions()) {
if (processed.add(fileExtension)) {
loadForFileExtension(loader, location + name, "." + fileExtension, profile, filterFactory,
consumer);
}
}
}
}
propertySourceLoaders
propertySourceLoaders使用spi方式加载yml和properties文件的加载类
org.springframework.boot.env.PropertySourceLoader=\
org.springframework.boot.env.PropertiesPropertySourceLoader,\
org.springframework.boot.env.YamlPropertySourceLoader
Loader(ConfigurableEnvironment environment, ResourceLoader resourceLoader) {
this.environment = environment;
//占位符解析器
this.placeholdersResolver = new PropertySourcesPlaceholdersResolver(this.environment);
//文件加载器
this.resourceLoader = (resourceLoader != null) ? resourceLoader
: new DefaultResourceLoader(getClass().getClassLoader());
//文件内容解析器
this.propertySourceLoaders = SpringFactoriesLoader.loadFactories(PropertySourceLoader.class,
getClass().getClassLoader());
}
此时BootstrapApplicationListener的bootstrap.yml/properties解析完毕后调用mergeDefaultProperties进行配置源合并,然后调用apply方法,将BootstrapConfiguration下的ApplicationContextInitializer类加入到SpringApplication的Initializers中。
BootstrapApplicationListener.apply
private void apply(ConfigurableApplicationContext context,
SpringApplication application, ConfigurableEnvironment environment) {
if (application.getAllSources().contains(BootstrapMarkerConfiguration.class)) {
return;
}
application.addPrimarySources(Arrays.asList(BootstrapMarkerConfiguration.class));
@SuppressWarnings("rawtypes")
Set target = new LinkedHashSet<>(application.getInitializers());
//加载ApplicationContextInitializer
target.addAll(
getOrderedBeansOfType(context, ApplicationContextInitializer.class));
application.setInitializers(target);
addBootstrapDecryptInitializer(application);
有一个重点ApplicationContextInitializer
org.springframework.cloud.bootstrap.BootstrapConfiguration=\
org.springframework.cloud.bootstrap.config.PropertySourceBootstrapConfiguration
然后bootstrap加载完毕,并移除了bootstrap配置源(和bootstrap.yml/properties不是一个)。
application
bootstrap加载完毕后,application上下文继续加载,紧接ApplicationEnvironmentPreparedEvent,此时该事件该由ConfigFileApplicationListener继续处理,此时将会加载application.yml/properties,漫漫长路走了一半,系统内的文件加载完毕,那么怎么加载外部配置,比如配置了springcloudconfig。
prepareContext
SpringApplication.prepareContext
此方法主要看applyInitializers启动所有initializers,对,就是在bootstrap中加载的一个initializer,此处需要使用到了。
private void prepareContext(ConfigurableApplicationContext context, ConfigurableEnvironment environment,
SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments, Banner printedBanner) {
context.setEnvironment(environment);
postProcessApplicationContext(context);
//启动所有Initializers
applyInitializers(context);
listeners.contextPrepared(context);
if (this.logStartupInfo) {
logStartupInfo(context.getParent() == null);
logStartupProfileInfo(context);
}
// Add boot specific singleton beans
ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
beanFactory.registerSingleton("springApplicationArguments", applicationArguments);
if (printedBanner != null) {
beanFactory.registerSingleton("springBootBanner", printedBanner);
}
if (beanFactory instanceof DefaultListableBeanFactory) {
((DefaultListableBeanFactory) beanFactory)
.setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding);
}
if (this.lazyInitialization) {
context.addBeanFactoryPostProcessor(new LazyInitializationBeanFactoryPostProcessor());
}
// Load the sources
Set<Object> sources = getAllSources();
Assert.notEmpty(sources, "Sources must not be empty");
load(context, sources.toArray(new Object[0]));
listeners.contextLoaded(context);
}
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);
}
}
PropertySourceBootstrapConfiguration
initialize
public void initialize(ConfigurableApplicationContext applicationContext) {
List<PropertySource<?>> composite = new ArrayList<>();
//对propertySourceLocators数组进行排序,根据默认的AnnotationAwareOrderComparator
AnnotationAwareOrderComparator.sort(this.propertySourceLocators);
boolean empty = true;
ConfigurableEnvironment environment = applicationContext.getEnvironment();
//获取所有的propertySourceLocators,PropertySourceLocator是自动注入进来的
for (PropertySourceLocator locator : this.propertySourceLocators) {
Collection<PropertySource<?>> source = locator.locateCollection(environment);
if (source == null || source.size() == 0) {
continue;
}
List<PropertySource<?>> sourceList = new ArrayList<>();
for (PropertySource<?> p : source) {
sourceList.add(new BootstrapPropertySource<>(p));
}
logger.info("Located property source: " + sourceList);
composite.addAll(sourceList);
empty = false;
}
if (!empty) {
MutablePropertySources propertySources = environment.getPropertySources();
String logConfig = environment.resolvePlaceholders("${logging.config:}");
LogFile logFile = LogFile.get(environment);
for (PropertySource<?> p : environment.getPropertySources()) {
if (p.getName().startsWith(BOOTSTRAP_PROPERTY_SOURCE_NAME)) {
propertySources.remove(p.getName());
}
}
insertPropertySources(propertySources, composite);
reinitializeLoggingSystem(environment, logConfig, logFile);
setLogLevels(applicationContext, environment);
handleIncludedProfiles(environment);
}
}
有一个PropertySourceLocator使用BootstrapConfiguration注入进来
org.springframework.cloud.bootstrap.BootstrapConfiguration=\
org.springframework.cloud.config.client.ConfigServiceBootstrapConfiguration,\
org.springframework.cloud.config.client.DiscoveryClientConfigServiceBootstrapConfiguration
这个配置类中注入了ConfigServicePropertySourceLocator
@Bean
@ConditionalOnMissingBean(ConfigServicePropertySourceLocator.class)
@ConditionalOnProperty(value = "spring.cloud.config.enabled", matchIfMissing = true)
public ConfigServicePropertySourceLocator configServicePropertySource(
ConfigClientProperties properties) {
ConfigServicePropertySourceLocator locator = new ConfigServicePropertySourceLocator(
properties);
return locator;
}
ConfigServicePropertySourceLocator
locateCollection
public Collection<org.springframework.core.env.PropertySource<?>> locateCollection(
org.springframework.core.env.Environment environment) {
return PropertySourceLocator.locateCollection(this, environment);
}
PropertySourceLocator.locateCollection
这个方法会调用子类的locate方法,来获得一个PropertySource,然后将PropertySource集合返回。接 着它会调用 ConfigServicePropertySourceLocator 的locate方法。
static Collection<PropertySource<?>> locateCollection(PropertySourceLocator locator,
Environment environment) {
PropertySource<?> propertySource = locator.locate(environment);
if (propertySource == null) {
return Collections.emptyList();
}
if (CompositePropertySource.class.isInstance(propertySource)) {
Collection<PropertySource<?>> sources = ((CompositePropertySource) propertySource)
.getPropertySources();
List<PropertySource<?>> filteredSources = new ArrayList<>();
for (PropertySource<?> p : sources) {
if (p != null) {
filteredSources.add(p);
}
}
return filteredSources;
}
else {
return Arrays.asList(propertySource);
}
}
ConfigServicePropertySourceLocator.locate
这个就是Config Client的关键实现了,它会通过RestTemplate调用一个远程地址获得配置信息, getRemoteEnvironment 。然后把这个配置PropertySources,然后将这个信息包装成一个OriginTrackedMapPropertySource,设置到 Composite 中。
public org.springframework.core.env.PropertySource<?> locate(
org.springframework.core.env.Environment environment) {
ConfigClientProperties properties = this.defaultProperties.override(environment);
CompositePropertySource composite = new OriginTrackedCompositePropertySource(
"configService");
RestTemplate restTemplate = this.restTemplate == null
? getSecureRestTemplate(properties) : this.restTemplate;
Exception error = null;
String errorBody = null;
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) {
log(result);
// result.getPropertySources() can be null if using xml
if (result.getPropertySources() != null) {
for (PropertySource source : result.getPropertySources()) {
@SuppressWarnings("unchecked")
Map<String, Object> map = translateOrigins(source.getName(),
(Map<String, Object>) source.getSource());
composite.addPropertySource(
new OriginTrackedMapPropertySource(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;
}
}
errorBody = String.format("None of labels %s found", Arrays.toString(labels));
}
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"
+ (errorBody == null ? "" : ": " + errorBody),
error);
}
logger.warn("Could not locate PropertySource: "
+ (error != null ? error.getMessage() : errorBody));
return null;
}
然后在PropertySourceBootstrapConfiguration.insertPropertySources(propertySources, composite)方法中将从配置中心获取的配置设置到首位,即调用了addfirst方法。
Config Server获取配置过程
服务器端去远程仓库加载配置的流程就比较简单了,核心接口是: ,提供了 配置读取的功能。我们先从请求入口开始看
EnvironmentController
Spring Cloud Config Server提供了EnvironmentController,这样通过在浏览器访问即可从git中获取配 置信息
在这个controller中,提供了很多的映射,最终会调用的是 getEnvironment 。
public Environment getEnvironment(String name, String profiles, String label,
boolean includeOrigin) {
name = Environment.normalize(name);
label = Environment.normalize(label);
Environment environment = this.repository.findOne(name, profiles, label,
includeOrigin);
if (!this.acceptEmpty
&& (environment == null || environment.getPropertySources().isEmpty())) {
throw new EnvironmentNotFoundException("Profile Not found");
}
return environment;
}
this.repository.findOne ,调用某个repository存储组件来获得环境配置信息进行返回。
repository是一个 EnvironmentRepository 对象,它有很多实现,其中就包含 RedisEnvironmentRepository 、 JdbcEnvironmentRepository 等。默认实现是 MultipleJGitEnvironmentRepository ,表示多个不同地址的git数据源。
MultipleJGitEnvironmentRepository.findOne
MultipleJGitEnvironmentRepository 代理遍历每个 JGitEnvironmentRepository, JGitEnvironmentRepository 下使用 NativeEnvironmentRepository 代理读取本地文件。
public Environment findOne(String application, String profile, String label,
boolean includeOrigin) {
//遍历所有git
for (PatternMatchingJGitEnvironmentRepository repository : this.repos.values()) {
if (repository.matches(application, profile, label)) {
for (JGitEnvironmentRepository candidate : getRepositories(repository,
application, profile, label)) {
try {
if (label == null) {
label = candidate.getDefaultLabel();
}
Environment source = candidate.findOne(application, profile,
label, includeOrigin);
if (source != null) {
return source;
}
}
catch (Exception e) {
if (this.logger.isDebugEnabled()) {
this.logger.debug(
"Cannot load configuration from " + candidate.getUri()
+ ", cause: (" + e.getClass().getSimpleName()
+ ") " + e.getMessage(),
e);
}
continue;
}
}
}
}
JGitEnvironmentRepository candidate = getRepository(this, application, profile,
label);
if (label == null) {
label = candidate.getDefaultLabel();
}
if (candidate == this) {
return super.findOne(application, profile, label, includeOrigin);
}
return candidate.findOne(application, profile, label, includeOrigin);
}
AbstractScmEnvironmentRepository.findOne
调用抽象类的findOne方法,主要有两个核心逻辑
- 调用getLocations从GIT远程仓库同步到本地
- 使用 NativeEnvironmentRepository 委托来读取本地文件内容
public synchronized Environment findOne(String application, String profile,
String label, boolean includeOrigin) {
NativeEnvironmentRepository delegate = new NativeEnvironmentRepository(
getEnvironment(), new NativeEnvironmentProperties());
Locations locations = getLocations(application, profile, label);
delegate.setSearchLocations(locations.getLocations());
Environment result = delegate.findOne(application, profile, "", includeOrigin);
result.setVersion(locations.getVersion());
result.setLabel(label);
return this.cleaner.clean(result, getWorkingDirectory().toURI().toString(),
getUri());
}
JGitEnvironmentRepository.getLocations
public synchronized Locations getLocations(String application, String profile,
String label) {
if (label == null) {
label = this.defaultLabel;
}
String version = refresh(label);
return new Locations(application, profile, label, version,
getSearchLocations(getWorkingDirectory(), application, profile, label));
}
JGitEnvironmentRepository.refresh
看到checkout的时候,就有了悟了的感觉,原来这货能保证每次获取的都是最新配置的原因,原来是这货每次都去checkout!
public String refresh(String label) {
Git git = null;
try {
git = createGitClient();
if (shouldPull(git)) {
FetchResult fetchStatus = fetch(git, label);
if (this.deleteUntrackedBranches && fetchStatus != null) {
deleteUntrackedLocalBranches(fetchStatus.getTrackingRefUpdates(),
git);
}
// checkout after fetch so we can get any new branches, tags, ect.
checkout(git, label);
tryMerge(git, label);
}
else {
// nothing to update so just checkout and merge.
// Merge because remote branch could have been updated before
checkout(git, label);
tryMerge(git, label);
}
// always return what is currently HEAD as the version
return git.getRepository().findRef("HEAD").getObjectId().getName();
}
catch (RefNotFoundException e) {
throw new NoSuchLabelException("No such label: " + label, e);
}
catch (NoRemoteRepositoryException e) {
throw new NoSuchRepositoryException("No such repository: " + getUri(), e);
}
catch (GitAPIException e) {
throw new NoSuchRepositoryException(
"Cannot clone or checkout repository: " + getUri(), e);
}
catch (Exception e) {
throw new IllegalStateException("Cannot load environment", e);
}
finally {
try {
if (git != null) {
git.close();
}
}
catch (Exception e) {
this.logger.warn("Could not close git repository", e);
}
}
}
至此,springcloudconfig解析完毕。