前言

本文针对版本2.2.0.RELEASE来分析SpringBoot的配置处理源码,通过查看SpringBoot的源码来弄清楚一些常见的问题比如:

  1. SpringBoot从哪里开始加载配置文件?
  2. SpringBoot从哪些地方加载配置文件?
  3. SpringBoot是如何支持yamlproperties类型的配置文件?
  4. 如果要支持json配置应该如何做?
  5. SpringBoot的配置优先级是怎么样的?
  6. placeholder是如何被解析的?

带着我们的问题一起去看一下SpringBoot配置相关的源代码,找出问题的答案。

SpringBoot从哪里开始加载配置文件?

SpringBoot加载配置文件的入口是由ApplicationEnvironmentPreparedEvent事件进入的,SpringBoot会在SpringApplication的构造函数中通过spring.factories文件获取ApplicationListener的实例类:

public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
...
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
...
}

spring.factories中有一个ConfigFileApplicationListener类,它会监听ApplicationEnvironmentPreparedEvent然后再加载配置文件 :

# Application Listeners
org.springframework.context.ApplicationListener= org.springframework.boot.context.config.ConfigFileApplicationListener
...

有了事件和事件处理的类后,再找出发送事件的地方,就可以搞清楚SpringBoot是怎么加载配置文件的了,SpringBoot在启动之前先初始化好SpringApplicationRunListeners这个类,它会实现SpringApplicationRunListener接口然后对事件进行转发:

class SpringApplicationRunListeners {

	private final Log log;

	private final List<SpringApplicationRunListener> listeners;

	SpringApplicationRunListeners(Log log, Collection<? extends SpringApplicationRunListener> listeners) {
this.log = log;
this.listeners = new ArrayList<>(listeners);
} void environmentPrepared(ConfigurableEnvironment environment) {
for (SpringApplicationRunListener listener : this.listeners) {
listener.environmentPrepared(environment);
}
}
...
}

获取SpringApplicationRunListeners的代码如下:


private SpringApplicationRunListeners getRunListeners(String[] args) {
Class<?>[] types = new Class<?>[] { SpringApplication.class, String[].class };
return new SpringApplicationRunListeners(logger,
getSpringFactoriesInstances(SpringApplicationRunListener.class, types, this, args));
}

同样也会去加载spring.factories文件,该文件有一个EventPublishingRunListener类,该类的作用就是SpringBoot的事件转换成ApplicationEvent发送出去。

# Run Listeners
org.springframework.boot.SpringApplicationRunListener=\
org.springframework.boot.context.event.EventPublishingRunListener

小结

  • SpringBoot会将事件转换成ApplicationEvent再分发
  • SpringBoot是通过监听ApplicationEnvironmentPreparedEvent事件来加载配置文件的
  • ConfigFileApplicationListener是处理配置文件的主要类

SpringBoot从哪些地方加载配置文件?

上面已经分析到ConfigFileApplicationListener是处理配置文件的主要类,然后进一步的查看SpringBoot是从哪些地址加载配置文件,进入ConfigFileApplicationListener类后会有两个默认的常量:

private static final String DEFAULT_SEARCH_LOCATIONS = "classpath:/,classpath:/config/,file:./,file:./config/";
private static final String DEFAULT_NAMES = "application";

首先在没有任何配置的情况下,会从DEFAULT_SEARCH_LOCATIONS常量列出来的位置中加载文件名为DEFAULT_NAMES(.properties或yml)的文件,默认位置包括:

  • classpath根目录(classpath:/)
  • classpath里面的config文件目录(classpath:/config/)
  • 程序运行目录(file:./)
  • 程序运行目录下的config目录(file:./config/)

上面说的是没有额外配置的情况,SpringBoot足够灵活可以指定配置文件搜索路径、配置文件名,在ConfigFileApplicationListener类中有个getSearchLocations方法,它主要负责获取配置搜索目录:

private Set<String> getSearchLocations() {
if (this.environment.containsProperty(CONFIG_LOCATION_PROPERTY)) {
return getSearchLocations(CONFIG_LOCATION_PROPERTY);
}
Set<String> locations = getSearchLocations(CONFIG_ADDITIONAL_LOCATION_PROPERTY);
locations.addAll(
asResolvedSet(ConfigFileApplicationListener.this.searchLocations, DEFAULT_SEARCH_LOCATIONS));
return locations;
}

它的操作步骤大致如下:

  1. 检查是否有spring.config.location属性,如果存在则直接使用它的值
  2. spring.config.additional-location属性中获取搜索路径
  3. 将默认搜索路径添加到搜索集合

这里就可以确定SpringBoot配置的搜索路径有两种情况:如果配置了spring.config.location则直接使用,否则使用spring.config.additional-location的属性值 + 默认搜索路径。

SpringBoot是如何支持yamlproperties类型的配置文件?

SpringBoot的配置支持propertiesyaml文件,SpringBoot是如何解析这两种文件的呢,继续分析ConfigFileApplicationListener这个类,里面有个子类叫Loader加载配置文件主要的工作就是由这货负责,但是直接读取propertiesyaml并转换成PropertySource还是由里面的PropertySourceLoader负责:

