美文网首页
Springboot源码跟读3--prepareEnvironm

Springboot源码跟读3--prepareEnvironm

作者: 安中古天乐 | 来源:发表于2020-04-16 19:50 被阅读0次

上篇我们已经把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方法中的重要点走读完毕。

相关文章

网友评论

      本文标题:Springboot源码跟读3--prepareEnvironm

      本文链接:https://www.haomeiwen.com/subject/iogdvhtx.html