springboot啟動讀取配置檔案過程&自定義配置檔案處理器
最近看到看到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屬性呼叫連如下: