Springboot源码跟读3--prepareEnvironment方法

上篇我们已经把SpringApplication.run过程中SpringApplicationRunListener的加载及starting流程讲解完了,本篇我们接着往下跟:

public ConfigurableApplicationContext run(String... args) {
    StopWatch stopWatch = new StopWatch();
    stopWatch.start();
    ConfigurableApplicationContext context = null;
    Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList();
    this.configureHeadlessProperty();
    SpringApplicationRunListeners listeners = this.getRunListeners(args);
    listeners.starting();

    Collection exceptionReporters;
    try {
        ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
        // 本篇的主角,prepareEnvironment方法
        ConfigurableEnvironment environment = this.prepareEnvironment(listeners, applicationArguments);
        ...
    }
}

具体看一下prepareEnvironment方法:

private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments) {
    // 根据应用类型,创建应用环境:如得到系统环境变量、JVM及Servlet等参数
    ConfigurableEnvironment environment = this.getOrCreateEnvironment();
    // 将 defaultProperties、commandLine及active-prifiles 属性加载到环境中
    // commandLine 在 args 中配置
    // 其它参数可在如下4个路径中配置:servletConfigInitParams、servletContextInitParams、systemProperties、systemEnvironment
    this.configureEnvironment((ConfigurableEnvironment)environment, applicationArguments.getSourceArgs());
    // listeners环境准备(就是广播ApplicationEnvironmentPreparedEvent事件)
    listeners.environmentPrepared((ConfigurableEnvironment)environment);
    // 将环境绑定到SpringApplication
    this.bindToSpringApplication((ConfigurableEnvironment)environment);
     // 如果是非web环境,将环境转换成StandardEnvironment
    if (this.webApplicationType == WebApplicationType.NONE) {
        environment = (new EnvironmentConverter(this.getClassLoader())).convertToStandardEnvironmentIfNecessary((ConfigurableEnvironment)environment);
    }
     // 配置PropertySources对它自己的递归依赖
    ConfigurationPropertySources.attach((Environment)environment);
    return (ConfigurableEnvironment)environment;
}

下面我们一行一行来看。

getOrCreateEnvironment

private ConfigurableEnvironment getOrCreateEnvironment() {
    // 如果environment不为null,说明已初始化过,直接返回即可
    if (this.environment != null) {
        return this.environment;
    } else {
        // 若envirment为null,需要执行初始化
        // 根据webApplicationType的类型来决定创建哪种environment
        // 若是Web应用,则创建StandardServletEnvironment,否则创建StandardEnvironment
        return (ConfigurableEnvironment)(this.webApplicationType == WebApplicationType.SERVLET ? new StandardServletEnvironment() : new StandardEnvironment());
    }
}

我们以StandardServletEnvironment为例,看一下环境初始化过程中发生了什么。

public StandardServletEnvironment() {
}

额,好像就是个空构造函数,难道啥也没做?

哈哈哈,不要被迷惑了,我们知道,类执行构造函数时,会从顶层类往下依次执行,是不是StandardServletEnvironment的父类里有具体操作。

StandardServletEnvironment的父类为StandardEnvironment:

public StandardEnvironment() {
}

仍然为空构造,继续往上追踪父类,StandardEnvironment的父类为AbstractEnvironment:

哈哈哈,终于找到你了!!!

public AbstractEnvironment() {
    this.propertySources = new MutablePropertySources(this.logger);
    this.propertyResolver = new PropertySourcesPropertyResolver(this.propertySources);
    // 关键是customizePropertySources方法
    this.customizePropertySources(this.propertySources);
    if (this.logger.isDebugEnabled()) {
        this.logger.debug("Initialized " + this.getClass().getSimpleName() + " with PropertySources " + this.propertySources);
    }

}

整个的继承关系如下:

1.jpg

AbstractEnvironment的customizePropertySources方法为protected修饰的空方法:

protected void customizePropertySources(MutablePropertySources propertySources) {
}

很明显,customizePropertySources的具体实现在子类StandardEnvironment及StandardServletEnvironment中。

先看StandardEnvironment的customizePropertySources方法:

