SpringBoot之浅析配置项解析(二)

简介: 我们在上一篇文章中简单的说了一些SpringBoot配置属性相关的一些内容,我们在这篇文章中接着上一篇的文章继续进行分析。

我们在上一篇文章中简单的说了一些SpringBoot配置属性相关的一些内容,我们在这篇文章中接着上一篇的文章继续进行分析。我们在上一篇文章中提到了这样一个类:ConfigFileApplicationListener,从类名来看的话这是一个配置文件应用监听器,这个类主要的一个作用是在 refresh context之前解析默认的配置文件。首先我们来看一下它的UML类图:
ConfigFileApplicationListener
ConfigFileApplicationListener实现了EnvironmentPostProcessor和SmartApplicationListener这两个接口,从上图中我们可以看到SmartApplicationListener其实是继承了ApplicationListener这个接口的。SmartApplicationListener这个类相比于ApplicationListener多了两个方法,一个是用来检测是否支持给定的类型,一个是用来检测得到的source type。EnvironmentPostProcessor这个接口的作用是在refresh application context之前定制化应用的环境配置信息,需要说明的是,实现这个接口的类必须要在spring.factories进行配置。大家可以想一下这里为什么要这样。既然ConfigFileApplicationListener也是一个监听器类,那么它肯定会监听某些动作的发生,我们在之前的文章中说过,在org.springframework.boot.SpringApplication#run(java.lang.String… args)这个方法中会负责SpringBoot的整个启动工作,在这里有这样的一段代码:

        SpringApplicationRunListeners listeners = getRunListeners(args);
        listeners.starting();

顺着listeners.starting();这个方法往下扒的话,你就会找到调用org.springframework.boot.context.config.ConfigFileApplicationListener#onApplicationEvent这个方法的地方,这个调用链不算很负责,这里就不再多说了。我们直接进入到ConfigFileApplicationListener#onApplicationEvent这个方法中:

    public void onApplicationEvent(ApplicationEvent event) {
        if (event instanceof ApplicationEnvironmentPreparedEvent) {
            onApplicationEnvironmentPreparedEvent(
                    (ApplicationEnvironmentPreparedEvent) event);
        }
        if (event instanceof ApplicationPreparedEvent) {
            onApplicationPreparedEvent(event);
        }
    }

但是啊但是,在onApplicationEvent中有两个条件判断,你通过listeners.starting()这个调用链一直跟踪到这里的话你会发现,这两个条件都不满足listeners.starting()的调用,那么是在什么地方触发这个监听事件的呢?在org.springframework.boot.SpringApplication#prepareEnvironment这个方法中,我们在上一篇文章中介绍了其中的一些代码,这里再看一段代码:

listeners.environmentPrepared(environment);

org.springframework.boot.SpringApplicationRunListeners#environmentPrepared

    public void environmentPrepared(ConfigurableEnvironment environment) {
    //这里的listeners是从spring.factories中得到的
        for (SpringApplicationRunListener listener : this.listeners) {
            listener.environmentPrepared(environment);
        }
    }

org.springframework.boot.context.event.EventPublishingRunListener#environmentPrepared

public void environmentPrepared(ConfigurableEnvironment environment) {
//这里的ApplicationEvent是ApplicationEnvironmentPreparedEvent满足我们上面的提到的两个条件中的一个     this.initialMulticaster.multicastEvent(new ApplicationEnvironmentPreparedEvent(
                this.application, this.args, environment));
    }

从上面的代码中我们可以看到在执行listeners.environmentPrepared(environment);
的时候,会调用ConfigFileApplicationListener#onApplicationEvent这个方法,并且所传入的ApplicationEvent是ApplicationEnvironmentPreparedEvent,所以这里会调用ConfigFileApplicationListener#onApplicationEvent中的org.springframework.boot.context.config.ConfigFileApplicationListener#onApplicationEnvironmentPreparedEvent方法。我们进入到onApplicationEnvironmentPreparedEvent方法中看一下:

private void onApplicationEnvironmentPreparedEvent(
            ApplicationEnvironmentPreparedEvent event) {
        //从spring.factories中获取key为org.springframework.boot.env.EnvironmentPostProcessor的配置信息不再细说    
        List<EnvironmentPostProcessor> postProcessors = loadPostProcessors();
        //前面说过ConfigFileApplicationListener也实现了EnvironmentPostProcessor接口
        postProcessors.add(this);
        //排序
        AnnotationAwareOrderComparator.sort(postProcessors);
        //循环上一步得到的List,分别调用各自的postProcessEnvironment方法,在这里我们只关注ConfigFileApplicationListener这个类的postProcessEnvironment方法即可
        for (EnvironmentPostProcessor postProcessor : postProcessors) {
            postProcessor.postProcessEnvironment(event.getEnvironment(),
                    event.getSpringApplication());
        }
    }

postProcessEnvironment的源码如下:

    public void postProcessEnvironment(ConfigurableEnvironment environment,
            SpringApplication application) {
        //添加PropertySource 这个是我们要分析的重点
        addPropertySources(environment, application.getResourceLoader());
        //JavaBean内省的配置
        configureIgnoreBeanInfo(environment);
        bindToSpringApplication(environment, application);
    }

在上面的代码中,我们目前需要重点关注的就是ConfigFileApplicationListener#addPropertySources这个方法:

    protected void addPropertySources(ConfigurableEnvironment environment,
            ResourceLoader resourceLoader) {
//添加RandomValuePropertySource
RandomValuePropertySource.addToEnvironment(environment);
//加载默认配置信息,这个environment就是我们在SpringApplication中创建的StandardServletEnvironment
new Loader(environment, resourceLoader).load();
    }

