SpringBoot(十)启动流程分析之ApplicationEnvironmentPreparedEvent事件发布

本文分析了SpringBoot启动过程中构建环境的流程,重点讲解了ApplicationEnvironmentPreparedEvent事件的发布,包括配置属性源、配置文件、EnvironmentPostProcessor的执行等,这些步骤涉及配置文件加载、日志系统初始化等关键操作。

SpringBoot版本:2.1.1   ==》启动流程分析汇总

接上篇博客 Spring Boot 2.1.1(九)启动流程分析之args参数的封装解析

目录

构建环境

1、创建ConfigurableEnvironment对象

2、配置环境

2.1、配置属性源

2.2、配置配置文件

3、发布ApplicationEnvironmentPreparedEvent事件

 4、将环境绑定到SpringApplication

 5、将配置属性源绑定到Environment

 总结


args参数封装执行完成以后,就是构建环境Environment,下面分析其运行流程。

public ConfigurableApplicationContext run(String... args) {
        .... 
	try {
        //本篇内容从本行开始记录
		ConfigurableEnvironment environment = prepareEnvironment(listeners,
					applicationArguments);
            //本篇内容记录到这,后续更新
        ....
        }
	catch (Throwable ex) {
		handleRunFailure(context, ex, exceptionReporters, listeners);
		throw new IllegalStateException(ex);
	}
}

构建环境

private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners,
		ApplicationArguments applicationArguments) {
	//创建环境,这里是根据web应用类型返回的,得到的是一个标准基于servlet的web环境
	ConfigurableEnvironment environment = getOrCreateEnvironment();
        //配置环境,参数是上一步创建的environment和原始args参数
	configureEnvironment(environment, applicationArguments.getSourceArgs());
        //发布事件ApplicationEnvironmentPreparedEvent
	listeners.environmentPrepared(environment);
        //将spring.main配置绑定到SpringApplication
	bindToSpringApplication(environment);
        //isCustomEnvironment默认为false,如果你通过SpringApplication对象的setEnvironment()方法设置了环境,该值变为true,直接跳过这里
        //转换为标准环境
	if (!this.isCustomEnvironment) {
		environment = new EnvironmentConverter(getClassLoader())
				.convertEnvironmentIfNecessary(environment, deduceEnvironmentClass());
	}
	ConfigurationPropertySources.attach(environment);
	return environment;
}

1、创建ConfigurableEnvironment对象

private ConfigurableEnvironment getOrCreateEnvironment() {
        //如果你通过SpringApplication对象的setEnvironment()方法设置了环境,则直接返回该环境
	if (this.environment != null) {
		return this.environment;
	}
        //根据webApplicationType选择
	switch (this.webApplicationType) {
	case SERVLET:
                //标准的servlet环境
		return new StandardServletEnvironment();
	case REACTIVE:
                //标准的响应式web环境
        return new StandardReactiveWebEnvironment();
	default:
                //标准环境
		return new StandardEnvironment();
	}
}

返回的标准servlet环境如下:

StandardServletEnvironment {
    activeProfiles=[], 
    defaultProfiles=[default], 
    propertySources[
        StubPropertySource{name='servletConfigInitParams'},
        StubPropertySource {name='servletContextInitParams'},
        MapPropertySource {name='systemProperties'}, 
        SystemEnvironmentPropertySource {name='systemEnvironment'}]
}

2、配置环境

protected void configureEnvironment(ConfigurableEnvironment environment,
			String[] args) {
        //是否添加转换服务,后面在必要时转换环境
	if (this.addConversionService) {
                //得到共享实例
		ConversionService conversionService = ApplicationConversionService
				.getSharedInstance();
                //set
		environment.setConversionService(
			(ConfigurableConversionService) conversionService);
	}
       //配置属性源,包括默认配置和命令行配置,默认配置使用SpringApplication的setDefaultProperties(Map<String, Object> defaultProperties)方法设置
	configurePropertySources(environment, args);
       //设置配置文件
	configureProfiles(environment, args);
}

2.1、配置属性源