protected void customizePropertySources(MutablePropertySources propertySources) {
    // 获取系统参数
    propertySources.addLast(new MapPropertySource("systemProperties", this.getSystemProperties()));
    // 获取系统环境变量
    propertySources.addLast(new SystemEnvironmentPropertySource("systemEnvironment", this.getSystemEnvironment()));
}

本质上底层还是走的System.getSystemProperties()和System.getenv()。

public Map<String, Object> getSystemProperties() {
    try {
        return System.getProperties();
    } catch (AccessControlException var2) {
        return new ReadOnlySystemAttributesMap() {
            @Nullable
            protected String getSystemAttribute(String attributeName) {
                try {
                    return System.getProperty(attributeName);
                } catch (AccessControlException var3) {
                    if (AbstractEnvironment.this.logger.isInfoEnabled()) {
                        AbstractEnvironment.this.logger.info("Caught AccessControlException when accessing system property '" + attributeName + "'; its value will be returned [null]. Reason: " + var3.getMessage());
                    }

                    return null;
                }
            }
        };
    }
}

public Map<String, Object> getSystemEnvironment() {
    if (this.suppressGetenvAccess()) {
        return Collections.emptyMap();
    } else {
        try {
            return System.getenv();
        } catch (AccessControlException var2) {
            return new ReadOnlySystemAttributesMap() {
                @Nullable
                protected String getSystemAttribute(String attributeName) {
                    try {
                        return System.getenv(attributeName);
                    } catch (AccessControlException var3) {
                        if (AbstractEnvironment.this.logger.isInfoEnabled()) {
                            AbstractEnvironment.this.logger.info("Caught AccessControlException when accessing system environment variable '" + attributeName + "'; its value will be returned [null]. Reason: " + var3.getMessage());
                        }

                        return null;
                    }
                }
            };
        }
    }
}
2.jpg
3.jpg

接着看StandardServletEnvironment的customizePropertySources方法:

protected void customizePropertySources(MutablePropertySources propertySources) {
    // 增加servletConfig初始化参数,当前没有任何值
    propertySources.addLast(new StubPropertySource("servletConfigInitParams"));
    // 增加servletContext初始化参数,当前没有任何值
    propertySources.addLast(new StubPropertySource("servletContextInitParams"));
    if (JndiLocatorDelegate.isDefaultJndiEnvironmentAvailable()) {
        propertySources.addLast(new JndiPropertySource("jndiProperties"));
    }
    // 紧接着调用StandardEnvironment的customizePropertySources方法
    super.customizePropertySources(propertySources);
}

说白了,StandardServletEnvironment比StandardEnvironment增加了2项Servlet相关的配置。

4.jpg

总结一句,getOrCreateEnvironment方法就是根据应用类型,创建应用环境:如得到系统环境变量、JVM及Servlet等参数。

configureEnvironment

protected void configureEnvironment(ConfigurableEnvironment environment, String[] args) {
    this.configurePropertySources(environment, args);
    this.configureProfiles(environment, args);
}

先看,configurePropertySources方法:

protected void configurePropertySources(ConfigurableEnvironment environment, String[] args) {
    // 从environment中获取PropertySources
    MutablePropertySources sources = environment.getPropertySources();
    // 如果defaultProperties不为null且不为空,将其加入sources
    if (this.defaultProperties != null && !this.defaultProperties.isEmpty()) {
        sources.addLast(new MapPropertySource("defaultProperties", this.defaultProperties));
    }
    // 否则,根据args重新构建SimpleCommandLinePropertySource,然后更新或添加进sources
    if (this.addCommandLineProperties && args.length > 0) {
        String name = "commandLineArgs";
        if (sources.contains(name)) {
            PropertySource<?> source = sources.get(name);
            CompositePropertySource composite = new CompositePropertySource(name);
            composite.addPropertySource(new SimpleCommandLinePropertySource("springApplicationCommandLineArgs", args));
            composite.addPropertySource(source);
            sources.replace(name, composite);
        } else {
            sources.addFirst(new SimpleCommandLinePropertySource(args));
        }
    }

}

SimpleCommandLinePropertySource构造过程中,主要完成解析args字符串,然后提取出相关配置,具体源码可以自行看一下。

总结一下,configurePropertySources方法的目的是将args里的相关配置添加进environment中

接着看configureProfiles方法:

protected void configureProfiles(ConfigurableEnvironment environment, String[] args) {
    environment.getActiveProfiles();
    Set<String> profiles = new LinkedHashSet(this.additionalProfiles);
    profiles.addAll(Arrays.asList(environment.getActiveProfiles()));
    environment.setActiveProfiles(StringUtils.toStringArray(profiles));
}

很明显,该方法的目的就是把active-prifiles属性加载到environment中。

environmentPrepared

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

    while(var2.hasNext()) {
        SpringApplicationRunListener listener = (SpringApplicationRunListener)var2.next();
        listener.environmentPrepared(environment);
    }

}

本质上调用的是SpringApplicationRunListener的environmentPrepared方法。

看一下EventPublishingRunListener的environmentPrepared方法实现:

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

具体过程其实跟SpringApplicationRunListener的starting类似,也是先根据event类型筛选出符合条件的ApplicationListener继承类实例集合,然后分别调用各个ApplicationListener继承类实例的onApplicationEvent(event)方法完成事件的回调,具体过程可参见上篇--Springboot源码跟读2--SpringApplicationRunListener的加载及starting

我们以spring.profiles.active配置的加载来看一下上述过程,在application.yml里配置:

spring:
  profiles:
    active: dev
5.jpg

Debug到上图断点处,看一下此时的environment:

6.jpg

可以看到,此时的activeProfiles仍旧为空,然后接着调用SpringApplicationRunListener的environmentPrepared,此时:

7.jpg

EventPublishingRunListener的environmentPrepared方法实现中,本质上是先将application、args、environment包装成ApplicationEnvironmentPreparedEvent,即表明环境已准备完毕的event。

上篇我们讲过,multicastEvent方法主要根据传入的event,从初始化后的ApplicationListeners中筛选出符合条件的ApplicationListener。

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

}

最后调用的是ApplicationListener的onApplicationEvent,而onApplicationEvent方法具体实现由ApplicationListener的各个继承类完成。

查看一下SpringApplication创建时初始化的所有ApplicationListener:

8.jpg

很明显,与配置加载相关的为ConfigFileApplicationListener。

查看一下ConfigFileApplicationListener的onApplicationEvent方法:

public void onApplicationEvent(ApplicationEvent event) {
    if (event instanceof ApplicationEnvironmentPreparedEvent) {
        this.onApplicationEnvironmentPreparedEvent((ApplicationEnvironmentPreparedEvent)event);
    }

    if (event instanceof ApplicationPreparedEvent) {
        this.onApplicationPreparedEvent(event);
    }

}

逻辑很简单,若传入的event是ApplicationEnvironmentPreparedEvent实例,则调用onApplicationEnvironmentPreparedEvent方法;若是ApplicationPreparedEvent实例,则调用onApplicationPreparedEvent方法。

跟一下onApplicationEnvironmentPreparedEvent方法:

private void onApplicationEnvironmentPreparedEvent(ApplicationEnvironmentPreparedEvent event) {
    List<EnvironmentPostProcessor> postProcessors = this.loadPostProcessors();
    postProcessors.add(this);
    // 此处打一个断点
    AnnotationAwareOrderComparator.sort(postProcessors);
    Iterator var3 = postProcessors.iterator();

    while(var3.hasNext()) {
        EnvironmentPostProcessor postProcessor = (EnvironmentPostProcessor)var3.next();
        postProcessor.postProcessEnvironment(event.getEnvironment(), event.getSpringApplication());
    }

}
9.jpg

然后调用所有被加载的postProcessor的postProcessEnvironment方法来对environment进行后置处理,而ConfigFileApplicationListener的postProcessEnvironment方法主要扫描"classpath:/,classpath:/config/,file:./,file:./config/"下的配置文件然后将相关配置更新到environment中,具体代码不细讲,自行查看。

至此,我们将prepareEnvironment方法中的重要点走读完毕。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,793评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,567评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,342评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,825评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,814评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,680评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,033评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,687评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,175评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,668评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,775评论 1 332
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,419评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,020评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,978评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,206评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,092评论 2 351
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,510评论 2 343

推荐阅读更多精彩内容