RandomValuePropertySource.addToEnvironment

public static void addToEnvironment(ConfigurableEnvironment environment) {
    //MutablePropertySources#addAfter 这个方法我们在上一章中说过了这里就不再多说了。这段代码的意思是将RandomValuePropertySource的顺序添加到systemEnvironment的后面
    environment.getPropertySources().addAfter(
    //systemEnvironment
StandardEnvironment.SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME,
//random
        new RandomValuePropertySource(RANDOM_PROPERTY_SOURCE_NAME));
}

上面这段代码的意思就将RandomValuePropertySource添加到systemEnvironment的后面,到目前为止我们的MutablePropertySources#propertySourceList中的元素顺序为:commandLineArgs、servletConfigInitParams 、servletContextInitParams 、jndiProperties 、systemProperties 、systemEnvironment、random。我们接着分析new Loader(environment, resourceLoader).load();这段代码,这个代码中的内容比较多,我们也不是全部都要关注,这里我们先把Profile相关的内容先剥离出去。我们直接到这里:

            while (!this.profiles.isEmpty()) {
                Profile profile = this.profiles.poll();
                //获取配置文件的位置 1)
                for (String location : getSearchLocations()) {
                    //如果不是以/结尾的话则调用下面的方法
                    if (!location.endsWith("/")) {
                        // location is a filename already, so don't search for more
                        // filenames
                        load(location, null, profile);
                    }
                    else {
                        //获取配置文件的名称 2)
                        for (String name : getSearchNames()) {
                            load(location, name, profile);
                        }
                    }
                }
                this.processedProfiles.add(profile);
            }

我们先来看上面标记为1)处的代码:

        private Set<String> getSearchLocations() {
            Set<String> locations = new LinkedHashSet<String>();
            // User-configured settings take precedence, so we do them first
            //如果配置了spring.config.location这个属性值,则先解析这个属性值
            if (this.environment.containsProperty(CONFIG_LOCATION_PROPERTY)) {
                //多个值以 , 进行分割
                for (String path : asResolvedSet(
                        this.environment.getProperty(CONFIG_LOCATION_PROPERTY), null)) {
                    if (!path.contains("$")) {
                        path = StringUtils.cleanPath(path);
                        if (!ResourceUtils.isUrl(path)) {
                            path = ResourceUtils.FILE_URL_PREFIX + path;
                        }
                    }
                    locations.add(path);
                }
            }
            //通常我们都不会设置  ' spring.config.location ' 这个属性值 这里添加默认位置
            //默认位置为 "classpath:/,classpath:/config/,file:./,file:./config/"
            //asResolvedSet会用 , 分割上述字符串 并且还有一个作用将上传得到的List进行 翻转 注意这个很重要!!! 即使你在上面设置了 ' spring.config.location '  它的位置也是在系统默认的位置的后面
            locations.addAll(
                    asResolvedSet(ConfigFileApplicationListener.this.searchLocations,
                            DEFAULT_SEARCH_LOCATIONS));
            return locations;
        }

我们接着看2)处的代码:

        private Set<String> getSearchNames() {
        //如果配置了 spring.config.name 属性值
            if (this.environment.containsProperty(CONFIG_NAME_PROPERTY)) {
                return asResolvedSet(this.environment.getProperty(CONFIG_NAME_PROPERTY),
                        null);
            }
            //解析默认的文件名 application  这里同样会对获取到的List进行翻转
            return asResolvedSet(ConfigFileApplicationListener.this.names, DEFAULT_NAMES);
        }

所以上面while的这一段代码我们可以简化为这样(极其简化):

        List<String> listString = (Arrays.asList("file:./config/,file:./,classpath:/config/,classpath:/".split(",")));
        List<String> listName = Arrays.asList("application");
        for (String location : listString) {
            for(String name : listName){
                load(location, name, profile);
            }
        }
相关文章
|
2天前
|
JSON Java Maven
Javaweb之SpringBootWeb案例之 SpringBoot原理的详细解析
Javaweb之SpringBootWeb案例之 SpringBoot原理的详细解析
7 0
Javaweb之SpringBootWeb案例之 SpringBoot原理的详细解析
|
2天前
|
存储 Java 程序员
SpringBoot之分层解耦以及 IOC&DI的详细解析
SpringBoot之分层解耦以及 IOC&DI的详细解析
5 0
|
2天前
|
前端开发 Java
SpringBoot之三层架构的详细解析
SpringBoot之三层架构的详细解析
13 0
|
2天前
|
前端开发 Java
SpringBoot之实体参数的详细解析
SpringBoot之实体参数的详细解析
10 0
|
2天前
|
Web App开发 前端开发 Java
SpringBoot之请求的详细解析
SpringBoot之请求的详细解析
12 0
|
XML Java 数据格式
SpringBoot之浅析配置项解析(三)
我们接着上一篇的文章继续分析。我们来看这一段代码: //在上一篇文章中我们分析了getSearchNames()这个方法,这个方法默认返回 只有一个元素 application的List...
1305 0
|
Java uml Spring
SpringBoot之浅析配置项解析(一)
在我们的开发工作总是离不了配置项相关的配置工作,SpringBoot也为我们提供了@ConfigurationProperties注解来进行配置项信息的配置工作,同时也提供了几个配置文件的默认加载位置,如:classpath:application.
1778 0
|
15天前
|
Java Linux
Springboot 解决linux服务器下获取不到项目Resources下资源
Springboot 解决linux服务器下获取不到项目Resources下资源

推荐镜像

更多