第五章 Springboot外化配置源码解析


这一章,主要分析: 外化配置文件、命令行参数、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();
	}

}
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值