Loader(ConfigurableEnvironment environment, ResourceLoader resourceLoader) {
...
this.propertySourceLoaders = SpringFactoriesLoader.loadFactories(PropertySourceLoader.class,
getClass().getClassLoader());
}

构造Loader对象的时候就会先加载PropertySourceLoader,加载方式还是从spring.factories中读取:

# PropertySource Loaders
org.springframework.boot.env.PropertySourceLoader=\
org.springframework.boot.env.PropertiesPropertySourceLoader,\
org.springframework.boot.env.YamlPropertySourceLoader

其中配置了两个PropertySourceLoader的实现类:

  • PropertiesPropertySourceLoader
  • YamlPropertySourceLoader

看名字就知道是分别负责propertiesyaml的啦。

如果要支持json配置应该如何做?

如果不喜欢propertiesyaml这两种格式,想要定义json做为配置文字格式可以直接定义json类型的PropertySourceLoader:

public class JSONPropertySourceLoader implements PropertySourceLoader {

    @Override
public String[] getFileExtensions() {
return new String[] {"json"};
} @Override
public List<PropertySource<?>> load(String name, Resource resource) throws IOException { if(resource == null || !resource.exists()){
return Collections.emptyList();
} Map<String, Object> configs = JSON.parseObject(resource.getInputStream(), Map.class); return Collections.singletonList(
new MapPropertySource(name, configs)
);
}
}

然后在resources目录里面建立个META-INF,再添加个spring.factories里面的内容如下:

org.springframework.boot.env.PropertySourceLoader=\
com.csbaic.arch.spring.env.loader.JSONPropertySourceLoader

最后在resources目录里面建个application.json的配置文件 :

{
"spring.application.name": "JSONConfig"
}

正常启动SpringBoot获取spring.applicaiton.name的配置的值就是JSONConfig

2019-11-02 14:50:17.730  INFO 55275 --- [           main] c.c.a.spring.env.SpringEnvApplication    : JSONConfig

SpringBoot的配置优先级是怎么样的?

SpringBoot中有个PropertySource接口,专门用来保存属性常见的实现类有:

  • CommandLinePropertySource
  • MapPropertySource
  • SystemEnvironmentPropertySource
  • ....

另外为了集中管理PropertySource还抽象出一个PropertySources接口,PropertySources就一个实现类叫:MutablePropertySources,它将所有的PropertySource都放置在一个名叫propertySourceList集合中,同时提供一些修改操作方法:

public void addFirst(PropertySource<?> propertySource) {}
public void addLast(PropertySource<?> propertySource) {}
public void addBefore(String relativePropertySourceName, PropertySource<?> propertySource) {}
public void addAfter(String relativePropertySourceName, PropertySource<?> propertySource) {}
public int precedenceOf(PropertySource<?> propertySource) { }
public PropertySource<?> remove(String name) {}
public void replace(String name, PropertySource<?> propertySource) {}

所有的PropertySource都保存在propertySourceList中,越小的索引优先级越高,所以如果想要覆盖属性只要保证优化级够高就行。

placeholder是如何被解析的?

继续分析ConfigFileApplicationListenerLoader子类,在构造时还会创建一个PropertySourcesPlaceholdersResolver,placeholder的解析都由它来完成:

Loader(ConfigurableEnvironment environment, ResourceLoader resourceLoader) {

	this.placeholdersResolver = new PropertySourcesPlaceholdersResolver(this.environment);
}

分析PropertySourcesPlaceholdersResolver发现,真正完成解析是由PropertyPlaceholderHelper完成,PropertySourcesPlaceholdersResolver 在构造的时候就会创建一个PropertyPlaceholderHelper

public PropertySourcesPlaceholdersResolver(Iterable<PropertySource<?>> sources, PropertyPlaceholderHelper helper) {
this.sources = sources;
this.helper = (helper != null) ? helper : new PropertyPlaceholderHelper(SystemPropertyUtils.PLACEHOLDER_PREFIX,
SystemPropertyUtils.PLACEHOLDER_SUFFIX, SystemPropertyUtils.VALUE_SEPARATOR, true);
}

PropertySourcesPlaceholdersResolver 在创建 PropertyPlaceholderHelper 的时候会传递三个参数:前缀、后缀、默认值分割符,分别由以下三个常量表示:

public static final String PLACEHOLDER_PREFIX = "${";
public static final String PLACEHOLDER_SUFFIX = "}";
public static final String VALUE_SEPARATOR = ":";

这样 PropertyPlaceholderHelper 在解析placeholder时就能知道以什么格式来解析比如:${spring.application.name}这个placeholder就会被解析成属性值。

总结

SpringBoot的配置非常灵活配置可以来自文件、环境变量、JVM系统属性、配置中心等等,SpringBoot通过

PropertySourcePropertySources实现属性优先级、CRUD的统一管理,为开发者提供统一的配置抽象。



《架构文摘》每天一篇架构领域重磅好文,涉及一线互联网公司应用架构(高可用、高性 能、高稳定)、大数据、机器学习等各个热门领域。

