springboot启动读取配置文件过程&自定义配置文件处理器&Springcloud中bootstrap.yml配置的读取
最近看到看到spring的配置文件放在了resources/config/application.yal 文件内部,第一次见。就想的研究下,springboot启动读取配置文件的过程。
1. 启动过程
- org.springframework.boot.SpringApplication#run(java.lang.Class<?>[], java.lang.String[])
public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {
return (new SpringApplication(primarySources)).run(args);
}
- org.springframework.boot.SpringApplication#SpringApplication(java.lang.Class<?>...)
public SpringApplication(Class<?>... primarySources) {
this((ResourceLoader)null, primarySources);
}
public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
this.sources = new LinkedHashSet();
this.bannerMode = Mode.CONSOLE;
this.logStartupInfo = true;
this.addCommandLineProperties = true;
this.addConversionService = true;
this.headless = true;
this.registerShutdownHook = true;
this.additionalProfiles = Collections.emptySet();
this.isCustomEnvironment = false;
this.lazyInitialization = false;
this.applicationContextFactory = ApplicationContextFactory.DEFAULT;
this.applicationStartup = ApplicationStartup.DEFAULT;
this.resourceLoader = resourceLoader;
Assert.notNull(primarySources, "PrimarySources must not be null");
this.primarySources = new LinkedHashSet(Arrays.asList(primarySources));
this.webApplicationType = WebApplicationType.deduceFromClasspath();
this.bootstrapRegistryInitializers = new ArrayList(this.getSpringFactoriesInstances(BootstrapRegistryInitializer.class));
this.setInitializers(this.getSpringFactoriesInstances(ApplicationContextInitializer.class));
this.setListeners(this.getSpringFactoriesInstances(ApplicationListener.class));
this.mainApplicationClass = this.deduceMainApplicationClass();
}
这里会调用到org.springframework.core.io.support.SpringFactoriesLoader#loadSpringFactories 去读取相关的自动配置。
private static Map<String, List<String>> loadSpringFactories(ClassLoader classLoader) {
Map<String, List<String>> result = (Map)cache.get(classLoader);
if (result != null) {
return result;
} else {
Map<String, List<String>> result = new HashMap();
try {
Enumeration<URL> urls = classLoader.getResources("META-INF/spring.factories");
while(urls.hasMoreElements()) {
URL url = (URL)urls.nextElement();
UrlResource resource = new UrlResource(url);
Properties properties = PropertiesLoaderUtils.loadProperties(resource);
Iterator var6 = properties.entrySet().iterator();
while(var6.hasNext()) {
Map.Entry<?, ?> entry = (Map.Entry)var6.next();
String factoryTypeName = ((String)entry.getKey()).trim();
String[] factoryImplementationNames = StringUtils.commaDelimitedListToStringArray((String)entry.getValue());
String[] var10 = factoryImplementationNames;
int var11 = factoryImplementationNames.length;
for(int var12 = 0; var12 < var11; ++var12) {
String factoryImplementationName = var10[var12];
((List)result.computeIfAbsent(factoryTypeName, (key) -> {
return new ArrayList();
})).add(factoryImplementationName.trim());
}
}
}
result.replaceAll((factoryType, implementations) -> {
return (List)implementations.stream().distinct().collect(Collectors.collectingAndThen(Collectors.toList(), Collections::unmodifiableList));
});
cache.put(classLoader, result);
return result;
} catch (IOException var14) {
throw new IllegalArgumentException("Unable to load factories from location [META-INF/spring.factories]", var14);
}
}
}
这里也就看到了我们熟悉的扫描classpath下META-INF/spring.factories 文件信息,然后缓存到map。
- 在springboot.xxx.jar META-INF/spring.factories 文件有下面配置:
# Run Listeners
org.springframework.boot.SpringApplicationRunListener=\
org.springframework.boot.context.event.EventPublishingRunListener
- 注意这里不是自动配置
自动配置是在一个Spring的后置处理器中处理的。org.springframework.boot.autoconfigure.AutoConfigurationImportSelector。
2. 读取配置文件过程
springboot 启动过程中会调用到org.springframework.boot.SpringApplication#run(java.lang.String...)
public ConfigurableApplicationContext run(String... args) {
long startTime = System.nanoTime();
DefaultBootstrapContext bootstrapContext = this.createBootstrapContext();
ConfigurableApplicationContext context = null;
this.configureHeadlessProperty();
SpringApplicationRunListeners listeners = this.getRunListeners(args);
listeners.starting(bootstrapContext, this.mainApplicationClass);
try {
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
ConfigurableEnvironment environment = this.prepareEnvironment(listeners, bootstrapContext, applicationArguments);
this.configureIgnoreBeanInfo(environment);
Banner printedBanner = this.printBanner(environment);
context = this.createApplicationContext();
context.setApplicationStartup(this.applicationStartup);
this.prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);
this.refreshContext(context);
this.afterRefresh(context, applicationArguments);
Duration timeTakenToStartup = Duration.ofNanos(System.nanoTime() - startTime);
if (this.logStartupInfo) {
(new StartupInfoLogger(this.mainApplicationClass)).logStarted(this.getApplicationLog(), timeTakenToStartup);
}
listeners.started(context, timeTakenToStartup);
this.callRunners(context, applicationArguments);
} catch (Throwable var12) {
this.handleRunFailure(context, var12, listeners);
throw new IllegalStateException(var12);
}
try {
Duration timeTakenToReady = Duration.ofNanos(System.nanoTime() - startTime);
listeners.ready(context, timeTakenToReady);
return context;
} catch (Throwable var11) {
this.handleRunFailure(context, var11, (SpringApplicationRunListeners)null);
throw new IllegalStateException(var11);
}
}
1. getRunListeners 方法
这个方法源码如下:
private SpringApplicationRunListeners getRunListeners(String[] args) {
Class<?>[] types = new Class[]{SpringApplication.class, String[].class};
return new SpringApplicationRunListeners(logger, this.getSpringFactoriesInstances(SpringApplicationRunListener.class, types, this, args), this.applicationStartup);
}
方法返回一个SpringApplicationRunListeners 对象。 getSpringFactoriesInstances 方法会从springboot 的自动配置读取SpringApplicationRunListener 实现类。也就是上面的EventPublishingRunListener。
2. prepareEnvironment
开始准备环境
private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners, DefaultBootstrapContext bootstrapContext, ApplicationArguments applicationArguments) {
ConfigurableEnvironment environment = this.getOrCreateEnvironment();
this.configureEnvironment(environment, applicationArguments.getSourceArgs());
ConfigurationPropertySources.attach(environment);
listeners.environmentPrepared(bootstrapContext, environment);
DefaultPropertiesPropertySource.moveToEnd(environment);
Assert.state(!environment.containsProperty("spring.main.environment-prefix"), "Environment prefix cannot be set via properties.");
this.bindToSpringApplication(environment);
if (!this.isCustomEnvironment) {
EnvironmentConverter environmentConverter = new EnvironmentConverter(this.getClassLoader());
environment = environmentConverter.convertEnvironmentIfNecessary(environment, this.deduceEnvironmentClass());
}
ConfigurationPropertySources.attach(environment);
return environment;
}
- 创建一个环境对象
- 对环境进行配置
- listeners.environmentPrepared(bootstrapContext, environment); 准备环境,发布事件
void environmentPrepared(ConfigurableBootstrapContext bootstrapContext, ConfigurableEnvironment environment) {
this.doWithListeners("spring.boot.application.environment-prepared", (listener) -> {
listener.environmentPrepared(bootstrapContext, environment);
});
}
- 继续调用到org.springframework.boot.context.event.EventPublishingRunListener#environmentPrepared
public void environmentPrepared(ConfigurableBootstrapContext bootstrapContext, ConfigurableEnvironment environment) {
this.initialMulticaster.multicastEvent(new ApplicationEnvironmentPreparedEvent(bootstrapContext, this.application, this.args, environment));
}
这里就是广播事件,接下来就是看事件处理器的处理。
- 继续调用到org.springframework.context.event.SimpleApplicationEventMulticaster#multicastEvent(org.springframework.context.ApplicationEvent)
public void multicastEvent(ApplicationEvent event) {
this.multicastEvent(event, this.resolveDefaultEventType(event));
}
public void multicastEvent(final ApplicationEvent event, @Nullable ResolvableType eventType) {
ResolvableType type = eventType != null ? eventType : this.resolveDefaultEventType(event);
Executor executor = this.getTaskExecutor();
Iterator var5 = this.getApplicationListeners(event, type).iterator();
while(var5.hasNext()) {
ApplicationListener<?> listener = (ApplicationListener)var5.next();
if (executor != null) {
executor.execute(() -> {
this.invokeListener(listener, event);
});
} else {
this.invokeListener(listener, event);
}
}
}
这里获取到的变量var5包含如下listener:
接着遍历6个listener,然后invokeListener 内部调用doInvokeListener 方法。也就是调用到 listener.onApplicationEvent(event); 方法。
4. 6个listener的inApplicationEvent 方法(环境后置处理器用法)
1. org.springframework.boot.env.EnvironmentPostProcessorApplicationListener#onApplicationEvent
public void onApplicationEvent(ApplicationEvent event) {
if (event instanceof ApplicationEnvironmentPreparedEvent) {
this.onApplicationEnvironmentPreparedEvent((ApplicationEnvironmentPreparedEvent)event);
}
if (event instanceof ApplicationPreparedEvent) {
this.onApplicationPreparedEvent();
}
if (event instanceof ApplicationFailedEvent) {
this.onApplicationFailedEvent();
}
}
private void onApplicationEnvironmentPreparedEvent(ApplicationEnvironmentPreparedEvent event) {
ConfigurableEnvironment environment = event.getEnvironment();
SpringApplication application = event.getSpringApplication();
Iterator var4 = this.getEnvironmentPostProcessors(application.getResourceLoader(), event.getBootstrapContext()).iterator();
while(var4.hasNext()) {
EnvironmentPostProcessor postProcessor = (EnvironmentPostProcessor)var4.next();
postProcessor.postProcessEnvironment(environment, application);
}
}
- 这里是获取到环境的后置处理器,然后进行处理。获取到的7个后置处理器如下:
2. 核心的后置环境后置处理器是:ConfigDataEnvironmentPostProcessor
其逻辑如下:
public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) {
this.postProcessEnvironment(environment, application.getResourceLoader(), application.getAdditionalProfiles());
}
void postProcessEnvironment(ConfigurableEnvironment environment, ResourceLoader resourceLoader, Collection<String> additionalProfiles) {
try {
this.logger.trace("Post-processing environment to add config data");
ResourceLoader resourceLoader = resourceLoader != null ? resourceLoader : new DefaultResourceLoader();
this.getConfigDataEnvironment(environment, (ResourceLoader)resourceLoader, additionalProfiles).processAndApply();
} catch (UseLegacyConfigProcessingException var5) {
this.logger.debug(LogMessage.format("Switching to legacy config file processing [%s]", var5.getConfigurationProperty()));
this.configureAdditionalProfiles(environment, additionalProfiles);
this.postProcessUsingLegacyApplicationListener(environment, resourceLoader);
}
}
ConfigDataEnvironment getConfigDataEnvironment(ConfigurableEnvironment environment, ResourceLoader resourceLoader, Collection<String> additionalProfiles) {
return new ConfigDataEnvironment(this.logFactory, this.bootstrapContext, environment, resourceLoader, additionalProfiles, this.environmentUpdateListener);
}
1.org.springframework.boot.context.config.ConfigDataEnvironment#ConfigDataEnvironment 创建对象和初始化
ConfigDataEnvironment(DeferredLogFactory logFactory, ConfigurableBootstrapContext bootstrapContext, ConfigurableEnvironment environment, ResourceLoader resourceLoader, Collection<String> additionalProfiles, ConfigDataEnvironmentUpdateListener environmentUpdateListener) {
Binder binder = Binder.get(environment);
UseLegacyConfigProcessingException.throwIfRequested(binder);
this.logFactory = logFactory;
this.logger = logFactory.getLog(this.getClass());
this.notFoundAction = (ConfigDataNotFoundAction)binder.bind("spring.config.on-not-found", ConfigDataNotFoundAction.class).orElse(ConfigDataNotFoundAction.FAIL);
this.bootstrapContext = bootstrapContext;
this.environment = environment;
this.resolvers = this.createConfigDataLocationResolvers(logFactory, bootstrapContext, binder, resourceLoader);
this.additionalProfiles = additionalProfiles;
this.environmentUpdateListener = environmentUpdateListener != null ? environmentUpdateListener : ConfigDataEnvironmentUpdateListener.NONE;
this.loaders = new ConfigDataLoaders(logFactory, bootstrapContext, resourceLoader.getClassLoader());
this.contributors = this.createContributors(binder);
}
(1). this.createContributors(binder) 是创建属性描述者:
private ConfigDataEnvironmentContributors createContributors(Binder binder) {
this.logger.trace("Building config data environment contributors");
MutablePropertySources propertySources = this.environment.getPropertySources();
List<ConfigDataEnvironmentContributor> contributors = new ArrayList(propertySources.size() + 10);
PropertySource<?> defaultPropertySource = null;
Iterator var5 = propertySources.iterator();
while(var5.hasNext()) {
PropertySource<?> propertySource = (PropertySource)var5.next();
if (DefaultPropertiesPropertySource.hasMatchingName(propertySource)) {
defaultPropertySource = propertySource;
} else {
this.logger.trace(LogMessage.format("Creating wrapped config data contributor for '%s'", propertySource.getName()));
contributors.add(ConfigDataEnvironmentContributor.ofExisting(propertySource));
}
}
contributors.addAll(this.getInitialImportContributors(binder));
if (defaultPropertySource != null) {
this.logger.trace("Creating wrapped config data contributor for default property source");
contributors.add(ConfigDataEnvironmentContributor.ofExisting(defaultPropertySource));
}
return this.createContributors((List)contributors);
}
(2). 继续调用到:
private List<ConfigDataEnvironmentContributor> getInitialImportContributors(Binder binder) {
List<ConfigDataEnvironmentContributor> initialContributors = new ArrayList();
this.addInitialImportContributors(initialContributors, this.bindLocations(binder, "spring.config.import", EMPTY_LOCATIONS));
this.addInitialImportContributors(initialContributors, this.bindLocations(binder, "spring.config.additional-location", EMPTY_LOCATIONS));
this.addInitialImportContributors(initialContributors, this.bindLocations(binder, "spring.config.location", DEFAULT_SEARCH_LOCATIONS));
return initialContributors;
}
从静态代码块可以看到默认的配置文件路径如下:
static {
List<ConfigDataLocation> locations = new ArrayList();
locations.add(ConfigDataLocation.of("optional:classpath:/;optional:classpath:/config/"));
locations.add(ConfigDataLocation.of("optional:file:./;optional:file:./config/;optional:file:./config/*/"));
DEFAULT_SEARCH_LOCATIONS = (ConfigDataLocation[])locations.toArray(new ConfigDataLocation[0]);
EMPTY_LOCATIONS = new ConfigDataLocation[0];
CONFIG_DATA_LOCATION_ARRAY = Bindable.of(ConfigDataLocation[].class);
STRING_LIST = Bindable.listOf(String.class);
ALLOW_INACTIVE_BINDING = new ConfigDataEnvironmentContributors.BinderOption[0];
DENY_INACTIVE_BINDING = new ConfigDataEnvironmentContributors.BinderOption[]{BinderOption.FAIL_ON_BIND_TO_INACTIVE_SOURCE};
}
(3).....继续后面的初始化
- org.springframework.boot.context.config.ConfigDataEnvironment#processAndApply 处理
void processAndApply() {
ConfigDataImporter importer = new ConfigDataImporter(this.logFactory, this.notFoundAction, this.resolvers, this.loaders);
this.registerBootstrapBinder(this.contributors, (ConfigDataActivationContext)null, DENY_INACTIVE_BINDING);
ConfigDataEnvironmentContributors contributors = this.processInitial(this.contributors, importer);
ConfigDataActivationContext activationContext = this.createActivationContext(contributors.getBinder((ConfigDataActivationContext)null, new ConfigDataEnvironmentContributors.BinderOption[]{BinderOption.FAIL_ON_BIND_TO_INACTIVE_SOURCE}));
contributors = this.processWithoutProfiles(contributors, importer, activationContext);
activationContext = this.withProfiles(contributors, activationContext);
contributors = this.processWithProfiles(contributors, importer, activationContext);
this.applyToEnvironment(contributors, activationContext, importer.getLoadedLocations(), importer.getOptionalLocations());
}
(1). this.processInitial 会根据上面生成的属性描述符去找文件,经过多层调用,调用到org.springframework.boot.context.config.StandardConfigDataLocationResolver#resolve(org.springframework.boot.context.config.ConfigDataLocationResolverContext, org.springframework.boot.context.config.ConfigDataLocation)
public List<StandardConfigDataResource> resolve(ConfigDataLocationResolverContext context, ConfigDataLocation location) throws ConfigDataNotFoundException {
return this.resolve(this.getReferences(context, location.split()));
}
参数如下:
解析到的结果如下:
class path resource [config/application.properties]
(2). this.createActivationContext(contributors.getBinder 会根据文件解析配置。找到文件开始调用propertySourceLoaders 解析文件,比如yaml和properties 对应的解析器分别为:PropertiesPropertySourceLoader、YamlPropertySourceLoader。 真正的解析方法也是在loadProperties 内部。调用连如下:
(3). 调用org.springframework.boot.context.config.ConfigDataEnvironment#applyToEnvironment 应用到environment 对象
private void applyToEnvironment(ConfigDataEnvironmentContributors contributors, ConfigDataActivationContext activationContext, Set<ConfigDataLocation> loadedLocations, Set<ConfigDataLocation> optionalLocations) {
this.checkForInvalidProperties(contributors);
this.checkMandatoryLocations(contributors, activationContext, loadedLocations, optionalLocations);
MutablePropertySources propertySources = this.environment.getPropertySources();
this.applyContributor(contributors, activationContext, propertySources);
DefaultPropertiesPropertySource.moveToEnd(propertySources);
Profiles profiles = activationContext.getProfiles();
this.logger.trace(LogMessage.format("Setting default profiles: %s", profiles.getDefault()));
this.environment.setDefaultProfiles(StringUtils.toStringArray(profiles.getDefault()));
this.logger.trace(LogMessage.format("Setting active profiles: %s", profiles.getActive()));
this.environment.setActiveProfiles(StringUtils.toStringArray(profiles.getActive()));
this.environmentUpdateListener.onSetProfiles(profiles);
}
1》MutablePropertySources propertySources = this.environment.getPropertySources(); 获取到propertySources;
继续调用org.springframework.boot.context.config.ConfigDataEnvironment#applyContributor:(应用配置)
private void applyContributor(ConfigDataEnvironmentContributors contributors, ConfigDataActivationContext activationContext, MutablePropertySources propertySources) {
this.logger.trace("Applying config data environment contributions");
Iterator var4 = contributors.iterator();
while(var4.hasNext()) {
ConfigDataEnvironmentContributor contributor = (ConfigDataEnvironmentContributor)var4.next();
PropertySource<?> propertySource = contributor.getPropertySource();
if (contributor.getKind() == Kind.BOUND_IMPORT && propertySource != null) {
if (!contributor.isActive(activationContext)) {
this.logger.trace(LogMessage.format("Skipping inactive property source '%s'", propertySource.getName()));
} else {
this.logger.trace(LogMessage.format("Adding imported property source '%s'", propertySource.getName()));
propertySources.addLast(propertySource);
this.environmentUpdateListener.onPropertySourceAdded(propertySource, contributor.getLocation(), contributor.getResource());
}
}
}
}
最终addLast(propertySource) 会加一个对象:
根据java引用传递,实际是向environment.getPropertySources(); 添加了一个propertySource 对象。实际也就是org.springframework.core.env.PropertySource 的实现类。
到这里大致流程走完。
3. 自定义配置后置处理器以及测试
假设一个场景是从自己定义配置来源。我们增加自定的配置后置处理器然后测试。
1. 建立自己的后置处理器
MyEnvironmentPostProcessor
package com.example.demo.config;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.env.EnvironmentPostProcessor;
import org.springframework.core.env.ConfigurableEnvironment;
import java.util.HashMap;
public class MyEnvironmentPostProcessor implements EnvironmentPostProcessor {
@Override
public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) {
environment.getPropertySources().addLast(new MyPropertySource("myPropertySource", new HashMap<>()));
}
}
MyPropertySource
package com.example.demo.config;
import org.springframework.core.env.PropertySource;
import java.util.HashMap;
public class MyPropertySource extends PropertySource<HashMap<String, Object>> {
public MyPropertySource(String name, HashMap<String, Object> source) {
super(name, source);
// 模拟加几个配置
source.put("name", "zs");
}
@Override
public Object getProperty(String name) {
return getSource().get(name);
}
}
resource/META-INT/spring.factories 文件增加如下配置:
org.springframework.boot.env.EnvironmentPostProcessor=\
com.example.demo.config.MyEnvironmentPostProcessor
2. 测试
测试类
package com.example.demo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.env.Environment;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
@Component
public class TestComponent {
@Autowired
private Environment environment;
@Value("${name}")
private String name;
@PostConstruct
public void init() {
System.out.println(name);
}
}
-
输出zs
-
com.example.demo.config.MyEnvironmentPostProcessor#postProcessEnvironment 调用连如下
-
com.example.demo.config.MyPropertySource#getProperty 注入name属性调用连如下: