从源码分析springboot环境配置加载

从源码分析springboot环境配置加载

一直没有搞清楚springboot环境配置信息到底是怎么加载的,是不是在启动时指定–spring.profiles.active之后spring就去指定读取这个文件了,因此这次从源码角度研究一下它的加载过程。

首先从入口开始分析:

public static void main(String[] args) {
    //这里的run是springboot启动入口
    SpringApplication.run(StarServiceAgencyOrderCoreApplication.class, args);
}
public ConfigurableApplicationContext run(String... args) {
            //其他的先不管,springboot的环境信息封装在Environment这个对象中,加载也是从这里开始的,
    	   //而在Environment对象中有个MutablePropertySources,这里是存储具体的环境配置信息的地方
            ConfigurableEnvironment environment = this.prepareEnvironment(listeners, applicationArguments);
          
    }

接下来开始分析一下prepareEnvironment大致干了些啥。

private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments) {
    	//创建environment对象
        ConfigurableEnvironment environment = this.getOrCreateEnvironment();
    	//做了一些配置初始化,以及加载了一些默认信息,这里可以看到在这个方法执行结束后environment中的propertySource中加载了一些信息,这里其实是在environment初始化时就已经初始化好的,在构造方法中就可以看到了
        this.configureEnvironment((ConfigurableEnvironment)environment, applicationArguments.getSourceArgs());
    	//从这里开始加载了。
        listeners.environmentPrepared((ConfigurableEnvironment)environment);
        this.bindToSpringApplication((ConfigurableEnvironment)environment);
        if (!this.isCustomEnvironment) {
            environment = (new EnvironmentConverter(this.getClassLoader())).convertEnvironmentIfNecessary((ConfigurableEnvironment)environment, this.deduceEnvironmentClass());
        }

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

可以看到这一块加载是通过listener去完成的。

public void environmentPrepared(ConfigurableEnvironment environment) {
        Iterator var2 = this.listeners.iterator();

        while(var2.hasNext()) {
            //这里通过的EventPublishingRunListener来处理
            SpringApplicationRunListener listener = (SpringApplicationRunListener)var2.next();
            listener.environmentPrepared(environment);
        }

}

接下来在来看EventPublishingRunListener。

//这里没啥好看的,一层一层剥洋葱吧。
public void environmentPrepared(ConfigurableEnvironment environment) {
    this.initialMulticaster.multicastEvent(new ApplicationEnvironmentPreparedEvent(this.application, this.args, environment));
}
//这里也没有看到重点,继续剥
public void multicastEvent(ApplicationEvent event, @Nullable ResolvableType eventType) {
        ResolvableType type = eventType != null ? eventType : this.resolveDefaultEventType(event);
        Iterator var4 = this.getApplicationListeners(event, type).iterator();

        while(var4.hasNext()) {
            ApplicationListener<?> listener = (ApplicationListener)var4.next();
            Executor executor = this.getTaskExecutor();
            if (executor != null) {
                executor.execute(() -> {
                    this.invokeListener(listener, event);
                });
            } else {
                this.invokeListener(listener, event);
            }
        }
}

一直到ConfigFileApplicationListener类中的onApplicationEvent方法。可以看到ConfigFileApplicationListener中的成员变量,有那么一些熟悉的字眼,接下来还有几层,从ConfigFileApplicationListener#onApplicationEvent() ->onApplicationEnvironmentPreparedEvent->postProcessEnvironment->addPropertySources->ConfigFileApplicationListener.Loader#load。一直到这里才真正开始加载了。

public void load() {
    //...
    //这个方法初始化profiles的信息,
    this.initializeProfiles();
	//...
    //开始加载配置文件信息
        this.load(profile, this::getPositiveProfileFilter, this.addToLoaded(MutablePropertySources::addLast, false));
}

这个方法还是看一下,在这里可以看到在Profiles中首先添加了一个null,另外添加了指定的active配置信息,如果active没有找到的情况下,默认添加了一个default。这也就是springboot会默认加载application-default.xx文件的原因。至于为什么添加一个null,在后面代码处就可以看明白了。

private void initializeProfiles() {
    this.profiles.add((Object)null);
    Set<ConfigFileApplicationListener.Profile> activatedViaProperty = this.getProfilesActivatedViaProperty();
    this.profiles.addAll(this.getOtherActiveProfiles(activatedViaProperty));
    this.addActiveProfiles(activatedViaProperty);
    if (this.profiles.size() == 1) {
        String[] var2 = this.environment.getDefaultProfiles();
        int var3 = var2.length;

        for(int var4 = 0; var4 < var3; ++var4) {
            String defaultProfileName = var2[var4];
            ConfigFileApplicationListener.Profile defaultProfile = new ConfigFileApplicationListener.Profile(defaultProfileName, true);
            this.profiles.add(defaultProfile);
        }
    }

}

接下来看一下load方法。

private void load(ConfigFileApplicationListener.Profile profile, ConfigFileApplicationListener.DocumentFilterFactory filterFactory, ConfigFileApplicationListener.DocumentConsumer consumer) {
    this.getSearchLocations().forEach((location) -> {
        boolean isFolder = location.endsWith("/");
        //这个name是配置文件前缀名。
        Set<String> names = isFolder ? this.getSearchNames() : ConfigFileApplicationListener.NO_SEARCH_NAMES;
        names.forEach((name) -> {
            //加载的方法
            this.load(location, name, profile, filterFactory, consumer);
        });
    });
}
//这里还是在准备读取过程中
private void load(String location, String name, ConfigFileApplicationListener.Profile profile, ConfigFileApplicationListener.DocumentFilterFactory filterFactory, ConfigFileApplicationListener.DocumentConsumer consumer) {
            //...
                        this.loadForFileExtension(loaderx, location + name, "." + fileExtension, profile, filterFactory, consumer);
}

private void loadForFileExtension(PropertySourceLoader loader, String prefix, String fileExtension, ConfigFileApplicationListener.Profile profile, ConfigFileApplicationListener.DocumentFilterFactory filterFactory, ConfigFileApplicationListener.DocumentConsumer consumer) {
            	//...
                //在这里可以看到spring-default.xx的文件是在这里拼接上去的
                String profileSpecificFile = prefix + "-" + profile + fileExtension;
			//...
            this.load(loader, prefix + fileExtension, profile, profileFilter, consumer);
 }

//完成配置文件加载的方法
private void load(PropertySourceLoader loader, String location, ConfigFileApplicationListener.Profile profile, ConfigFileApplicationListener.DocumentFilter filter, ConfigFileApplicationListener.DocumentConsumer consumer) {
                //通过resourceLoader去寻找文件
                Resource resource = this.resourceLoader.getResource(location);
                StringBuilder descriptionxx;
                		//...
                        String name = "applicationConfig: [" + location + "]";
                		//最终在loadDocument中完成加载
                        List<ConfigFileApplicationListener.Document> documents = this.loadDocuments(loader, name, resource);
                       //... 
}

private List<ConfigFileApplicationListener.Document> loadDocuments(PropertySourceLoader loader, String name, Resource resource) throws IOException {
            //..             
    		//加载完成,是properties文件就通过PropertiesPropertySourceLoader加载,是yaml文件就通过YamlPropertySourceLoader加载。
                List<PropertySource<?>> loaded = loader.load(name, resource);
              //..
        }

附上分析的时序图:在这里插入图片描述到这初始化的配置文件加载就分析的差不多了。可以得出的结论是spring在初始化时application.xx文件一定会加载,另外则是看你配置的active指定环境文件。那么最后,假如两边都定义了一个变量,spring是怎么去取值呢,哪个值优先呢?

那么回到SpringApplication中,refreshContext看spring容器的初始化方法,这里是入口,具体的步骤很复杂,可以自己跟一下或者单独去看spring的初始化过程,这里就省略了。那么跳到PropertySourcesPropertyResolver#getProperty,这里是spring中对初始化对象赋值的其中一个方法。

//这里的key就是你要赋值的对象key,例如你定义的成员变量
protected <T> T getProperty(String key, Class<T> targetValueType, boolean resolveNestedPlaceholders) {
    if (this.propertySources != null) {
        //这个propertySources就是刚才加载配置文件后拿到的MutablePropertySources,而这里赋值是通过循环来查找PropertySources中对应存在的key值,找到后就return了。
        Iterator var4 = this.propertySources.iterator();

        while(var4.hasNext()) {
            PropertySource<?> propertySource = (PropertySource)var4.next();
            if (this.logger.isTraceEnabled()) {
                this.logger.trace("Searching for key '" + key + "' in PropertySource '" + propertySource.getName() + "'");
            }
		   //取值过程
            Object value = propertySource.getProperty(key);
            if (value != null) {
                if (resolveNestedPlaceholders && value instanceof String) {
                    value = this.resolveNestedPlaceholders((String)value);
                }

                this.logKeyFound(key, propertySource, value);
                //取到值后就return了
                return this.convertValueIfNecessary(value, targetValueType);
            }
        }
    }

    if (this.logger.isTraceEnabled()) {
        this.logger.trace("Could not find key '" + key + "' in any property source");
    }

    return null;
}

从上面的代码可以看出来,假如两边都定义了一个变量,spring是怎么去取值优先顺序跟MutablePropertySources中的顺序有关,具体的顺序spring官方给出了定义。参考:https://docs.spring.io/spring-boot/docs/2.2.0.RELEASE/reference/html/spring-boot-features.html#boot-features-external-config 中的Externalized Configuration项。如下:

Spring Boot uses a very particular PropertySource order that is designed to allow sensible overriding of values. Properties are considered in the following order:

  1. Devtools global settings properties in the $HOME/.config/spring-boot folder when devtools is active.
  2. @TestPropertySource annotations on your tests.
  3. properties attribute on your tests. Available on @SpringBootTest and the test annotations for testing a particular slice of your application.
  4. Command line arguments.
  5. Properties from SPRING_APPLICATION_JSON (inline JSON embedded in an environment variable or system property).
  6. ServletConfig init parameters.
  7. ServletContext init parameters.
  8. JNDI attributes from java:comp/env.
  9. Java System properties (System.getProperties()).
  10. OS environment variables.
  11. A RandomValuePropertySource that has properties only in random.*.
  12. Profile-specific application properties outside of your packaged jar (application-{profile}.properties and YAML variants).
  13. Profile-specific application properties packaged inside your jar (application-{profile}.properties and YAML variants).
  14. Application properties outside of your packaged jar (application.properties and YAML variants).
  15. Application properties packaged inside your jar (application.properties and YAML variants).
  16. @PropertySource annotations on your @Configuration classes.
  17. Default properties (specified by setting SpringApplication.setDefaultProperties).

自此springboot的配置项加载分析完成。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章