前言
首先我们都知道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注解中的