SpringBoot版本 2.3.5.RELEASE
SpringCloud版本 Hoxton.SR9
本文只讨论配置文件加载,以bootstrap.yml和application.yml为例,后缀名的加载顺序可以通过源码看到.
bootstrap.yml是SpringCloud使用的配置文件,SpringBoot中其实并没有加载bootStrap.yml的默认实现
SpringBoot加载配置文件的方式是使用了观察者模式,在启动时发出一个事件(ApplicationEnvironmentPreparedEvent),然后基于这个事件,来做配置文件的加载或者其他的一些操作,这种模式扩展性较强.
而bootstrap.yml的加载就借助了这种模式,SpringCloud扩展了一个BootstrapApplicationListener监听器,来处理该事件,在这个监听器里做加载.
先看Springboot配置文件的加载
从springBoot启动起
public static void main(String[] args) {
SpringApplication.run(Demo1Application.class, args);
}
public static ConfigurableApplicationContext run(Class<?> primarySource, String... args) {
return run(new Class<?>[] { primarySource }, args);
}
/**
* Static helper that can be used to run a {@link SpringApplication} from the
* specified sources using default settings and user supplied arguments.
* @param primarySources the primary sources to load
* @param args the application arguments (usually passed from a Java main method)
* @return the running {@link ApplicationContext}
*/
public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {
// 此处new SpringApplication 进行一些默认的初始化
return new SpringApplication(primarySources).run(args);
}
/**
* Run the Spring application, creating and refreshing a new
* {@link ApplicationContext}.
* @param args the application arguments (usually passed from a Java main method)
* @return a running {@link ApplicationContext}
*/
public ConfigurableApplicationContext run(String... args) {
StopWatch stopWatch = new StopWatch();
stopWatch.start();
ConfigurableApplicationContext context = null;
Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
configureHeadlessProperty();
SpringApplicationRunListeners listeners = getRunListeners(args);
listeners.starting();
try {
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
// 在此处进行环境的处理 也就是配置文件的的读取和加载
ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
configureIgnoreBeanInfo(environment);
Banner printedBanner = printBanner(environment);
context = createApplicationContext();
exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class,
new Class[] { ConfigurableApplicationContext.class }, context);
prepareContext(context, environment, listeners, applicationArguments, printedBanner);
refreshContext(context);
afterRefresh(context, applicationArguments);
stopWatch.stop();
if (this.logStartupInfo) {
new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
}
listeners.started(context);
callRunners(context, applicationArguments);
}
catch (Throwable ex) {
handleRunFailure(context, ex, exceptionReporters, listeners);
throw new IllegalStateException(ex);
}
try {
listeners.running(context);
}
catch (Throwable ex) {
handleRunFailure(context, ex, exceptionReporters, null);
throw new IllegalStateException(ex);
}
return context;
}
private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners,
ApplicationArguments applicationArguments) {
// Create and configure the environment
ConfigurableEnvironment environment = getOrCreateEnvironment();
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;
}
void environmentPrepared(ConfigurableEnvironment environment) {
for (SpringApplicationRunListener listener : this.listeners) {
listener.environmentPrepared(environment);
}
}
@Override
public void environmentPrepared(ConfigurableEnvironment environment) {
this.initialMulticaster
.multicastEvent(new ApplicationEnvironmentPreparedEvent(this.application, this.args, environment));
}
@Override
public void multicastEvent(final ApplicationEvent event, @Nullable ResolvableType eventType) {
ResolvableType type = (eventType != null ? eventType : resolveDefaultEventType(event));
Executor executor = getTaskExecutor();
// getApplicationListeners 获取监听器列表
for (ApplicationListener<?> listener : getApplicationListeners(event, type)) {
if (executor != null) {
executor.execute(() -> invokeListener(listener, event));
}
else {
// 执行处理方法
invokeListener(listener, event);
}
}
}
可以通过debug进入getApplicationListeners看一下,这里根据事件的类型查询可以处理该事件的监听器
其中ConfigFileApplicationListener就是重点对象,就是这个监听器加载了配置文件
继续进入重载方法
/**
* Invoke the given listener with the given event.
* @param listener the ApplicationListener to invoke
* @param event the current event to propagate
* @since 4.1
*/
protected void invokeListener(ApplicationListener<?> listener, ApplicationEvent event) {
ErrorHandler errorHandler = getErrorHandler();
if (errorHandler != null) {
try {
doInvokeListener(listener, event);
}
catch (Throwable err) {
errorHandler.handleError(err);
}
}
else {
doInvokeListener(listener, event);
}
}
// 重载方法
private void doInvokeListener(ApplicationListener listener, ApplicationEvent event) {
try {
// 可以看到是调用了对应监听器的onApplicationEvent方法
listener.onApplicationEvent(event);
}
catch (ClassCastException ex) {
String msg = ex.getMessage();
if (msg == null || matchesClassCastMessage(msg, event.getClass())) {
// Possibly a lambda-defined listener which we could not resolve the generic event type for
// -> let's suppress the exception and just log a debug message.
Log logger = LogFactory.getLog(getClass());
if (logger.isTraceEnabled()) {
logger.trace("Non-matching event type for listener: " + listener, ex);
}
}
else {
throw ex;
}
}
}
ConfigFileApplicationListener就是最终进行操作的类,在这个类里定义了配置文件默认目录和默认名字
进入onApplicationEvent方法
看到基于不同的事件,进行不同的处理
@Override
public void onApplicationEvent(ApplicationEvent event) {
if (event instanceof ApplicationEnvironmentPreparedEvent) {
// 进入此方法,加载配置文件
onApplicationEnvironmentPreparedEvent((ApplicationEnvironmentPreparedEvent) event);
}
if (event instanceof ApplicationPreparedEvent) {
onApplicationPreparedEvent(event);
}
}
接下来就是配置文件的加载,接下面的源码就是分析SpringBoot如何加载application-xxx.yml
紧跟上文进入onApplicationEnvironmentPreparedEvent方法
private void onApplicationEnvironmentPreparedEvent(ApplicationEnvironmentPreparedEvent event) {
// 根据spring.factories文件加载处理器. 有兴趣可以debug进去看下
List<EnvironmentPostProcessor> postProcessors = loadPostProcessors();
// 把ConfigFileApplicationListener也加进去
postProcessors.add(this);
// 根据Order排序处理器
AnnotationAwareOrderComparator.sort(postProcessors);
// 循环执行处理器的处理方法
for (EnvironmentPostProcessor postProcessor : postProcessors) {
postProcessor.postProcessEnvironment(event.getEnvironment(), event.getSpringApplication());
}
}
此处重点在于把ConfigFileApplicationListener加载到了postProcessors中,
可以debug看一下postProcessors
然后接下来就会执行ConfigFileApplicationListener.postProcessEnvironment方法
进入ConfigFileApplicationListener.postProcessEnvironment方法
在进入addPropertySources方法,
可以看到new Loader(environment, resourceLoader).load();
Loader是ConfigFileApplicationListener的一个内部类,在load方法内进行配置文件的加载
@Override
public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) {
addPropertySources(environment, application.getResourceLoader());
}
/**
* Add config file property sources to the specified environment.
* @param environment the environment to add source to
* @param resourceLoader the resource loader
* @see #addPostProcessors(ConfigurableApplicationContext)
*/
protected void addPropertySources(ConfigurableEnvironment environment, ResourceLoader resourceLoader) {
RandomValuePropertySource.addToEnvironment(environment);
new Loader(environment, resourceLoader).load();
}
这串代码就是配置文件的加载
void load() {
FilteredPropertySource.apply(this.environment, DEFAULT_PROPERTIES, LOAD_FILTERED_PROPERTY,
(defaultProperties) -> {
// 初始化配置
this.profiles = new LinkedList<>();
this.processedProfiles = new LinkedList<>();
// 默认启用false
this.activatedProfiles = false;
this.loaded = new LinkedHashMap<>();
// 初始化配置, 也就是default
initializeProfiles();
// 初始化后this.profiles会有一个default的配置
while (!this.profiles.isEmpty()) {
Profile profile = this.profiles.poll();
if (isDefaultProfile(profile)) {
addProfileToEnvironment(profile.getName());
}
// 进入此方法就可以看到配置文件的加载了 addToLoaded是一个回调,主要是配置属性的合并
load(profile, this::getPositiveProfileFilter,
addToLoaded(MutablePropertySources::addLast, false));
this.processedProfiles.add(profile);
}
load(null, this::getNegativeProfileFilter, addToLoaded(MutablePropertySources::addFirst, true));
addLoadedPropertySources();
applyActiveProfiles(defaultProperties);
});
}
initializeProfiles后会默认初始化一个default的配置
循环配置调用重载的load方法
进入上文中的load方法
private void load(Profile profile, DocumentFilterFactory filterFactory, DocumentConsumer consumer) {
// getSearchLocations这个会获取默认的配置文件路径
getSearchLocations().forEach((location) -> {
// 是否是目录,默认配置文件路径都是以/结尾的
boolean isDirectory = location.endsWith("/");
// 获取默认的配置文件名称也就是spring.config.name属性 默认application
Set<String> names = isDirectory ? getSearchNames() : NO_SEARCH_NAMES;
// 再次循环 调用load重载方法
names.forEach((name) -> load(location, name, profile, filterFactory, consumer));
});
}
可以看到getSearchLocations方法会获取配置文件路径,如下,正是SpringBoot默认的配置文件加载顺序,但是这个顺序是反过来的
进入load重载方法
private void load(String location, String name, Profile profile, DocumentFilterFactory filterFactory,
DocumentConsumer consumer) {
// 如果配置文件名称为空进行处理,默认都是有值的 application
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<>();
// this.propertySourceLoaders 配置文件加载器默认有两种实现 yaml和properties
for (PropertySourceLoader loader : this.propertySourceLoaders) {
// 根据配置文件加载器循环
for (String fileExtension : loader.getFileExtensions()) {
if (processed.add(fileExtension)) {
// 根据路径+名字+文件后缀名加载配置文件
loadForFileExtension(loader, location + name, "." + fileExtension, profile, filterFactory,
consumer);
}
}
}
}
继续重载
private void loadForFileExtension(PropertySourceLoader loader, String prefix, String fileExtension,
Profile profile, DocumentFilterFactory filterFactory, DocumentConsumer consumer) {
DocumentFilter defaultFilter = filterFactory.getDocumentFilter(null);
DocumentFilter profileFilter = filterFactory.getDocumentFilter(profile);
// 当profile不为空时处理即 active.profiles=xxx时 第一次进行不走这,还是默认的
if (profile != null) {
// Try profile-specific file & profile section in profile file (gh-340)
String profileSpecificFile = prefix + "-" + profile + fileExtension;
load(loader, profileSpecificFile, profile, defaultFilter, consumer);
load(loader, profileSpecificFile, profile, profileFilter, consumer);
// Try profile specific sections in files we've already processed
for (Profile processedProfile : this.processedProfiles) {
if (processedProfile != null) {
String previouslyLoaded = prefix + "-" + processedProfile + fileExtension;
load(loader, previouslyLoaded, profile, profileFilter, consumer);
}
}
}
// 默认的配置文件处理 default
// Also try the profile-specific section (if any) of the normal file
load(loader, prefix + fileExtension, profile, profileFilter, consumer);
}
这次重载就是到头了,在这个方法里就会进行属性的读取
private void load(PropertySourceLoader loader, String location, Profile profile, DocumentFilter filter,
DocumentConsumer consumer) {
// 根据2.10.6 中拼接的路径加载
Resource[] resources = getResources(location);
for (Resource resource : resources) {
try {
// 当该拼接的文件不存在时,会直接进行下一次循环
if (resource == null || !resource.exists()) {
if (this.logger.isTraceEnabled()) {
StringBuilder description = getDescription("Skipped missing config ", location, resource,
profile);
this.logger.trace(description);
}
continue;
}
// 配置文件为空时也不加载
if (!StringUtils.hasText(StringUtils.getFilenameExtension(resource.getFilename()))) {
if (this.logger.isTraceEnabled()) {
StringBuilder description = getDescription("Skipped empty config extension ", location,
resource, profile);
this.logger.trace(description);
}
continue;
}
// 隐藏路径时返回 安全?
if (resource.isFile() && hasHiddenPathElement(resource)) {
if (this.logger.isTraceEnabled()) {
StringBuilder description = getDescription("Skipped location with hidden path element ",
location, resource, profile);
this.logger.trace(description);
}
continue;
}
// 开始正式加载
String name = "applicationConfig: [" + getLocationName(location, resource) + "]";
// 加载配置文件的元素
List<Document> documents = loadDocuments(loader, name, resource);
// 如果配置文件里没有解析出元素
if (CollectionUtils.isEmpty(documents)) {
if (this.logger.isTraceEnabled()) {
StringBuilder description = getDescription("Skipped unloaded config ", location, resource,
profile);
this.logger.trace(description);
}
continue;
}
List<Document> loaded = new ArrayList<>();
// 默认active profile 为default
for (Document document : documents) {
if (filter.match(document)) {
addActiveProfiles(document.getActiveProfiles());
addIncludedProfiles(document.getIncludeProfiles());
loaded.add(document);
}
}
// 反转属性顺序 目前没看到啥作用.
Collections.reverse(loaded);
if (!loaded.isEmpty()) {
// consumer.accept(profile, document)在回调里合并属性 即高优先级的覆盖低优先级的属性
loaded.forEach((document) -> consumer.accept(profile, document));
if (this.logger.isDebugEnabled()) {
StringBuilder description = getDescription("Loaded config file ", location, resource,
profile);
this.logger.debug(description);
}
}
}
catch (Exception ex) {
StringBuilder description = getDescription("Failed to load property source from ", location,
resource, profile);
throw new IllegalStateException(description.toString(), ex);
}
}
}
private DocumentConsumer addToLoaded(BiConsumer<MutablePropertySources, PropertySource<?>> addMethod,
boolean checkForExisting) {
return (profile, document) -> {
if (checkForExisting) { // 检查属性是否存在,如果是第一次加载默认的配置文件这个参数为false,2.10.3中可以看到
for (MutablePropertySources merged : this.loaded.values()) {
// 如果参数已存在 就不在加载了
if (merged.contains(document.getPropertySource().getName())) {
return;
}
}
}
// 第一次加载或者属性不存在的化直接放入this.loaded
MutablePropertySources merged = this.loaded.computeIfAbsent(profile,
(k) -> new MutablePropertySources());
// 调用回调 MutablePropertySources::addLast
addMethod.accept(merged, document.getPropertySource());
};
}
addToLoaded 主要就是为了高优先级的属性覆盖低优先级的属性
SpringBoot基于观察者模式,在ApplicationEnvironmentPreparedEvent事件中,对配置文件加载,配置文件的加载主要在ConfigFileApplicationListener这个类中,基于PropertiesPropertySourceLoader 和 YamlPropertySourceLoader加载配置文件的属性,并根据加载文件的顺序来做高优先级的覆盖低优先级的属性.
bootstrap.yml的加载其实还是使用SpringBoot加载配置文件,只不过在SpringBoot的加载之前,先创建了一个名为bootstrap(默认)的context
引入SpringCloud的依赖后
从上文中2.7开始,会发现listeners多了一个,并且优先级相当高
进入该监听器
进入BootstrapApplicationListener的onApplicationEvent方法,在此方法中进行bootsrap.yml的读取
String configLocation = environment
.resolvePlaceholders("${spring.cloud.bootstrap.location:}");
String configAdditionalLocation = environment
.resolvePlaceholders("${spring.cloud.bootstrap.additional-location:}");
Map<String, Object> bootstrapMap = new HashMap<>();
// 给配置文件一个名字 加载bootstrap,yml就是靠这个
bootstrapMap.put("spring.config.name", configName);
@Override
public void onApplicationEvent(ApplicationEnvironmentPreparedEvent event) {
// 获取上下文信息
ConfigurableEnvironment environment = event.getEnvironment();
// 判断是否启用spring.cloud.bootstrap.enabled属性,默认启用,如果不启用就不加载bootstrap.yml文件,直接结束
if (!environment.getProperty("spring.cloud.bootstrap.enabled", Boolean.class,
true)) {
return;
}
// 如果已经执行过bootstrap的监听事件,就不再重复执行了,这也是为什么bootstrap.yml属性不变的原因.
// don't listen to events in a bootstrap context
if (environment.getPropertySources().contains(BOOTSTRAP_PROPERTY_SOURCE_NAME)) {
return;
}
// 开始初始化上下文
ConfigurableApplicationContext context = null;
// 获取配置文件名称 默认bootstrap (bootstrap.yml) 就是这里给配置文件复制了
String configName = environment
.resolvePlaceholders("${spring.cloud.bootstrap.name:bootstrap}");
// 寻找是否有父容器上下文的初始化器 ParentContextApplicationContextInitializer 正常情况下是没有的 直接往下走
for (ApplicationContextInitializer<?> initializer : event.getSpringApplication()
.getInitializers()) {
if (initializer instanceof ParentContextApplicationContextInitializer) {
context = findBootstrapContext(
(ParentContextApplicationContextInitializer) initializer,
configName);
}
}
// 没有ParentContextApplicationContextInitializer 父容器初始化的化,就创建一个
if (context == null) {
context = bootstrapServiceContext(environment, event.getSpringApplication(),
configName);
event.getSpringApplication()
.addListeners(new CloseContextOnFailureApplicationListener(context));
}
apply(context, event.getSpringApplication(), environment);
}
进入bootstrapServiceContext方法,方法太长不再全部粘贴,这个方法里最重要的就是根据bootstrap.yml创建出一个SpringApplication对象
// 创建SpringApplication对象
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();
// 创建context对象 注意: 此处是又执行了一次 SpringApplication.run()方法.
builder.sources(BootstrapImportSelectorConfiguration.class);
final ConfigurableApplicationContext context = builder.run();
// 设置父容器对象
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);
// 合并属性
mergeDefaultProperties(environment.getPropertySources(), bootstrapProperties);
加载配置文件还是上面SpringBoot加载application那一套,但是不同的是SpringBoot给的默认的application在这里并没有使用,而是使用了BootStrapApplicationListener中设置的spring.config.name 如下图.
在上文中2.10.4中使用getSearchNames获取要加载的文件名,看下这个方法
private Set<String> getSearchNames() {
if (this.environment.containsProperty(CONFIG_NAME_PROPERTY)) { //SpringBoot默认不走这个 bootstrap.yml的加载就是依赖这里
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); // 返回默认的Application
}
这样一来,除了加载文件的名字改变了,其他的都没变,还是SpringBoot这一套.
通过对SpringBoot2.3.5的源码的阅读,学习到如下: