文章目录
- 1. 外化配置简介
- 2. ApplicationArguements参数处理
- 3.ConfigFileApplicationListener
- 4. Loader
- 4.1 load()
- initializeProfiles()初始化Profile配置
- load(Profile profile, DocumentFilterFactory filterFactory, DocumentConsumer consumer)
- load(String location, String name, Profile profile, DocumentFilterFactory filterFactory,DocumentConsumer consumer)
- loadForFileExtension(PropertySourceLoader loader, String prefix, String fileExtension,Profile profile, DocumentFilterFactory filterFactory, DocumentConsumer consumer)
- load(PropertySourceLoader loader, String location, Profile profile, DocumentFilter filter,DocumentConsumer consumer)
- 4.2 PropertySourceLoader
这一章,主要分析: 外化配置文件、命令行参数、Profile实现机制及整个加载过程
1. 外化配置简介
1.简介
springboot允许我们将配置进行外部化处理,当我们使用同一套代码,可以在启动时指定不同的配置,从而在不同的环境中运行。
我们可以使用属性文件、yaml文件、环境变量、命令行参数来指定,在其中配置的属性,我们又可以通过@Value注解注入到对应的bean中、使用Environment对象访问、使用@ConfigurationProperties(基于类型安全的配置方式)绑定到结构化的对象中
2. 优先级
Spring Boot使用一种非常特殊的PropertySource顺序,旨在允许合理地覆盖值
1.Devtools 主目录上的全局设置属性(~/.spring-boot-devtools.properties当devtools处于活动状态时)。
2.@TestPropertySource 你的测试注释。
3.properties属性测试。可 用于测试特定应用程序片段@SpringBootTest的 测试注释。
4.命令行参数。
5.来自SPRING_APPLICATION_JSON(嵌入在环境变量或系统属性中的内联JSON)的属性。
6.ServletConfig init参数。
7.ServletContext init参数。
8.JNDI属性来自java:comp/env。
9.Java系统属性(System.getProperties())。
10.OS环境变量。
11.RandomValuePropertySource,只有在拥有random.*属性。
12.特定于配置文件的应用程序属性在打包的jar(application-{profile}.properties和YAML变体)之外。
13.打包在jar中的特定于配置文件的应用程序属性(application-{profile}.properties 以及YAML变体)。
14.打包jar之外的应用程序属性(application.properties以及YAML变体)。
15.打包在jar中的应用程序属性(application.properties和YAML变体)。
16.@PropertySource 在@Configuration类上的注释。
17.默认属性(由设置指定SpringApplication.setDefaultProperties)。
2. ApplicationArguements参数处理
2.1 ApplicationArguments
用来封装命令行参数,它的实现为DefaultApplicationArguments
public interface ApplicationArguments {
// 返回未经处理的参数(即: 从命令行中获取的最原始的参数, 多个参数使用空格隔开)
String[] getSourceArgs();
// 获取有配置项的参数名, 比如: java -jar demo.jar --foo=bar, 那这里的foo就属于有配置项参数
Set<String> getOptionNames();
// 某个参数是否有配置项(是否在有配置项的参数名集合中)
boolean containsOption(String name);
// 获取指定配置项参数名对应的值(可以指定多个值, 比如: java -jar demo.jar --foo=bar1 --foo=bar2)
List<String> getOptionValues(String name);
// 获取到所有没有配置项参数的集合
List<String> getNonOptionArgs();
}
DefaultApplicationArguments
作为ApplicationArguments 接口的实现,内部完全依赖其中的内部类Source来实现ApplicationArguments 接口,内部类Source继承自SimpleCommandLinePropertySource,属于PropertySource(命令行属性配置源)。
PropertySource描述了属性源,属性源有1个自己的名字和1个用来获取属性值的source,这个source是泛型,具体由子类定义实现,该类只负责维护这2个属性,详情可见:spring属性配置文件解析&类型转换
public class DefaultApplicationArguments implements ApplicationArguments {
private final Source source;
private final String[] args;
public DefaultApplicationArguments(String... args) {
Assert.notNull(args, "Args must not be null");
// 完全将运行参数args传给内部类Source
this.source = new Source(args);
this.args = args;
}
@Override
public String[] getSourceArgs() {
return this.args;
}
// 关键的几个方法都交给了内部类对象source去实现
@Override
public Set<String> getOptionNames() {
String[] names = this.source.getPropertyNames();
return Collections.unmodifiableSet(new HashSet<>(Arrays.asList(names)));
}
@Override
public boolean containsOption(String name) {
return this.source.containsProperty(name);
}
@Override
public List<String> getOptionValues(String name) {
List<String> values = this.source.getOptionValues(name);
return (values != null) ? Collections.unmodifiableList(values) : null;
}
@Override
public List<String> getNonOptionArgs() {
return this.source.getNonOptionArgs();
}
// Source继承了SimpleCommandLinePropertySource
private static class Source extends SimpleCommandLinePropertySource {
// 将命令行参数数组, 交给SimpleCommandLinePropertySource 处理
Source(String[] args) {
super(args);
}
@Override
public List<String> getNonOptionArgs() {
return super.getNonOptionArgs();
}
@Override
public List<String> getOptionValues(String name) {
return super.getOptionValues(name);
}
}
}
SimpleCommandLinePropertySource
SimpleCommandLinePropertySource继承自CommandLinePropertySource<T>,它的父类在构造方法里,默认指定的PropertySource的名字是“commandLineArgs”
// 指定泛型实现类为 CommandLineArgs, 也即作为PropertySource的实现, source为CommandLineArgs, 获取到的source对象就是CommandLineArgs对象
public class SimpleCommandLinePropertySource extends CommandLinePropertySource<CommandLineArgs> {
public SimpleCommandLinePropertySource(String... args) {
// 使用SimpleCommandLineArgsParser解析命令行参数, 并传递给了PropertySource, 作为泛型实现
super(new SimpleCommandLineArgsParser().parse(args));
}
// 剩下的操作都是依赖于泛型CommandLineArgs来实现的,如下
@Override
public String[] getPropertyNames() {
return StringUtils.toStringArray(this.source.getOptionNames());
}
@Override
protected boolean containsOption(String name) {
return this.source.containsOption(name);
}
@Override
@Nullable
protected List<String> getOptionValues(String name) {
return this.source.getOptionValues(name);
}
@Override
protected List<String> getNonOptionArgs() {
return this.source.getNonOptionArgs();
}
}
SimpleCommandLineArgsParser
看看它是怎么解析命令行参数的
class SimpleCommandLineArgsParser {
public CommandLineArgs parse(String... args) {
// 先创建一个CommandLineArgs 对象
CommandLineArgs commandLineArgs = new CommandLineArgs();
// 遍历命令行参数数组
for (String arg : args) {
// 如果参数是以--k开头, 则将此参数添加到CommandLineArgs对象的optionArg属性中
if (arg.startsWith("--")) {
String optionText = arg.substring(2, arg.length());
String optionName;
String optionValue = null;
if (optionText.contains("=")) {
optionName = optionText.substring(0, optionText.indexOf('='));
optionValue = optionText.substring(optionText.indexOf('=')+1, optionText.length());
}
else {
optionName = optionText;
}
if (optionName.isEmpty() || (optionValue != null && optionValue.isEmpty())) {
throw new IllegalArgumentException("Invalid argument syntax: " + arg);
}
commandLineArgs.addOptionArg(optionName, optionValue);
}
else {
// 否则, 添加到commandLineArgs对象的nonOptionArg属性中
commandLineArgs.addNonOptionArg(arg);
}
}
return commandLineArgs;
}
}
2.2 prepareEnvironment(listeners, applicationArguments)
刚刚创建完environment对象
private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners,ApplicationArguments applicationArguments) {
// Create and configure the environment
ConfigurableEnvironment environment = getOrCreateEnvironment();
// 刚刚创建完environment后, 立即封装命令行参数(只关心这里)
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;
}
protected void configureEnvironment(ConfigurableEnvironment environment, String[] args) {
if (this.addConversionService) {
ConversionService conversionService = ApplicationConversionService.getSharedInstance();
environment.setConversionService((ConfigurableConversionService) conversionService);
}
// 配置属性源
configurePropertySources(environment, args);
// 配置profiles
configureProfiles(environment, args);
}
configurePropertySources(environment, args)
protected void configurePropertySources(ConfigurableEnvironment environment, String[] args) {
// 获取env维护的MutablePropertySources 对象
MutablePropertySources sources = environment.getPropertySources();
// 如果SpringApplication实例有设置defaultProperties,
// 则将它作为属性源添加到最后面(属性名为: defaultProperties)
if (this.defaultProperties != null && !this.defaultProperties.isEmpty()) {
sources.addLast(new MapPropertySource("defaultProperties", this.defaultProperties));
}
// 默认情况下, addCommandLineProperties 属性为true
if (this.addCommandLineProperties && args.length > 0) {
// 名字为: commandLineArgs,将作为属性源的名字
String name = CommandLinePropertySource.COMMAND_LINE_PROPERTY_SOURCE_NAME;
// 如果env中早就配置了这个属性源的名字, 则使用组合模式将原来的和命令行的组合到一起(命令行的有心啊)
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));
}
// 由此可见, 命令行参数具有很高的优先级
}
}
configureProfiles(environment, args);
protected void configureProfiles(ConfigurableEnvironment environment, String[] args) {
// SpringApplication实例的additionalProfiles属性可以自己指定
Set<String> profiles = new LinkedHashSet<>(this.additionalProfiles);
// 获取env对象的activeProfiles属性, 如果这个属性为空的话, 则从env中获取spring.profiles.active配置的值(可配置多个,使用逗号分隔)
// 添加到profiles集合中
profiles.addAll(Arrays.asList(environment.getActiveProfiles()));
// 将profiles设置为env的activeProfiles作为激活配置集合
environment.setActiveProfiles(StringUtils.toStringArray(profiles));
}
doGetActiveProfiles()
protected Set<String> doGetActiveProfiles() {
synchronized (this.activeProfiles) {
// 如果env的activeProfiles属性未指定任何激活配置,
// 则获取"spring.profiles.active"配置项对象的值,
// 此时, 我们应当注意到env中已经添加了命令行参数属性源,
// 那么, 如果我们在命令行参数中指定了 "spring.profiles.active"的值,
// 那激活的profiles不就是我们从外部指定的么
if (this.activeProfiles.isEmpty()) {
String profiles = getProperty(ACTIVE_PROFILES_PROPERTY_NAME);
if (StringUtils.hasText(profiles)) {
setActiveProfiles(StringUtils.commaDelimitedListToStringArray(
StringUtils.trimAllWhitespace(profiles)));
}
}
return this.activeProfiles;
}
}
3.ConfigFileApplicationListener
在前面获取env对象,并对env对象配置之后,springboot调用了listeners.environmentPrepared(environment);通知所有的SpringApplicationRunListeners的environmentPrepared环境对象已准备的方法,而提供的监听器实现类EventPublishingRunListener会发布ApplicationEnvironmentPreparedEvent事件。
在SpringApplicationRunListeners的内部获取到了SpringApplication实例的所有ApplicationListener,而SpringApplication实例的ApplicationListener在构造的时候,是通过SPI加载spring.factories获取的,其中就有ConfigFileApplicationListener,所以SpringApplicationRunListeners实例,在接收到environmentPrepared事件后,就会调用ConfigFileApplicationListener的onApplicationEvent方法。
public class ConfigFileApplicationListener implements EnvironmentPostProcessor, SmartApplicationListener, Ordered {
private static final String DEFAULT_PROPERTIES = "defaultProperties";
// 配置文件加载位置, 优先级从后面到前面
private static final String DEFAULT_SEARCH_LOCATIONS = "classpath:/,classpath:/config/,file:./,file:./config/";
// 配置文件默认名称为
private static final String DEFAULT_NAMES = "application";
// 文件名已在location中指定
private static final Set<String> NO_SEARCH_NAMES = Collections.singleton(null);
// 绑定为字符串数组
private static final Bindable<String[]> STRING_ARRAY = Bindable.of(String[].class);
// 绑定为字符串集合
private static final Bindable<List<String>> STRING_LIST = Bindable.listOf(String.class);
// defaultProperties属性源中需要忽略的属性(默认忽略: spring.profiles.active、spring.profiles.include)
private static final Set<String> LOAD_FILTERED_PROPERTY;
static {
Set<String> filteredProperties = new HashSet<>();
filteredProperties.add("spring.profiles.active");
filteredProperties.add("spring.profiles.include");
LOAD_FILTERED_PROPERTY = Collections.unmodifiableSet(filteredProperties);
}
// 指定激活的配置(可配置多个,使用逗号隔开)
public static final String ACTIVE_PROFILES_PROPERTY = "spring.profiles.active";
// 指定激活的配置子项(可配置多个,使用逗号隔开)
public static final String INCLUDE_PROFILES_PROPERTY = "spring.profiles.include";
// 指定默认配置文件名称
public static final String CONFIG_NAME_PROPERTY = "spring.config.name";
// 指定配置文件位置
public static final String CONFIG_LOCATION_PROPERTY = "spring.config.location";
// 指定附加的配置文件位置
public static final String CONFIG_ADDITIONAL_LOCATION_PROPERTY = "spring.config.additional-location";
// 指定当前EnvironmentPostProcessor实例的执行顺序
public static final int DEFAULT_ORDER = Ordered.HIGHEST_PRECEDENCE + 10;
// 默认查找配置文件的路径
private String searchLocations;
// 默认查找配置文件的名称
private String names;
// 比最高优先级要低10
private int order = DEFAULT_ORDER;
// ...省略方法
}
3.1 onApplicationEvent(ApplicationEvent event)
@Override
public void onApplicationEvent(ApplicationEvent event) {
// 很显然, 我们关注的正是ApplicationEnvironmentPreparedEvent这个事件
if (event instanceof ApplicationEnvironmentPreparedEvent) {
onApplicationEnvironmentPreparedEvent((ApplicationEnvironmentPreparedEvent) event);
}
// 这个ApplicationPreparedEvent事件会在上下文加载之后, 被调用(我们暂时不关心这个)
if (event instanceof ApplicationPreparedEvent) {
onApplicationPreparedEvent(event);
}
}
onApplicationEnvironmentPreparedEvent(event)
private void onApplicationEnvironmentPreparedEvent(ApplicationEnvironmentPreparedEvent event) {
// 使用SpringFactoriesLoader加载spring.factories文件中配置的EnvironmentPostProcessor全类名所对应的类实例(SPI加载)
List<EnvironmentPostProcessor> postProcessors = loadPostProcessors();
// 因为当前ConfigFileApplicationListener也实现了EnvironmentPostProcessor接口
postProcessors.add(this);
// 对EnvironmentPostProcessor实例排序
AnnotationAwareOrderComparator.sort(postProcessors);
// 遍历所有的EnvironmentPostProcessor实例,
// 回调它们的postProcessEnvironment,来对env对象处理
for (EnvironmentPostProcessor postProcessor : postProcessors) {
postProcessor.postProcessEnvironment(event.getEnvironment(), event.getSpringApplication());
}
}
3.2 postProcessEnvironment(environment, application)
@Override
public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) {
// 往env对象中添加属性源(怎么添加?就是通过读取配置文件)
// SpringApplication实例, 默认没有配置ResourceLoader
addPropertySources(environment, application.getResourceLoader());
}
protected void addPropertySources(ConfigurableEnvironment environment, ResourceLoader resourceLoader) {
// 添加一个名为“random”的属性源到名为“systemEnvironment”的属性源后面
RandomValuePropertySource.addToEnvironment(environment);
// Loader是ConfigFileApplicationListener中的成员内部类, 传入了env对象和resourceLoader对象(如果不为null的话),
// 然后调用Loader的load()方法, 读取配置文件, 加载到env的MutablePropertySources中
new Loader(environment, resourceLoader).load();
}
4. Loader
Loader是ConfigFileApplicationListener中的成员内部类
private class Loader {
// env对象
private final ConfigurableEnvironment environment;
// 用来解析属性占位符
private final PropertySourcesPlaceholdersResolver placeholdersResolver;
// 资源加载器对象
private final ResourceLoader resourceLoader;
// 通过SPI加载的属性源加载器
private final List<PropertySourceLoader> propertySourceLoaders;
// 配置
private Deque<Profile> profiles;
// 已处理的配置
private List<Profile> processedProfiles;
// 是否存在已激活的配置
private boolean activatedProfiles;
// 每个配置都会封装成一个MutablePropertySources(内部维护了List<PropertySource<?>>列表)
private Map<Profile, MutablePropertySources> loaded;
// document文档缓存(使用PropertySourceLoader和resource来标记)
private Map<DocumentsCacheKey, List<Document>> loadDocumentsCache = new HashMap<>();
Loader(ConfigurableEnvironment environment, ResourceLoader resourceLoader) {
// env对象赋值
this.environment = environment;
// 使用env对象提供的属性源, 解析占位符
this.placeholdersResolver = new PropertySourcesPlaceholdersResolver(this.environment);
// 确定资源加载器,
// 默认情况下, SpringApplication未设置resourceLoader属性, 因此使用DefaultResourceLoader
this.resourceLoader = (resourceLoader != null) ? resourceLoader : new DefaultResourceLoader();
// 通过SPI加载属性源加载器,
// springboot默认配置了2个
// PropertiesPropertySourceLoader(支持"properties", "xml)
// YamlPropertySourceLoader(支持"yml", "yaml")
this.propertySourceLoaders = SpringFactoriesLoader.loadFactories(PropertySourceLoader.class,getClass().getClassLoader());
}
// 省略方法...
}
4.1 load()
void load() {
// DEFAULT_PROPERTIES即: "defaultProperties"
// 注意到:defaultProperties在SpringApplication#configurePropertySources时,
// 会根据是否设置了defaultProperties, 往env中添加该名字的属性源
// LOAD_FILTERED_PROPERTY即: "spring.profiles.active"和"spring.profiles.include"
FilteredPropertySource.apply(this.environment, DEFAULT_PROPERTIES, LOAD_FILTERED_PROPERTY,
// 在SpringApplication中没有配置defaultProperties时, 这里就是null(默认)
// 但是如果配置了, 那这里就是配置的defaultProperties, 并且env中的该defaultProperties属性源会忽略掉"spring.profiles.active"和"spring.profiles.include"属性值
// (也就是在配置过程中defaultProperties属性源的这2个配置项暂时无效, 配置完成后, 才恢复)
(defaultProperties) -> {
// 配置集合
this.profiles = new LinkedList<>();
// 已处理的配置集合
this.processedProfiles = new LinkedList<>();
// 暂时还没有激活的配置
this.activatedProfiles = false;
// Profile配置 -> MutablePropertySources(其中维护了List<PropertySource<?>>列表)
this.loaded = new LinkedHashMap<>();
// 首先, 初始化配置集合
initializeProfiles();
// 然后, 遍历该配置集合
while (!this.profiles.isEmpty()) {
// 从配置集合中取出一个配置
// (Profile类是ConfigFileApplicationListener中的静态内部类, 有name属性和defaultProfile属性(默认false))
Profile profile = this.profiles.poll();
// 如果profile是非默认配置(即defaultProfile属性为false)
if (isDefaultProfile(profile)) {
// 如果该profile已经在env的activeProfiles中了, 那就忽略
// 否则, 就添加到env的activeProfiles中
addProfileToEnvironment(profile.getName());
}
// 加载该profile,
// 但是通过 方法引用 提供DocumentFilter和DocumentConsumer(添加到最后面)给后续处理使用
load(profile,
this::getPositiveProfileFilter,
addToLoaded(MutablePropertySources::addLast, false)
);
// 标记该profile已经处理过了
this.processedProfiles.add(profile);
}
// 传入的profile为null, 处理不带profile的配置, 并且把它添加在最前面
load(null, this::getNegativeProfileFilter, addToLoaded(MutablePropertySources::addFirst, true));
// 将载入的propertySource添加到env中
addLoadedPropertySources();
// 设置env的activeProfiles属性
applyActiveProfiles(defaultProperties);
});
}
getPositiveProfileFilter(Profile profile)
private DocumentFilter getPositiveProfileFilter(Profile profile) {
// 返回了一个lambda表达式, 始终持有最开始传过来的profile
return (Document document) -> {
// 如果最开始传过来的profile是null,
if (profile == null) {
// 如果document中未指定任何profiles, 那就是true
// 否则false
return ObjectUtils.isEmpty(document.getProfiles());
}
// 当前document中指定了的profiles中包含传最开始传过来的profile, 则为true
// 或者 环境对象中 指定了profile是否在env中 有效
return ObjectUtils.containsElement(document.getProfiles(), profile.getName())
&& this.environment.acceptsProfiles(Profiles.of(document.getProfiles()));
};
}
addToLoaded
private DocumentConsumer addToLoaded(BiConsumer<MutablePropertySources, PropertySource<?>> addMethod,boolean checkForExisting) {
return (profile, document) -> {
// 是否检查已存在
if (checkForExisting) {
// 如果已载入的profiles名字已存在, 则忽略
for (MutablePropertySources merged : this.loaded.values()) {
if (merged.contains(document.getPropertySource().getName())) {
return;
}
}
}
// 获取到profile对应的已载入的属性源
MutablePropertySources merged = this.loaded.computeIfAbsent(profile, (k) -> new MutablePropertySources());
// 使用方法引用将document中的propertySource添加到loader的loaded属性中去
addMethod.accept(merged, document.getPropertySource());
};
}
addLoadedPropertySources()
最终添加进env的操作
private void addLoadedPropertySources() {
// env的属性源(最终目标)
MutablePropertySources destination = this.environment.getPropertySources();
// 最终是要拿到loaded的value
List<MutablePropertySources> loaded = new ArrayList<>(this.loaded.values());
// 对loaded倒序
Collections.reverse(loaded);
String lastAdded = null;
// 标记已添加的属性源名
Set<String> added = new HashSet<>();
// 遍历loaded(Profile->MutablePropertySources)
for (MutablePropertySources sources : loaded) {
// 遍历每个Profile下的所有属性源
for (PropertySource<?> source : sources) {
//忽略已添加的属性源名
if (added.add(source.getName())) {
// 添加属性源
addLoadedPropertySource(destination, lastAdded, source);
// 记录最后添加的属性源名
lastAdded = source.getName();
}
}
}
}
private void addLoadedPropertySource(MutablePropertySources destination,
String lastAdded,
PropertySource<?> source) {
// 说明是第一次进来
if (lastAdded == null) {
// 如果配置了"defaultProperties"属性源, 则添加在它的前面
if (destination.contains(DEFAULT_PROPERTIES)) {
destination.addBefore(DEFAULT_PROPERTIES, source);
}
else {
// 添加到后面即可
destination.addLast(source);
}
}
else {
// 添加到上一个属性源的后面
destination.addAfter(lastAdded, source);
}
}
initializeProfiles()初始化Profile配置
// 从env的activeProfiles属性(有可能被手动添加)或者是spring.profiles.active和spring.profiles.include配置项指定
private void initializeProfiles() {
// 首先添加一个null, 让这个null会被后面首先处理, 这样它就是最低的优先级
this.profiles.add(null);
// 从env中获取"spring.profiles.active"配置(可指定多个)
Set<Profile> activatedViaProperty = getProfilesFromProperty(ACTIVE_PROFILES_PROPERTY);
// 从env中获取"spring.profiles.include"配置(可指定多个)
Set<Profile> includedViaProperty = getProfilesFromProperty(INCLUDE_PROFILES_PROPERTY);
// 从env的activeProfiles属性中, 获取不在以上2个配置向中的激活配置(有可能被设置)
List<Profile> otherActiveProfiles = getOtherActiveProfiles(activatedViaProperty, includedViaProperty);
// 首先加入用户通过env指定的激活配置到profiles中
// (可见: SpringApplication#configureProfiles,
// 一般只有通过命令行参数或者通过SpringApplication实例的additionalProfiles指定(因为这个时候属性源还不多))
// 它们具有更高的优先级
this.profiles.addAll(otherActiveProfiles);
// 添加通过"spring.profiles.include"配置子项加入到profiles中
this.profiles.addAll(includedViaProperty);
// 如果此时有指定"spring.profiles.actvie", 并且不为空, 那么也会把指定的添加到profiles中, 并且标记激活配置为true
// 否则, 啥也不干, 激活配置标记仍然为false
addActiveProfiles(activatedViaProperty);
// 如果只有1个, 那就是说, 前面都没有指定, 就是默认的添加的那个null
if (this.profiles.size() == 1) {
// 获取env的defaultProfiles属性
// (如果有手动设置,则直接返回;未手动设置,则获取spring.profiles.default的配置值, "default"作为默认属性)
for (String defaultProfileName : this.environment.getDefaultProfiles()) {
// 标记profiles是默认配置加载的
Profile defaultProfile = new Profile(defaultProfileName, true);
this.profiles.add(defaultProfile);
}
}
}
load(Profile profile, DocumentFilterFactory filterFactory, DocumentConsumer consumer)
private void load(Profile profile,
DocumentFilterFactory filterFactory,
DocumentConsumer consumer) {
// 获取要查找配置文件的位置, 遍历这些位置
// 可通过 spring.config.location、spring.config.additional-location 来指定
// 默认找"classpath:/,classpath:/config/,file:./,file:./config/"这4个地方
// 注意这个时候, 配置文件还为真正读取, 这些值只能是在命令行参数或者系统参数中获取
getSearchLocations()
.forEach(
(location) -> {
// 如果location是以"/"结尾, 则认为是文件夹;
// 否则, 会认为是一个文件, 就不会获取文件名了
boolean isFolder = location.endsWith("/");
// 获取配置文件名字, 可通过spring.config.name指定(可配置多个)
Set<String> names = isFolder ? getSearchNames() : NO_SEARCH_NAMES;
// 遍历配置文件名, 挨个载入(遍历前面提到的每个路径下的这里的每个文件名)
names.forEach((name) -> load(location, name, profile, filterFactory, consumer));
}
);
}
getSearchLocations()
获取要从哪些地方读取配置文件
private Set<String> getSearchLocations() {
// 如果前面有指定"spring.config.location",就获取它
if (this.environment.containsProperty(CONFIG_LOCATION_PROPERTY)) {
return getSearchLocations(CONFIG_LOCATION_PROPERTY);
}
// 如果没有指定"spring.config.location", 才会获取"spring.config.additional-location"
Set<String> locations = getSearchLocations(CONFIG_ADDITIONAL_LOCATION_PROPERTY);
// 没有指定spring.config.location,
// 才会添加默认的"classpath:/,classpath:/config/,file:./,file:./config/"
locations.addAll(asResolvedSet(ConfigFileApplicationListener.this.searchLocations, DEFAULT_SEARCH_LOCATIONS));
return locations;
}
private Set<String> getSearchLocations(String propertyName) {
// 最终返回的location
Set<String> locations = new LinkedHashSet<>();
// 如果环境中有配置该propertyName
if (this.environment.containsProperty(propertyName)) {
// 从env中获取配置的属性值, 注意, 在asResolvedSet()方法中调用, h获取时, 它这里倒序了
// (如果只写了,但是又不配置值,就会是null)
for (String path : asResolvedSet(this.environment.getProperty(propertyName), null)) {
// 如果路径中没有$, 并且没有以"classpath:"开头或者是非Url资源, 则添加"file:"前缀
if (!path.contains("$")) {
path = StringUtils.cleanPath(path);
if (!ResourceUtils.isUrl(path)) {
path = ResourceUtils.FILE_URL_PREFIX + path;
}
}
// 加入的顺序与配置的顺序相反
locations.add(path);
}
}
return locations;
}
private Set<String> asResolvedSet(String value, String fallback) {
List<String> list = Arrays.asList(
StringUtils.trimArrayElements(
// 解析占位符, 使用逗号分割
StringUtils.commaDelimitedListToStringArray(value != null) ?
this.environment.resolvePlaceholders(value)
: fallback
)
)
);
// 逆序排列
Collections.reverse(list);
return new LinkedHashSet<>(list);
}
getSearchNames()
获取要读取的配置文件名
private Set<String> getSearchNames() {
// 获取"spring.config.name"配置值
if (this.environment.containsProperty(CONFIG_NAME_PROPERTY)) {
// 如果有配置, 那就获取, 并倒序(如果只写了,但是又不配置值,就会是null)
String property = this.environment.getProperty(CONFIG_NAME_PROPERTY);
return asResolvedSet(property, null);
}
// 在没有配置"spring.config.name"的情况下,才会使用默认的文件名: "application"
return asResolvedSet(ConfigFileApplicationListener.this.names, DEFAULT_NAMES);
}
load(String location, String name, Profile profile, DocumentFilterFactory filterFactory,DocumentConsumer consumer)
private void load(String location,
String name,
Profile profile,
DocumentFilterFactory filterFactory,
DocumentConsumer consumer) {
// location直接指定了文件(不是以"/"结尾)
if (!StringUtils.hasText(name)) {
// 遍历SPI提供的属性源加载器(PropertiesPropertySourceLoader、YamlPropertySourceLoader)
for (PropertySourceLoader loader : this.propertySourceLoaders) {
// 根据配置文件的后缀和属性源加载器支持的文件后缀,
// 判断能够加载当前的配置文件, 默认xml、prorperteis或yml、yaml
if (canLoadFileExtension(loader, location)) {
//
load(loader, location, profile, filterFactory.getDocumentFilter(profile), consumer);
return;
}
}
throw new IllegalStateException();
}
// location是个文件夹(以"/"结尾)
// 避免不同的资源加载器,重复加载同一后缀名的文件
Set<String> processed = new HashSet<>();
// 遍历属性源加载器
for (PropertySourceLoader loader : this.propertySourceLoaders) {
// 遍历了属性元加载器支持的文件后缀名
for (String fileExtension : loader.getFileExtensions()) {
// 如果之前没有加载过该后缀名结尾的文件
if (processed.add(fileExtension)) {
// 那就加载它
// 拼接: location + name + "." + 属性源加载器支持的文件名后缀
// 当前的 profile 以及 filterFactory、consumer
// 从这里我们可以看出, 并不需要我们指定文件名后缀,
// springboot自己会根据提供的属性源加载器拼接起来
loadForFileExtension(loader, location + name, "." + fileExtension, profile, filterFactory,consumer);
}
}
}
}
loadForFileExtension(PropertySourceLoader loader, String prefix, String fileExtension,Profile profile, DocumentFilterFactory filterFactory, DocumentConsumer consumer)
这里用上了属性源加载器
private void loadForFileExtension(PropertySourceLoader loader, String prefix, String fileExtension,Profile profile, DocumentFilterFactory filterFactory, DocumentConsumer consumer) {
// 使用了前面的过滤器工厂, 创建DocumentFilter
// 那么有什么用呢?
// 因为当前正在遍历最开始的profile, 在解析配置文件的时候, 可以指定当前配置文件是在哪个profile时生效,
// springboot要确定当前遍历的这个profile 和 从配置文件解析出来的profile是一致的, 才会加载里面的属性源到env中
// 这里传的是null(document未指定profile才生效)
DocumentFilter defaultFilter = filterFactory.getDocumentFilter(null);
// 这里传的是当前遍历的profile(document的profiles与当前遍历的profile匹配 或者 在env中是激活配置才生效)
DocumentFilter profileFilter = filterFactory.getDocumentFilter(profile);
// 如果profile不为null, 那就证明肯定设置了profile
if (profile != null) {
// location + name + "-" + profile + 文件名后缀 来定位文件
String profileSpecificFile = prefix + "-" + profile + fileExtension;
// 这里用的是defaultFilter, 只会载入未指定profile的document的配置
// (没有指定profile,当然应该生效)
load(loader, profileSpecificFile, profile, defaultFilter, consumer);
// 这里用的是profileFilter, 只有匹配当前profile的document配置
// (指定了profile,在当前profile下, 也应该生效)
load(loader, profileSpecificFile, profile, profileFilter, consumer);
// 从已经处理过的profile中, 再次载入符合当前profile的配置
for (Profile processedProfile : this.processedProfiles) {
if (processedProfile != null) {
String previouslyLoaded = prefix + "-" + processedProfile + fileExtension;
load(loader, previouslyLoaded, profile, profileFilter, consumer);
}
}
}
// 尝试加载 prefix + fileExtension(忽略profile指定的环境)文件
load(loader, prefix + fileExtension, profile, profileFilter, consumer);
}
load(PropertySourceLoader loader, String location, Profile profile, DocumentFilter filter,DocumentConsumer consumer)
private void load(PropertySourceLoader loader,
String location,
Profile profile,
DocumentFilter filter,
DocumentConsumer consumer) {
try {
// 加载配置文件
Resource resource = this.resourceLoader.getResource(location);
// 文件不存在, 直接返回
if (resource == null || !resource.exists()) {
return;
}
// 文件名没有后缀, 直接返回
if (!StringUtils.hasText(StringUtils.getFilenameExtension(resource.getFilename()))) {
return;
}
// 属性源的名称
String name = "applicationConfig: [" + location + "]";
// 载入配置文件, 读取成Document集合(也就是一个配置文件, 可对应多个Document文档)
List<Document> documents = loadDocuments(loader, name, resource);
// 如果documents是空的, 直接返回
if (CollectionUtils.isEmpty(documents)) {
return;
}
List<Document> loaded = new ArrayList<>();
// 遍历documents文档集合
for (Document document : documents) {
// 这里就开始过滤了,
if (filter.match(document)) {
// 如果document文档中, 又激活了一些配置, 则会添加到profiles中去
// (但是注意: addActiveProfiles只会调用1次, 调用1次后, 后面继续指定将会无效)
addActiveProfiles(document.getActiveProfiles());
// 但是document文档中还可以继续通过spring.profiles.include来指定子配置项
addIncludedProfiles(document.getIncludeProfiles());
// 添加到loaded中, 看上面的addToLoaded
loaded.add(document);
}
}
// 对loaded倒序
Collections.reverse(loaded);
if (!loaded.isEmpty()) {
// 最后, 才将由当前profile读取的document添加到env中
loaded.forEach((document) -> consumer.accept(profile, document));
}
}
catch (Exception ex) {
throw new IllegalStateException();
}
}
loadDocuments(PropertySourceLoader loader, String name, Resource resource)
private List<Document> loadDocuments(PropertySourceLoader loader, String name, Resource resource){
// 使用loader和resource作为文档缓存的key
DocumentsCacheKey cacheKey = new DocumentsCacheKey(loader, resource);
// 先从缓存中拿
List<Document> documents = this.loadDocumentsCache.get(cacheKey);
// 缓存中没有, 则使用属性源加载器加载
if (documents == null) {
// 加载配置文件(一个配置文件可对应多个PropertySource, 比如yml文档块)
List<PropertySource<?>> loaded = loader.load(name, resource);
// 封装为Document
documents = asDocuments(loaded);
// 放入缓存
this.loadDocumentsCache.put(cacheKey, documents);
}
return documents;
}
asDocuments(List<PropertySource<?>> loaded)
private List<Document> asDocuments(List<PropertySource<?>> loaded) {
// null处理
if (loaded == null) {
return Collections.emptyList();
}
// 遍历配置文件读取的所有PropertySource
return loaded.stream().map((propertySource) -> {
// 此处binder的属性源就是, 其中的一个propertySource
// placeholdersResolver只是用来解析占位符的
Binder binder = new Binder(ConfigurationPropertySources.from(propertySource),this.placeholdersResolver);
// 封装为Document,
// 第一个参数: propertySource
// 第二个参数: 该文档块中spring.profiles配置的值, 如果未配置, 就是null
// 第三个参数: 该文档块中spring.profiles.active的值
// 第四个参数: 该文档块中spring.profiles.include的值
return new Document(propertySource, binder.bind("spring.profiles", STRING_ARRAY).orElse(null),getProfiles(binder, ACTIVE_PROFILES_PROPERTY), getProfiles(binder, INCLUDE_PROFILES_PROPERTY));
}).collect(Collectors.toList());
}
Document
了解一下Document
private static class Document {
private final PropertySource<?> propertySource;
private String[] profiles;
private final Set<Profile> activeProfiles;
private final Set<Profile> includeProfiles;
Document(PropertySource<?> propertySource,
String[] profiles,
Set<Profile> activeProfiles,
Set<Profile> includeProfiles)
{
this.propertySource = propertySource;
this.profiles = profiles;
this.activeProfiles = activeProfiles;
this.includeProfiles = includeProfiles;
}
// 省略get方法
}
4.2 PropertySourceLoader
YamlPropertySourceLoader
public class YamlPropertySourceLoader implements PropertySourceLoader {
@Override
public String[] getFileExtensions() {
// 支持 yml 和 yaml 后缀名
return new String[] { "yml", "yaml" };
}
@Override
public List<PropertySource<?>> load(String name, Resource resource) throws IOException {
// 使用snakeyaml解析yml文件(每个文档块都是一个map)
if (!ClassUtils.isPresent("org.yaml.snakeyaml.Yaml", null)) {
throw new IllegalStateException();
}
// 交给OriginTrackedYamlLoader处理yml配置文件
List<Map<String, Object>> loaded = new OriginTrackedYamlLoader(resource).load();
// 没有任何配置, 返回空集合
if (loaded.isEmpty()) {
return Collections.emptyList();
}
// 最终返回的集合
List<PropertySource<?>> propertySources = new ArrayList<>(loaded.size());
for (int i = 0; i < loaded.size(); i++) {
// 属性源名为: applicationConfig: [{location}](document#{i})
// 或: applicationConfig: [{location}]
String documentNumber = (loaded.size() != 1) ? " (document #" + i + ")" : "";
propertySources.add(new OriginTrackedMapPropertySource(name + documentNumber, Collections.unmodifiableMap(loaded.get(i)), true));
}
return propertySources;
}
}
PropertiesPropertySourceLoader
public class PropertiesPropertySourceLoader implements PropertySourceLoader {
private static final String XML_FILE_EXTENSION = ".xml";
@Override
public String[] getFileExtensions() {
// 支持的文件名后缀: properties、xml
return new String[] { "properties", "xml" };
}
@Override
public List<PropertySource<?>> load(String name, Resource resource) throws IOException {
Map<String, ?> properties = loadProperties(resource);
if (properties.isEmpty()) {
return Collections.emptyList();
}
// properties文件名后缀, 只会返回一个PropertySource属性源, 名字为: applicationConfig: [{location}]
return Collections.singletonList(new OriginTrackedMapPropertySource(name, Collections.unmodifiableMap(properties), true));
}
private Map<String, ?> loadProperties(Resource resource) throws IOException {
String filename = resource.getFilename();
if (filename != null && filename.endsWith(XML_FILE_EXTENSION)) {
// xml后缀名文件处理
return (Map) PropertiesLoaderUtils.loadProperties(resource);
}
// properties后缀名文件处理
return new OriginTrackedPropertiesLoader(resource).load();
}
}