前言

首先我们都知道springboot的约定大于配置的一条约定就是classpath下指定application.properties或者yaml文件的配置文件会被加载到spring中作为配置文件

那么在springboot中它是如何加载的呢?本文就是从源码介绍application文件的加载

源码解析

从springboot启动类入手

SpringApplication.run(ProvideApplication.class, args)
public static ConfigurableApplicationContext run(Class<?> primarySource, String... args) {
		return run(new Class<?>[] { primarySource }, args);
	}
public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {
		return new SpringApplication(primarySources).run(args);
	}
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);
			。。。。。。
	}

看名字就知道是处理环境变量Environment的

private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners,
			ApplicationArguments applicationArguments) {
		// Create and configure the environment
		// 核心1
		ConfigurableEnvironment environment = getOrCreateEnvironment();
		// 核心2
		configureEnvironment(environment, applicationArguments.getSourceArgs());
		// 核心3
		ConfigurationPropertySources.attach(environment);
		// 核心4
		listeners.environmentPrepared(environment);
		bindToSpringApplication(environment);
		if (!this.isCustomEnvironment) {
			environment = new EnvironmentConverter(getClassLoader()).convertEnvironmentIfNecessary(environment,
					deduceEnvironmentClass());
		}
		ConfigurationPropertySources.attach(environment);
		return environment;
	}

先看核心1,创建了environment 对象

	private ConfigurableEnvironment getOrCreateEnvironment() {
		if (this.environment != null) {
			return this.environment;
		}
		switch (this.webApplicationType) {
		case SERVLET:
			return new StandardServletEnvironment();
		case REACTIVE:
			return new StandardReactiveWebEnvironment();
		default:
			return new StandardEnvironment();
		}
	}

这里打断点发现,我当前的启动方式会使用StandardEnvironment的环境,如果添加了servlet或者reactive相关的类会切换成其他的

public class StandardEnvironment extends AbstractEnvironment {
    public static final String SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME = "systemEnvironment";
    public static final String SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME = "systemProperties";

    public StandardEnvironment() {
    }

    protected void customizePropertySources(MutablePropertySources propertySources) {
        propertySources.addLast(new PropertiesPropertySource("systemProperties", this.getSystemProperties()));
        propertySources.addLast(new SystemEnvironmentPropertySource("systemEnvironment", this.getSystemEnvironment()));
    }
}

看父类AbstractEnvironment 的构造方法

public AbstractEnvironment() {
        this.propertyResolver = new PropertySourcesPropertyResolver(this.propertySources);
        this.customizePropertySources(this.propertySources);
    }

customizePropertySources回调到子类StandardEnvironment的customizePropertySources

protected void customizePropertySources(MutablePropertySources propertySources) {
        propertySources.addLast(new PropertiesPropertySource("systemProperties", this.getSystemProperties()));
        propertySources.addLast(new SystemEnvironmentPropertySource("systemEnvironment", this.getSystemEnvironment()));
    }

可以看到创建了Environment对象,并且Environment对象有一个propertySources集合,往里面添加了两个propertySource,这两个propertySource分别对应的是

  • systemProperties:应用启动时指定的参数,如java -jar -Dserver.port=8888 demo.jar设置的参数
  • systemEnvironment:系统环境变量,如设置的JAVA_HOME

然后回到prepareEnvironment方法,继续看核心2 configureEnvironment方法

protected void configureEnvironment(ConfigurableEnvironment environment, String[] args) {
		if (this.addConversionService) {
			ConversionService conversionService = ApplicationConversionService.getSharedInstance();
			environment.setConversionService((ConfigurableConversionService) conversionService);
		}
		// 核心1
		configurePropertySources(environment, args);
		// 核心2
		configureProfiles(environment, args);
	}
protected void configurePropertySources(ConfigurableEnvironment environment, String[] args) {
		MutablePropertySources sources = environment.getPropertySources();
		if (this.defaultProperties != null && !this.defaultProperties.isEmpty()) {
			// 核心1
			sources.addLast(new MapPropertySource("defaultProperties", this.defaultProperties));
		}
		if (this.addCommandLineProperties && args.length > 0) {
			String name = CommandLinePropertySource.COMMAND_LINE_PROPERTY_SOURCE_NAME;
			if (sources.contains(name)) {
				PropertySource<?> source = sources.get(name);
				CompositePropertySource composite = new CompositePropertySource(name);
				composite.addPropertySource(
						new SimpleCommandLinePropertySource("springApplicationCommandLineArgs", args));
				composite.addPropertySource(source);
				sources.replace(name, composite);
			}
			else {
				// 核心2
				sources.addFirst(new SimpleCommandLinePropertySource(args));
			}
		}
	}

这里又设置了两个propertySource

  • defaultProperties:默认参数,是加到最后面的,优先级低
  • SimpleCommandLinePropertySource:命令行参数propertySource,是加到最前面的,例如:java -jar demo.jar –server.port=8888 设置的参数

然后看核心2,configureProfiles方法

protected void configureProfiles(ConfigurableEnvironment environment, String[] args) {
		Set<String> profiles = new LinkedHashSet<>(this.additionalProfiles);
		profiles.addAll(Arrays.asList(environment.getActiveProfiles()));
		environment.setActiveProfiles(StringUtils.toStringArray(profiles));
	}

这个方法把配置文件中指定的激活相关的环境设置到了environment中,比如设置了spring.profiles.active=dev,在后续会使用到

继续看prepareEnvironment的核心3,ConfigurationPropertySources.attach(environment)

	public static void attach(Environment environment) {
		Assert.isInstanceOf(ConfigurableEnvironment.class, environment);
		MutablePropertySources sources = ((ConfigurableEnvironment) environment).getPropertySources();
		PropertySource<?> attached = sources.get(ATTACHED_PROPERTY_SOURCE_NAME);
		if (attached != null && attached.getSource() != sources) {
			sources.remove(ATTACHED_PROPERTY_SOURCE_NAME);
			attached = null;
		}
		if (attached == null) {
			// 核心
			sources.addFirst(new ConfigurationPropertySourcesPropertySource(ATTACHED_PROPERTY_SOURCE_NAME,
					new SpringConfigurationPropertySources(sources)));
		}
	}

这里又添加了一个PropertySource,这个PropertySource不太关心是干嘛的,感兴趣的自己了解以下

继续看核心4,listeners.environmentPrepared

这里的listeners是在上面的run方法创建的

SpringApplicationRunListeners listeners = getRunListeners(args);

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

创建SpringApplicationRunListeners ,并且通过spi找到SpringApplicationRunListener从构造方法传进去了,这里spi只能找到一个EventPublishingRunListener
在这里插入图片描述

void environmentPrepared(ConfigurableEnvironment environment) {
		for (SpringApplicationRunListener listener : this.listeners) {
			listener.environmentPrepared(environment);
		}
	}

这里的listeners只有一个EventPublishingRunListener,上面通过spi创建的

public void environmentPrepared(ConfigurableEnvironment environment) {
		this.initialMulticaster
				.multicastEvent(new ApplicationEnvironmentPreparedEvent(this.application, this.args, environment));
	}

发布一个ApplicationEnvironmentPreparedEvent事件,这里里面不看了,会查找所有的事件类ApplicationListener,调用他们相应的onApplicationEvent,此时有很多个ApplicationListener,需要找到相关的监听类,这里是一个ConfigFileApplicationListener,看一下它的onApplicationEvent方法

@Override
	public void onApplicationEvent(ApplicationEvent event) {
		if (event instanceof ApplicationEnvironmentPreparedEvent) {
			// 核心方法
			onApplicationEnvironmentPreparedEvent((ApplicationEnvironmentPreparedEvent) event);
		}
		if (event instanceof ApplicationPreparedEvent) {
			onApplicationPreparedEvent(event);
		}
	}

专门处理ApplicationEnvironmentPreparedEvent事件的

private void onApplicationEnvironmentPreparedEvent(ApplicationEnvironmentPreparedEvent event) {
		List<EnvironmentPostProcessor> postProcessors = loadPostProcessors();
		// 核心1
		postProcessors.add(this);
		AnnotationAwareOrderComparator.sort(postProcessors);
		for (EnvironmentPostProcessor postProcessor : postProcessors) {
			postProcessor.postProcessEnvironment(event.getEnvironment(), event.getSpringApplication());
		}
	}

核心1,获得了很多EnvironmentPostProcessor,并且把当前ConfigFileApplicationListener也加进去了,ConfigFileApplicationListener同时继承了EnvironmentPostProcessor

然后循环执行所有的postProcessEnvironment方法,这里就看ConfigFileApplicationListener的postProcessEnvironment就行了

@Override
	public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) {
		addPropertySources(environment, application.getResourceLoader());
	}
	protected void addPropertySources(ConfigurableEnvironment environment, ResourceLoader resourceLoader) {
		// 核心1
		RandomValuePropertySource.addToEnvironment(environment);
		new Loader(environment, resourceLoader).load();
	}
public static void addToEnvironment(ConfigurableEnvironment environment) {
		environment.getPropertySources().addAfter(StandardEnvironment.SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME,
				new RandomValuePropertySource(RANDOM_PROPERTY_SOURCE_NAME));
		logger.trace("RandomValuePropertySource add to Environment");
	}

这里又加了一个RandomValuePropertySource,不太关心

继续看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<>();
						this.activatedProfiles = false;
						this.loaded = new LinkedHashMap<>();
						initializeProfiles();
						// 核心1
						while (!this.profiles.isEmpty()) {
							Profile profile = this.profiles.poll();
							if (isDefaultProfile(profile)) {
								addProfileToEnvironment(profile.getName());
							}
							// 核心2
							load(profile, this::getPositiveProfileFilter,
									addToLoaded(MutablePropertySources::addLast, false));
							this.processedProfiles.add(profile);
						}
						// 核心3
						load(null, this::getNegativeProfileFilter, addToLoaded(MutablePropertySources::addFirst, true));
						// 核心4
						addLoadedPropertySources();
						applyActiveProfiles(defaultProperties);
					});
		}

核心1处取到了我们之前设置的spring.profiles.active参数,因为要拼接为application-dev.properties等文件名进行查找,核心2就是处理这种情况的

这里没有设置spring.profiles.active就直接看核心3即可

private void load(Profile profile, DocumentFilterFactory filterFactory, DocumentConsumer consumer) {
			getSearchLocations().forEach((location) -> {
				boolean isDirectory = location.endsWith("/");
				Set<String> names = isDirectory ? getSearchNames() : NO_SEARCH_NAMES;
				names.forEach((name) -> load(location, name, profile, filterFactory, consumer));
			});
		}

这个getSearchLocations()先看一下
在这里插入图片描述
可以看到这个是要查找的路径,进行遍历,这里可以看到我们的application文件不仅可以放着classpath下,也可以放着config目录下

这里断点直接找到classpath:/这个name,因为其他路径下没有配置要找的配置文件

private void load(String location, String name, Profile profile, DocumentFilterFactory filterFactory,
				DocumentConsumer consumer) {
			。。。。。。
			Set<String> processed = new HashSet<>();
			// 核心1
			for (PropertySourceLoader loader : this.propertySourceLoaders) {
				// 核心2
				for (String fileExtension : loader.getFileExtensions()) {
					if (processed.add(fileExtension)) {
						// 核心3
						loadForFileExtension(loader, location + name, "." + fileExtension, profile, filterFactory,
								consumer);
					}
				}
			}
		}

核心1,这里的propertySourceLoaders,可以看到分别是处理properties和yaml文件的,这里的2个类是通过spi获取的,也可以自己扩展,比如我想扩展.json后缀的也可以,只需要使用springboot的spi机制即可,可以看这篇文章:https://blog.csdn.net/qq_31086797/article/details/107397463
在这里插入图片描述

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);
			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);
					}
				}
			}
			// Also try the profile-specific section (if any) of the normal file
			// 核心
			load(loader, prefix + fileExtension, profile, profileFilter, consumer);
		}

继续看load

private void load(PropertySourceLoader loader, String location, Profile profile, DocumentFilter filter,
				DocumentConsumer consumer) {
			// 核心1
			Resource[] resources = getResources(location);
			for (Resource resource : resources) {
		。。。。。。
					// 核心2
					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<>();
					for (Document document : documents) {
						if (filter.match(document)) {
							addActiveProfiles(document.getActiveProfiles());
							addIncludedProfiles(document.getIncludeProfiles());
							loaded.add(document);
						}
					}
					Collections.reverse(loaded);
					if (!loaded.isEmpty()) {
						// 核心3
						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);
				}
			}
		}

核心1的location等于classpath:/application.properties,也就是查找该目录下的资源,找到了才能继续往下走

核心2,loadDocuments

private List<Document> loadDocuments(PropertySourceLoader loader, String name, Resource resource)
				throws IOException {
			DocumentsCacheKey cacheKey = new DocumentsCacheKey(loader, resource);
			List<Document> documents = this.loadDocumentsCache.get(cacheKey);
			if (documents == null) {
				// 核心1
				List<PropertySource<?>> loaded = loader.load(name, resource);
				documents = asDocuments(loaded);
				this.loadDocumentsCache.put(cacheKey, documents);
			}
			return documents;
		}

核心1处使用之前的PropertySourceLoader,这里是专门处理properties后缀的PropertySourceLoader ,调用load方法,传入资源resource,最终返回了PropertySource,并且包装为了Document对象返回

核心3处,consumer.accept(profile, document)),这个consumer是一个匿名类,是之前ConfigFileApplicationListener类load方法创建的,在看一下这个load方法

void load() {
			FilteredPropertySource.apply(this.environment, DEFAULT_PROPERTIES, LOAD_FILTERED_PROPERTY,
					(defaultProperties) -> {
						this.profiles = new LinkedList<>();
						this.processedProfiles = new LinkedList<>();
						this.activatedProfiles = false;
						this.loaded = new LinkedHashMap<>();
						initializeProfiles();
						while (!this.profiles.isEmpty()) {
							Profile profile = this.profiles.poll();
							if (isDefaultProfile(profile)) {
								addProfileToEnvironment(profile.getName());
							}
							load(profile, this::getPositiveProfileFilter,
									addToLoaded(MutablePropertySources::addLast, false));
							this.processedProfiles.add(profile);
						}
						// 核心代码
						load(null, this::getNegativeProfileFilter, addToLoaded(MutablePropertySources::addFirst, true));
						addLoadedPropertySources();
						applyActiveProfiles(defaultProperties);
					});
		}

核心处通过addToLoaded方法传入了一个DocumentConsumer赋值

private DocumentConsumer addToLoaded(BiConsumer<MutablePropertySources, PropertySource<?>> addMethod,
				boolean checkForExisting) {
			return (profile, document) -> {
				if (checkForExisting) {
					for (MutablePropertySources merged : this.loaded.values()) {
						if (merged.contains(document.getPropertySource().getName())) {
							return;
						}
					}
				}
				// 核心
				MutablePropertySources merged = this.loaded.computeIfAbsent(profile,
						(k) -> new MutablePropertySources());
				addMethod.accept(merged, document.getPropertySource());
			};
		}

所以之前的consumer.accept(profile, document)会调用到这里的lambda表达式

这个就是把document的propertySource添加到了loaded的这个map中,key是Profile,value是MutablePropertySources

这里的逻辑走完,假如配置了参数spring.profiles.active=dev,那么这个loader中最终就会有两个值。
一个是标准的application.properties的另外一个是application-dev.properties的

这里看完了,load加载配置文件就全部完成了,继续看之前的核心4,addLoadedPropertySources()

private void addLoadedPropertySources() {
			MutablePropertySources destination = this.environment.getPropertySources();
			List<MutablePropertySources> loaded = new ArrayList<>(this.loaded.values());
			Collections.reverse(loaded);
			String lastAdded = null;
			Set<String> added = new HashSet<>();
			for (MutablePropertySources sources : loaded) {
				for (PropertySource<?> source : sources) {
					if (added.add(source.getName())) {
						// 核心
						addLoadedPropertySource(destination, lastAdded, source);
						lastAdded = source.getName();
					}
				}
			}
		}

遍历所有的loaded,之前加载就是把配置文件存储到这里,然后addLoadedPropertySource

private void addLoadedPropertySource(MutablePropertySources destination, String lastAdded,
				PropertySource<?> source) {
			if (lastAdded == null) {
				if (destination.contains(DEFAULT_PROPERTIES)) {
					destination.addBefore(DEFAULT_PROPERTIES, source);
				}
				else {
					destination.addLast(source);
				}
			}
			else {
				destination.addAfter(lastAdded, source);
			}
		}

这里的destination就是environment里面的MutablePropertySources,里面存储了目前所有的PropertySources,然后把现在loaded里面的PropertySource全部加到environment的PropertySources中

到这里application.properties的配置文件解析就完成了

下一篇,看看spring是如何从environment中的PropertySources取值注入到bean的@Value注解中的

转自:
https://blog.csdn.net/qq_31086797/article/details/124109200