SpringBoot是如何加载配置文件的?的更多相关文章

  1. SpringBoot启动如何加载application.yml配置文件

    一.前言 在spring时代配置文件的加载都是通过web.xml配置加载的(Servlet3.0之前),可能配置方式有所不同,但是大多数都是通过指定路径的文件名的形式去告诉spring该加载哪个文件: ...

  2. springboot属性类自动加载配置文件中的值

    springboot属性类自动加载配置文件中的值,如Person类加载在yml中配置的name,age等属性值,可以通过如下步骤获取: 类上添加@ConfigurationProperties注解,p ...

  3. springboot(五) 加载配置文件优先级顺序

    github代码地址:https://github.com/showkawa/springBoot_2017/tree/master/spb-demo/spb-brian-query-service ...

  4. SpringBoot加载配置文件(@PropertySource@importSource@Value)

    情景描述 最近新搭建了一个项目,从Spring迁到了Springboot,为了兼容Spring加载配置文件的风格,所以还想把PropertyPlaceholderConfigurer放在.xml文件里 ...

  5. Springboot 加载配置文件源码分析

    Springboot 加载配置文件源码分析 本文的分析是基于springboot 2.2.0.RELEASE. 本篇文章的相关源码位置:https://github.com/wbo112/blogde ...

  6. SpringBoot 配置的加载

    SpringBoot 配置的加载 SpringBoot配置及环境变量的加载提供许多便利的方式,接下来一起来学习一下吧! 本章内容的源码按实战过程采用小步提交,可以按提交的节点一步一步来学习,仓库地址: ...

  7. 微服务架构 | *2.3 Spring Cloud 启动及加载配置文件源码分析(以 Nacos 为例)

    目录 前言 1. Spring Cloud 什么时候加载配置文件 2. 准备 Environment 配置环境 2.1 配置 Environment 环境 SpringApplication.prep ...

  8. spring加载配置文件

    spring加载配置文件 1.把applicationContext.xml直接放在WEB-INF/classes下,spring会采用默认的加载方式2.采用在web.xml中配置ContextLoa ...

  9. spring-自动加载配置文件\使用属性文件注入

    在上一篇jsf环境搭建的基础上 , 加入spring框架 , 先看下目录结构 src/main/resources 这个source folder 放置web项目所需的主要配置,打包时,会自动打包到W ...

随机推荐

  1. Spring BeanDefinition的加载

     前面提到AbstractRefreshableApplicationContext在刷新BeanFactory时,会调用loadBeanDefinitions方法以加载系统中Bean的定义,下面将讲 ...

  2. 微信小程序项目-你是什么垃圾?

    垃圾分类特别火也不知道北京什么时候也开始执行,看见之前上海市民被灵魂拷问了以后垃圾真的不知道如何丢了,作为程序员就做一个小程序造福人类吧. 效果图: 一.全局的app.json和app.wxss加入了 ...

  3. Hexo 博客快速整合公众号导流工具,不用互推也能实现粉丝躺增!

    readmore 插件简介 Hexo 整合 OpenWrite 平台的 readmore 插件,实现博客的每一篇文章自动增加阅读更多效果,关注公众号后方可解锁全站文章,从而实现博客流量导流到微信公众号 ...

  4. Django默认权限机制介绍及实践

    演示Django版本为当前最新版本v2.2 当Django配置文件中的INSTALL_APPS包含了django.contrib.auth时,就默认启用了一个简单的权限系统,提供了为用户或组分配权限的 ...

  5. Vue/React圆环进度条

    数据展示,一直是各行各业乐此不疲的需求,具体到前端开发行业,则是各种各种图表数据展示,各种表格数据展示,烦不胜烦(繁不胜繁)! 前几天刚做了折线图.柱状图.饼状图之类的图表数据展示效果,今天又碰到了类 ...

  6. TF-IDF算法——原理及实现

    TF-IDF算法是一种用于信息检索与数据挖掘的常用加权技术.TF的意思是词频(Term - frequency),IDF的意思是逆向文件频率(inverse Document frequency). ...

  7. 01 python安装与初识

    一.简要概述 python学习时设计要大于开发. 二.编程语言 1.分类 编程语言分为高级语言和低级语言.高级语言如python.c#,Java.PHP等,低级语言(基础语言)如C.汇编语言. 2.机 ...

  8. Lab_0操作系统实验准备(全)

    一.实验介绍 实验目的: 知识储备: 二.相关下载 1.下载镜像文件 这个镜像文件是vdi类型的,只能用visualbox下载 链接:https://pan.baidu.com/s/1L7WX6ju4 ...

  9. MIPI CSI2-TX接口基于FPGA实现

    MIPI CSI2-TX用途: 跟海思的3559A芯片进行图像数据传输: MIPI CSI2-TX接口特性: xilinx 7系列芯片最大支持1.25Gbps: 最大支持lanes数量为4: 支持的图 ...

  10. react16 路由按需加载、路由权限配置

    1. 路由按需加载: 不做按需加载,代码全部打包在bundle.js 文件里,首屏渲染很慢,项目文件较多,会出现1分钟加载的可能性. import React, { Component } from ...