protected void configurePropertySources(ConfigurableEnvironment environment,
			String[] args) {
        //得到属性源,即返回的标准环境中的propertySources,上面贴出来了
	MutablePropertySources sources = environment.getPropertySources();
        //判断默认配置是否为空,不为空就添加到source里即propertySources,name为defaultProperties
        //通过application.setDefaultProperties(defaultProperties);设置
	if (this.defaultProperties != null && !this.defaultProperties.isEmpty()) {
		sources.addLast(
				new MapPropertySource("defaultProperties", this.defaultProperties));
	}
        //判断原始args参数长度是否大于0,addCommandLineProperties 默认为true
	if (this.addCommandLineProperties && args.length > 0) {
                //得到name:commandLineArgs,name值前面封装args参数说过了就不解释了
		String name = CommandLinePropertySource.COMMAND_LINE_PROPERTY_SOURCE_NAME;
                //判断source是否存在commandLineArgs,哈哈哈,肯定不存在,propertySources的内容上面都贴出来了
		if (sources.contains(name)) {
                        //通过commandLineArgs得到配置
			PropertySource<?> source = sources.get(name);
                        //new一个CompositePropertySource,是PropertySource的子类,所以构造方法里传一个name知道啥意思了吧,前面介绍过PropertySource了
                        //该类中有一个Set<PropertySource<?>>
            CompositePropertySource composite = new CompositePropertySource(name);
                        //添加到set集合
			composite.addPropertySource(new SimpleCommandLinePropertySource(
					"springApplicationCommandLineArgs", args));
			composite.addPropertySource(source); 
                        //用composite替换原 source中name为commandLineArgs的配置
			sources.replace(name, composite);
		}
		else {
                    //不存在就new一个SimpleCommandLinePropertySource实例添加
			sources.addFirst(new SimpleCommandLinePropertySource(args));
		}
	}
}

2.2、配置配置文件

protected void configureProfiles(ConfigurableEnvironment environment, String[] args) {
	environment.getActiveProfiles(); // 确保已初始化
	// But these ones should go first (last wins in a property key clash)
        //this.additionalProfiles为set集合,使用application.setAdditionalProfiles(profiles)可以设置,即设置启用的配置文件
	Set<String> profiles = new LinkedHashSet<>(this.additionalProfiles);
        //将配置项设置的值也添加到set集合
	profiles.addAll(Arrays.asList(environment.getActiveProfiles()));
        //设置ActiveProfiles
	environment.setActiveProfiles(StringUtils.toStringArray(profiles));
}

public String[] getActiveProfiles() {
	return StringUtils.toStringArray(doGetActiveProfiles());
}

可以看到得到的配置文件就是配置项spring.profiles.active配置的。会判断this.activeProfiles是否为空,如果你是通过context.getEnvironment().setActiveProfiles(profiles)方法设置的,那就直接返回了。

3、发布ApplicationEnvironmentPreparedEvent事件

事件的具体发布过程就不详细解释了,前面解释过了。

listeners.environmentPrepared(environment);

检索出来的Listener有七个。

同样简单介绍下Lintener的onApplication()方法执行了什么操作。

ConfigFileApplicationListener:得到EnvironmentPostProcessor接口的实现类实例,List集合,并添加本身,根据order排序,循环执行postProcessEnvironment方法。

得到的EnvironmentPostProcessor接口实现类有三个,加上ConfigFileApplicationListener,一共四个,如下。

SystemEnvironmentPropertySourceEnvironmentPostProcessor:从标准环境中得到name为systemEnvironment的PropertySource,然后将SystemEnvironmentPropertySource替换为OriginAwareSystemEnvironmentPropertySource,用于提供从系统环境加载的项对原始属性名的访问。即可以通过原始属性名的到属性值。

SpringApplicationJsonEnvironmentPostProcessor:循环数组String[] CANDIDATES={spring.application.json,SPRING_APPLICATION_JSON},确认propertySource中是否存在这两个系统属性,如果存在new一个JsonPropertyValue对象,该类为内部类,将name和value封装,如果不存在返回空,然后过滤是否为空,找到第一个确认是否有这个值,如果有就解析json封装成JsonPropertySource并添加到环境(environment)中,如果不存在不执行任何操作。

CloudFoundryVcapEnvironmentPostProcessor:判断环境中是否存在VCAP_APPLICATION或者VCAP_SERVICES。如果有就添加Cloud Foundry的配置;没有就不执行任何操作。

ConfigFileApplicationListener:得到PropertySourceLoader的实现类PropertiesPropertySourceLoader、YamlPropertySourceLoader,然后加载配置文件,包括application.properties/yml(或yaml)/xml以及application-{dev,prod,test}.properties/yml(或yaml)/xml

AnsiOutputApplicationListener:将spring.output.ansi.enabled的值跟环境自绑定,再根据绑定值配置ANSI输出。

有三个值:

DETECT:尝试检测ANSI着色功能是否可用,默认是这个

ALWAYS:启用ANSI彩色输出

NEVER:禁用ANSI彩色输出

然后根据spring.output.ansi.console-available的值设置System.Console()是否可用。

LoggingApplicationListener:调用initialize()方法完成日志系统的初始化,前面ApplicationStartingEvent事件发布的时候已经调用了beforeInitialize()方法。如果没有配置日志,则使用默认配置,默认情况下,日志输出只写入控制台。如果需要日志文件,logging.path和logging.file属性可以使用。

ClasspathLoggingApplicationListener:在调试级别记录线程上下文类加载器(TCCL)的类路径,对ApplicationEnvironmentPreparedEvent和ApplicationFailedEvent做出反应。

BackgroundPreinitializer:貌似在这一步没做什么事,在ApplicationStartingEvent事件发布的时候已经执行了。

DelegatingApplicationListener:如果是ApplicationEnvironmentPreparedEvent事件,在getListeners方法中会得到配置项"context.listener.classes"设置的listener,得到实例对象添加到多播器multicaster中。否则判断多播器multicaster是否为空,不为空继续广播事件。

FileEncodingApplicationListener:默认情况下不起什么作用,也就是啥都不执行。但是如果spring.mandatory_file_encoding的值和file.encoding属性值不同时会引发报错。

 4、将环境绑定到SpringApplication

protected void bindToSpringApplication(ConfigurableEnvironment environment) {
	try {
		Binder.get(environment).bind("spring.main", Bindable.ofInstance(this));
	}
	catch (Exception ex) {
		throw new IllegalStateException("Cannot bind to SpringApplication", ex);
	}
}

 5、将配置属性源绑定到Environment

public static void attach(Environment environment) {
	Assert.isInstanceOf(ConfigurableEnvironment.class, environment);
        //得到PropertySources
	MutablePropertySources sources = ((ConfigurableEnvironment) environment)
			.getPropertySources();
        //得到name为configurationProperties的配置源,现在是没有的,所以是null
	PropertySource<?> attached = sources.get(ATTACHED_PROPERTY_SOURCE_NAME);
	if (attached != null && attached.getSource() != sources) {
		sources.remove(ATTACHED_PROPERTY_SOURCE_NAME);
		attached = null;
	}
	if (attached == null) {
            //new了一个ConfigurationPropertySourcesPropertySource,name为configurationProperties,source就是整个source,该类继承了PropertySource,构造方法参数啥用不多说了,添加到第一个
		sources.addFirst(new ConfigurationPropertySourcesPropertySource(
				ATTACHED_PROPERTY_SOURCE_NAME,
				new SpringConfigurationPropertySources(sources)));
	}
}

 总结

整个prepareEnvironment()方法走完以后得到的Environment如下:

StandardServletEnvironment {
    activeProfiles=[], 
    defaultProfiles=[default], 
    propertySources=[
        ConfigurationPropertySourcesPropertySource {name='configurationProperties'},     
        StubPropertySource {name='servletConfigInitParams'}, 
        StubPropertySource {name='servletContextInitParams'}, 
        MapPropertySource {name='systemProperties'}, 
        OriginAwareSystemEnvironmentPropertySource {name='systemEnvironment'},     
        RandomValuePropertySource {name='random'}]}

其中ApplicationEnvironmentPreparedEvent事件的发布主要做的就是加载配置文件,初始化日志系统。

用起来有多方便,debug看源码的时候都要还的.....

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值