从源码解析SpringBoot如何读取配置文件

SpringBoot 版本 2.6.1

Spring项目一般有.properties和.yml两种类型的配置文件,日常工作中知道在resource文件夹中新建这两种类型的文件,Spring框架会将配置文件中的内容自动装载,特别的是SpringBoot自从2.4.0版本以后大改了这部分内容的代码逻辑,原有的关键类ConfigFileApplicationListener被遗弃,因此趁此机会深入新版本的SpringBoot框架源码,看看它在什么时候怎么样去装载这些文件的。
SpringApplication.run函数是一切的起点,我们看看这个函数的实现是怎样的的:

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);
        }
    }

在这段代码中,Spring读取配置文件的入口在于:

ConfigurableEnvironment environment = this.prepareEnvironment(listeners, bootstrapContext, applicationArguments);

我们看看preparedEnvironment函数具体的内容:

private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners, DefaultBootstrapContext bootstrapContext, ApplicationArguments applicationArguments) {
        ConfigurableEnvironment environment = this.getOrCreateEnvironment();
        this.configureEnvironment((ConfigurableEnvironment)environment, applicationArguments.getSourceArgs());
        ConfigurationPropertySources.attach((Environment)environment);
        listeners.environmentPrepared(bootstrapContext, (ConfigurableEnvironment)environment);
        DefaultPropertiesPropertySource.moveToEnd((ConfigurableEnvironment)environment);
        Assert.state(!((ConfigurableEnvironment)environment).containsProperty("spring.main.environment-prefix"), "Environment prefix cannot be set via properties.");
        this.bindToSpringApplication((ConfigurableEnvironment)environment);
        if (!this.isCustomEnvironment) {
            environment = this.convertEnvironment((ConfigurableEnvironment)environment);
        }

        ConfigurationPropertySources.attach((Environment)environment);
        return (ConfigurableEnvironment)environment;
    }

SpringBoot在环境建立好的时候需要去广播这个事件,那在上面这段代码中监听器EventPublishingRunListener(接口SpringApplicationRunListener的一个实现类)会调用environmentPrepared函数去广播事件。接下来看看这个函数中是如何广播的:

void environmentPrepared(ConfigurableBootstrapContext bootstrapContext, ConfigurableEnvironment environment) {
        this.doWithListeners("spring.boot.application.environment-prepared", (listener) -> {
            listener.environmentPrepared(bootstrapContext, environment);
        });
}

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

SimpleApplicationEventMulticaster将调用multicastEvent方法:

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);
            }
        }
    }
    
protected void invokeListener(ApplicationListener<?> listener, ApplicationEvent event) {
        ErrorHandler errorHandler = this.getErrorHandler();
        if (errorHandler != null) {
            try {
                this.doInvokeListener(listener, event);
            } catch (Throwable var5) {
                errorHandler.handleError(var5);
            }
        } else {
            this.doInvokeListener(listener, event);
        }

    }

    private void doInvokeListener(ApplicationListener listener, ApplicationEvent event) {
        try {
            listener.onApplicationEvent(event);
        } catch (ClassCastException var6) {
            String msg = var6.getMessage();
            if (msg != null && !this.matchesClassCastMessage(msg, event.getClass()) && (!(event instanceof PayloadApplicationEvent) || !this.matchesClassCastMessage(msg, ((PayloadApplicationEvent)event).getPayload().getClass()))) {
                throw var6;
            }

            Log loggerToUse = this.lazyLogger;
            if (loggerToUse == null) {
                loggerToUse = LogFactory.getLog(this.getClass());
                this.lazyLogger = loggerToUse;
            }

            if (loggerToUse.isTraceEnabled()) {
                loggerToUse.trace("Non-matching event type for listener: " + listener, var6);
            }
        }

    }

从前面三段代码中我们可以看出,在multicastEvent函数中,会获取到Application中所有的listeners:
Iterator var5 = this.getApplicationListeners(event, type).iterator();
我们读取配置文件的listener就是EnvironmentPostProcessorApplicationListener, 并在之后的doInvokeListener函数中调用这个listener的onApplicationEvent方法。
onApplicationEvent方法在EnvironmentPostProcessorApplicationListener类的实现如下:

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);
        }

    }

在onApplicationEnvironmentPreparedEvent函数中,通过getEnvironmentPostProcessors函数获取所有EnvironmentPostProcessor对象的列表,EnvironmentPostProcessor这个类在Spring里的作用我们可以理解成为读取配置文件对外暴露的统一接口,我们可以通过实现这个接口的类来实现读取自定义配置文件。Spring框架中的ConfigDataEnvironmentPostProcessor类就是实现了这个接口用以读取默认的配置文件。
在以上代码中,ConfigDataEnvironmentPostProcessor的实例调用了postProcessEnvironment函数将配置文件的内容读取到环境中,我们来看一下这段代码的实现:

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);
        }

    }

以上代码中,实现读取配置文件并加载进入env中是这一句代码:
this.getConfigDataEnvironment(environment, (ResourceLoader)resourceLoader, additionalProfiles).processAndApply();
getConfigDataEnvironment用以新建一个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);
    }

接下来processApply()函数将会进行一番骚操作:

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 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());
    }

首先提供了一个初始化函数对应默认配置文件的载入,并同时生成一个ConfigDataEnvironmentContributors 的实例:
ConfigDataEnvironmentContributors contributors = this.processInitial(this.contributors, importer);
然后提供了两个函数分别对应有profile和无profile的配置文件的载入:
contributors = this.processWithoutProfiles(contributors, importer, activationContext);
contributors = this.processWithProfiles(contributors, importer, activationContext);
我们先看看初始化的载入是怎么样的:

private ConfigDataEnvironmentContributors processInitial(ConfigDataEnvironmentContributors contributors, ConfigDataImporter importer) {
        this.logger.trace("Processing initial config data environment contributors without activation context");
        contributors = contributors.withProcessedImports(importer, (ConfigDataActivationContext)null);
        this.registerBootstrapBinder(contributors, (ConfigDataActivationContext)null, DENY_INACTIVE_BINDING);
        return contributors;
    }

其中的关键函数是contributors.withProcessedImports(importer, activationContext),在这段函数中我们终于找到了读取application.properties 和 application.yml文件的关键信息:

ConfigDataEnvironmentContributors withProcessedImports(ConfigDataImporter importer, ConfigDataActivationContext activationContext) {
        ImportPhase importPhase = ImportPhase.get(activationContext);
        this.logger.trace(LogMessage.format("Processing imports for phase %s. %s", importPhase, activationContext != null ? activationContext : "no activation context"));
        ConfigDataEnvironmentContributors result = this;
        int processed = 0;

        while(true) {
            ConfigDataEnvironmentContributor contributor = this.getNextToProcess(result, activationContext, importPhase);
            if (contributor == null) {
                this.logger.trace(LogMessage.format("Processed imports for of %d contributors", processed));
                return result;
            }

            if (contributor.getKind() == Kind.UNBOUND_IMPORT) {
                Iterable<ConfigurationPropertySource> sources = Collections.singleton(contributor.getConfigurationPropertySource());
                PlaceholdersResolver placeholdersResolver = new ConfigDataEnvironmentContributorPlaceholdersResolver(result, activationContext, true);
                Binder binder = new Binder(sources, placeholdersResolver, (ConversionService)null, (Consumer)null, (BindHandler)null);
                ConfigDataEnvironmentContributor bound = contributor.withBoundProperties(binder);
                result = new ConfigDataEnvironmentContributors(this.logger, this.bootstrapContext, result.getRoot().withReplacement(contributor, bound));
            } else {
                ConfigDataLocationResolverContext locationResolverContext = new ConfigDataEnvironmentContributors.ContributorConfigDataLocationResolverContext(result, contributor, activationContext);
                ConfigDataLoaderContext loaderContext = new ConfigDataEnvironmentContributors.ContributorDataLoaderContext(this);
                List<ConfigDataLocation> imports = contributor.getImports();
                this.logger.trace(LogMessage.format("Processing imports %s", imports));
                Map<ConfigDataResolutionResult, ConfigData> imported = importer.resolveAndLoad(activationContext, locationResolverContext, loaderContext, imports);
                this.logger.trace(LogMessage.of(() -> {
                    return this.getImportedMessage(imported.keySet());
                }));
                ConfigDataEnvironmentContributor contributorAndChildren = contributor.withChildren(importPhase, this.asContributors(imported));
                result = new ConfigDataEnvironmentContributors(this.logger, this.bootstrapContext, result.getRoot().withReplacement(contributor, contributorAndChildren));
                ++processed;
            }
        }
    }

ConfigDataEnvironmentContributor这个类是一个树状结构的对象,在以上代码段中,将会遍历这棵树:
this.getNextToProcess(result, activationContext, importPhase);
在这棵树的root位置是一个INITIAL_IMPORT的ConfigDataEnvironmentContributor对象,这个对象会包含默认配置文件的文件位置,通过 List<ConfigDataLocation> imports = contributor.getImports()来获取ConfigDataLocation的列表。接下来通过传进来的ConfigDataImporter对象去解析并加载配置文件中包含的信息
Map<ConfigDataResolutionResult, ConfigData> imported = importer.resolveAndLoad(activationContext, locationResolverContext, loaderContext, imports),并保存进一个Map当中,我们看看resolveAndLoad函数是怎样的:

Map<ConfigDataResolutionResult, ConfigData> resolveAndLoad(ConfigDataActivationContext activationContext, ConfigDataLocationResolverContext locationResolverContext, ConfigDataLoaderContext loaderContext, List<ConfigDataLocation> locations) {
        try {
            Profiles profiles = activationContext != null ? activationContext.getProfiles() : null;
            List<ConfigDataResolutionResult> resolved = this.resolve(locationResolverContext, profiles, locations);
            return this.load(loaderContext, resolved);
        } catch (IOException var7) {
            throw new IllegalStateException("IO error on loading imports from " + locations, var7);
        }
    }

通过resolve方法根据locations获得配置文件列表并通过load方法加载配置文件,返回的map中的value是ConfigData对象,我们看看ConfigData对象的定义是啥:


image.png

PropertySource类型 太熟悉了是不是?经过这一系列的操作之后,我们算是获取到了默认情况下配置文件中包含的键值对了, processWithoutProfiles 和processWithProfiles函数都会调用核心代码withProcessedImports,所以细节就不再叙说了,值得注意的一点是,每一个ConfigDataLocation实例都代表了配置文件夹位置,那么默认情况下是在哪里初始化的呢?请看以下代码:

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 BinderOption[0];
        DENY_INACTIVE_BINDING = new BinderOption[]{BinderOption.FAIL_ON_BIND_TO_INACTIVE_SOURCE};
    }

这段静态代码段存在于ConfigDataEnvironment类当中,那么我们是什么时候初始化这个类的呢?还记不记得getConfigDataEnvironment函数?至此读配置文件的过程就结束了,那么接下来就是要把之前的这个map放到environment里面了,所以在processAndApply函数中的最后一段代码就是来做这个事情的:
this.applyToEnvironment(contributors, activationContext, importer.getLoadedLocations(), importer.getOptionalLocations()),我们最后来看看是如何加载进环境当中的:

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);
    }

很好理解是不是,都是一些set函数,那现在我们这个ApplicationContext的环境成功读取了配置文件的信息。

©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容