Springboot应用在启动时通常是通过调用SpringApplication.run() 方法进行,配置的加载要在应用最前面部分进行,即在准备Environment阶段进行。在SpringApplication 中有如下代码

    public ConfigurableApplicationContext run(String... args) {
       // 省略代码、
        ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
        ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);  //配置加载主要在这个方法中进行
        configureIgnoreBeanInfo(environment);
        //省略代码
private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners,ApplicationArguments applicationArguments) {
        // Create and configure the environment
        ConfigurableEnvironment environment = getOrCreateEnvironment();
        configureEnvironment(environment, applicationArguments.getSourceArgs());
        ConfigurationPropertySources.attach(environment);
        listeners.environmentPrepared(environment);  //监听者模式,在这里会触发一个事件 ApplicationEnvironmentPreparedEvent,相应的监听器在监听到该事件后便会做相应的操作
        //省略代码
        return environment;

跟踪以上代码发现,配置信息的加载实际上是在监听者中进行的,即ConfigFileApplicationListener,

public class ConfigFileApplicationListener implements EnvironmentPostProcessor, SmartApplicationListener, Ordered { * 该监听者对哪些事件感兴趣 @Override public boolean supportsEventType(Class<? extends ApplicationEvent> eventType) { return ApplicationEnvironmentPreparedEvent.class.isAssignableFrom(eventType) || ApplicationPreparedEvent.class.isAssignableFrom(eventType); //监听到事件 @Override public void onApplicationEvent(ApplicationEvent event) { if (event instanceof ApplicationEnvironmentPreparedEvent) { onApplicationEnvironmentPreparedEvent((ApplicationEnvironmentPreparedEvent) event); //对事件进行处理 //省略代码 //对事件进行详细处理 private void onApplicationEnvironmentPreparedEvent(ApplicationEnvironmentPreparedEvent event) { List<EnvironmentPostProcessor> postProcessors = loadPostProcessors(); postProcessors.add(this); AnnotationAwareOrderComparator.sort(postProcessors); for (EnvironmentPostProcessor postProcessor : postProcessors) { postProcessor.postProcessEnvironment(event.getEnvironment(), event.getSpringApplication()); @Override public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) { addPropertySources(environment, application.getResourceLoader()); + Add config file property sources to the specified environment. + @param environment the environment to add source to + @param resourceLoader the resource loader + @see #addPostProcessors(ConfigurableApplicationContext) protected void addPropertySources(ConfigurableEnvironment environment, ResourceLoader resourceLoader) { RandomValuePropertySource.addToEnvironment(environment); new Loader(environment, resourceLoader).load();

从代码中可以看出,该监听器在监听事件后,实际调用的 EnvironmentPostProcessor,通过对EnvironmentPostProcessor进行扩展可以对ConfigurableEnvironment进行定制化处理。而ConfigFileApplicationListener是实现了EnvironmentPostProcessor,所以配置文件的加载也就在这个监听器中进行,从上边的代码看到,调用链接到Loader中,Loader是ConfigFileApplicationListener的内部类,实际加载工作都在Loder中进行。

private class Loader { 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(); //每次加载完一个document会将active.profile,include.profile 添加到this.profiles,这样能够加载到所有指定的profile 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); private void load(Profile profile, DocumentFilterFactory filterFactory, DocumentConsumer consumer) { getSearchLocations().forEach((location) -> { //获取配置文件地址并遍历,如果通过属性spring.config.location指定,则使用指定的路径,否则按序查找默认位置:file:./config/,file:./config/*/,file:./,classpath:/config/,classpath:/,如果spring.config.additional-location 有值,会与前面获取到的路径合并 boolean isDirectory = location.endsWith("/"); Set<String> names = isDirectory ? getSearchNames() : NO_SEARCH_NAMES; //获取配置文件名称,如果spring.config.name指定文件名,则使用否则使用默认文件名application names.forEach((name) -> load(location, name, profile, filterFactory, consumer)); //遍历文件名加载文件 private void load(String location, String name, Profile profile, DocumentFilterFactory filterFactory,DocumentConsumer consumer) { if (!StringUtils.hasText(name)) { //未指定的文件名,认为文件路径中已经包含文件名,直接进行加载 for (PropertySourceLoader loader : this.propertySourceLoaders) { if (canLoadFileExtension(loader, location)) { load(loader, location, profile, filterFactory.getDocumentFilter(profile), consumer); return; //省略抛出异常代码 Set<String> processed = new HashSet<>(); for (PropertySourceLoader loader : this.propertySourceLoaders) { //遍历不同的属性加载器(yml,yaml,properties,xml) for (String fileExtension : loader.getFileExtensions()) { if (processed.add(fileExtension)) { loadForFileExtension(loader, location + name, "." + fileExtension, profile, filterFactory, consumer); 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) { //如果profile不为空,则优先加载指定profile的配置文件,eg:profile=dev,prefix=classpath://appliaction,fileExtension=.yml,则文件名classpath://appliaction-dev.yml // 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); // 加载不指定profile的文件,eg:classpath://appliaction.yml,profile可能在文件中分离 load(loader, prefix + fileExtension, profile, profileFilter, consumer); + 加载文件 private void load(PropertySourceLoader loader, String location, Profile profile, DocumentFilter filter,DocumentConsumer consumer) { Resource[] resources = getResources(location); for (Resource resource : resources) { try { //省略判断代码 String name = "applicationConfig: [" + getLocationName(location, resource) + "]"; //PropertySource name List<Document> documents = loadDocuments(loader, name, resource); //load配置文件,一个配置文件中可以指定多个profile,所以会返回 List<Document>,在yml文件中使用---分割多个profile if (CollectionUtils.isEmpty(documents)) { continue; List<Document> loaded = new ArrayList<>(); for (Document document : documents) { if (filter.match(document)) {//主要是判断profile是否是activeprofile或为空 addActiveProfiles(document.getActiveProfiles()); //获取spring.profiles.active,并入队,加载该profile中的配置信息 addIncludedProfiles(document.getIncludeProfiles()); //获取spring.profiles.include,并入队,加载该profile中的配置信息 loaded.add(document); Collections.reverse(loaded); if (!loaded.isEmpty()) { loaded.forEach((document) -> consumer.accept(profile, document)); //将加载好的符合条件的配置暂存 catch (Exception ex) { //异常处理

通过对以上代码分析,我们可以知道配置文件的加载流程

项目启动SpringApplication.run() --> prepareEnvironment() -->SpringApplicationRunListeners.environmentPrepared(environment);(发送ApplicationEnvironmentPreparedEvent) -->ConfigFileApplicationListener.onApplicationEvent()(监听到event) -->ConfigFileApplicationListener.postProcessEnvironment() -->Loader.load() -->Loader.loadForFileExtension()
Springcloud
  • 在以上文档中我们可以知道Springboot的配置文件加载方式,其实Springcloud是对Springboot进行封装,简单点说cloud执行了两次boot的流程。
  • 新增加监听器BootstrapApplicationListener,该监听器的执行优先级比ConfigFileApplicationListener高,所以在cloud应用启动时先执行BootstrapApplicationListener
    public class BootstrapApplicationListener implements ApplicationListener<ApplicationEnvironmentPreparedEvent>, Ordered{
            //监听事件
            @Override
            public void onApplicationEvent(ApplicationEnvironmentPreparedEvent event) {
                ConfigurableEnvironment environment = event.getEnvironment();
                //是否需要执行启动流程
                if (!environment.getProperty("spring.cloud.bootstrap.enabled", Boolean.class,
                        true)) {
                    return;
                // don't listen to events in a bootstrap context
                //表示正在初始化bootstrapcontext,直接返回执行后续listener
                if (environment.getPropertySources().contains(BOOTSTRAP_PROPERTY_SOURCE_NAME)) {
                    return;
                ConfigurableApplicationContext context = null;
                //可通过该属性指定启动上下文中加载的文件
                String configName = environment
                        .resolvePlaceholders("${spring.cloud.bootstrap.name:bootstrap}");
                for (ApplicationContextInitializer<?> initializer : event.getSpringApplication()
                        .getInitializers()) {
                    if (initializer instanceof ParentContextApplicationContextInitializer) {
                        context = findBootstrapContext(
                                (ParentContextApplicationContextInitializer) initializer,
                                configName);
                if (context == null) {
                    //bootstrap context 启动
                    context = bootstrapServiceContext(environment, event.getSpringApplication(),
                            configName);
                    event.getSpringApplication()
                            .addListeners(new CloseContextOnFailureApplicationListener(context));
                apply(context, event.getSpringApplication(), environment);
        private ConfigurableApplicationContext bootstrapServiceContext(
                    ConfigurableEnvironment environment, final SpringApplication application,
                    String configName) {
                StandardEnvironment bootstrapEnvironment = new StandardEnvironment();
                MutablePropertySources bootstrapProperties = bootstrapEnvironment
                        .getPropertySources();
                //清除所有配置信息
                for (PropertySource<?> source : bootstrapProperties) {
                    bootstrapProperties.remove(source.getName());
                //获取指定启动配置位置
                String configLocation = environment
                        .resolvePlaceholders("${spring.cloud.bootstrap.location:}");
                //额外文件夹
                String configAdditionalLocation = environment
                        .resolvePlaceholders("${spring.cloud.bootstrap.additional-location:}");
                Map<String, Object> bootstrapMap = new HashMap<>();
                //配置文件名称,会在后续的ConfigFileApplicationListener 中使用
                bootstrapMap.put("spring.config.name", configName);
                // if an app (or test) uses spring.main.web-application-type=reactive, bootstrap
                // will fail
                // force the environment to use none, because if though it is set below in the
                // builder
                // the environment overrides it
                bootstrapMap.put("spring.main.web-application-type", "none");
                if (StringUtils.hasText(configLocation)) {
                    //配置文件,会在后续的ConfigFileApplicationListener 中使用
                    bootstrapMap.put("spring.config.location", configLocation);
                if (StringUtils.hasText(configAdditionalLocation)) {
                     //配置文件,会在后续的ConfigFileApplicationListener 中使用
                    bootstrapMap.put("spring.config.additional-location",
                            configAdditionalLocation);
                //将组织好的初始化加载信息放入启动属性中
                bootstrapProperties.addFirst(
                        new MapPropertySource(BOOTSTRAP_PROPERTY_SOURCE_NAME, bootstrapMap));
                //将当前环境中PropertySource放入启动属性中,主要是系统属性,命令行属性等
                for (PropertySource<?> source : environment.getPropertySources()) {
                    if (source instanceof StubPropertySource) {
                        continue;
                    bootstrapProperties.addLast(source);
                //通过SpringApplicationBuilder 新建一个SpringApplication,并且填充一些属性
                // TODO: is it possible or sensible to share a ResourceLoader?
                SpringApplicationBuilder builder = new SpringApplicationBuilder()
                        .profiles(environment.getActiveProfiles()).bannerMode(Mode.OFF)
                        .environment(bootstrapEnvironment)
                        // Don't use the default properties in this builder
                        .registerShutdownHook(false).logStartupInfo(false)
                        .web(WebApplicationType.NONE);
                final SpringApplication builderApplication = builder.application();
                if (builderApplication.getMainApplicationClass() == null) {
                    // gh_425:
                    // SpringApplication cannot deduce the MainApplicationClass here
                    // if it is booted from SpringBootServletInitializer due to the
                    // absense of the "main" method in stackTraces.
                    // But luckily this method's second parameter "application" here
                    // carries the real MainApplicationClass which has been explicitly
                    // set by SpringBootServletInitializer itself already.
                    builder.main(application.getMainApplicationClass());
                if (environment.getPropertySources().contains("refreshArgs")) {
                    // If we are doing a context refresh, really we only want to refresh the
                    // Environment, and there are some toxic listeners (like the
                    // LoggingApplicationListener) that affect global static state, so we need a
                    // way to switch those off.
                    builderApplication
                            .setListeners(filterListeners(builderApplication.getListeners()));
                //类似于MainClass,通过BootstrapImportSelectorConfiguration 可以将spring.factories中 org.springframework.cloud.bootstrap.BootstrapConfiguration 指定的类先加载,做一些cloud 的前置加载工作
                builder.sources(BootstrapImportSelectorConfiguration.class);
                //嵌套调用SpringApplication.run(),本次boostrap完成后,会继续执行之前未完成的上下文
                final ConfigurableApplicationContext context = builder.run();
                // gh-214 using spring.application.name=bootstrap to set the context id via
                // `ContextIdApplicationContextInitializer` prevents apps from getting the actual
                // spring.application.name
                // during the bootstrap phase.
                context.setId("bootstrap");
                // Make the bootstrap context a parent of the app context
                addAncestorInitializer(application, context);
                // It only has properties in it now that we don't want in the parent so remove
                // it (and it will be added back later)
                //移除name=bootstrap 的 PropertySource,该属性源是为了加载bootstrap context 添加的,已经完成使命,如果不移除会影响后续application context 中配置的加载,主要是因为他的优先级很高,会影响spring.config.name等的取值
                bootstrapProperties.remove(BOOTSTRAP_PROPERTY_SOURCE_NAME);
                //bootstrap context 初始化完成以后合并属性,为后续的Application context 添加属性,此时会把加载的 configName文件合并
                mergeDefaultProperties(environment.getPropertySources(), bootstrapProperties);
                return context;
            + 向applicationcontext中添加一些信息
            private void apply(ConfigurableApplicationContext context,SpringApplication application, ConfigurableEnvironment environment) {
                if (application.getAllSources().contains(BootstrapMarkerConfiguration.class)) {
                    return;
                application.addPrimarySources(Arrays.asList(BootstrapMarkerConfiguration.class));
                @SuppressWarnings("rawtypes")
                Set target = new LinkedHashSet<>(application.getInitializers());
                target.addAll(
                        getOrderedBeansOfType(context, ApplicationContextInitializer.class));
                application.setInitializers(target);
                addBootstrapDecryptInitializer(application);
    
  • 由以上代码可以看出,cloud实际上是通过一个优先级较高的Listener来嵌套生成一个bootstrap context,从而能够提前加载一些cloud相关的一些配置,准备cloud的环境,bootstrap context 加载完成后,会继续执行application context的加载,也就是上边的springBoot的加载过程
  • 我们可以得出的调用流程是
  • 项目启动SpringApplication.run() 
    --> prepareEnvironment() 
    -->SpringApplicationRunListeners.environmentPrepared(environment);(发送ApplicationEnvironmentPreparedEvent) 
        -->BootstrapApplicationListener.onApplicationEvent()(监听到event) 
        -->判断是否正在执行初始化bootstrap context 阶段,若是则跳过该listener,执行后续的listener
        -->BootstrapApplicationListener.bootstrapServiceContext()(生成bootstrap context) 
            -->设置bootstrapEnvironment,spring.config.name,spring.config.location等属性
            -->SpringApplicationgBuilder.run() 
                -->SpringApplication.run() 
    -->ConfigFileApplicationListener.onApplicationEvent()(监听到event) 
    -->ConfigFileApplicationListener.postProcessEnvironment() 
    -->Loader.load() 
    -->Loader.loadForFileExtension()
    复制代码
  •