Spring boot源码深入学习(四) | prepareEnvironment准备环境

回顾

上一篇 Spring boot源码深入学习(三) | SpringApplication实例化以及第一个监听器事件发布
学习了springboot中:
1.SpringApplication的实例化流程
2.springboot读取spring.factories文件,获取监听器,第一次执行发布事件流程等等
下面接着后面的分析学习:
【1】获取监听器
【2】准备环境
【3】控制台打印Banner
【4】创建容器,根据不同类型创建不同的容器
【5】实例化异常报告期实例,用于记录启动过程中的错误。
【6】准备容器,给刚刚创建的容器做一些初始化工作
【7】刷新容器,这一步至关重要。后续再做解析
【8】刷新容器后的一些操作,这里是空方法
本文着重分析学习【2】准备环境
在这里插入图片描述

prepareEnvironment源码分析

prepareEnvironment源码步骤分析:
1.取得当前环境或者创建一个环境:SERVLET,REACTIVE,NONE
2.配置环境
3.加载配置文件.通过ApplicationEnvironmentPreparedEvent事件发布,执行对应的监听器事件
4.绑定环境到SpringApplication

ConfigurableEnvironment environment = prepareEnvironment(listeners,
					applicationArguments);
	private ConfigurableEnvironment prepareEnvironment(
			SpringApplicationRunListeners listeners,
			ApplicationArguments applicationArguments) {
		// 1.取得当前环境或者创建一个环境:SERVLET,REACTIVE,NONE
		ConfigurableEnvironment environment = getOrCreateEnvironment();
		// 2.配置环境
		configureEnvironment(environment, applicationArguments.getSourceArgs());
		// 3.加载配置文件.通过ApplicationEnvironmentPreparedEvent事件发布,执行对应的监听器事件
		listeners.environmentPrepared(environment);
		// 4.绑定环境到SpringApplication
		bindToSpringApplication(environment);
		if (!this.isCustomEnvironment) {
			environment = new EnvironmentConverter(getClassLoader())
					.convertEnvironmentIfNecessary(environment, deduceEnvironmentClass());
		}
		ConfigurationPropertySources.attach(environment);
		return environment;
	}

下面分步分析

初始化环境getOrCreateEnvironment
	private ConfigurableEnvironment getOrCreateEnvironment() {
		// 当前有环境配置,则返回
		if (this.environment != null) {
			return this.environment;
		}
		// 根据类型初始化环境
		switch (this.webApplicationType) {
			// SERVLET环境
		case SERVLET:
			return new StandardServletEnvironment();
			// REACTIVE环境
		case REACTIVE:
			return new StandardReactiveWebEnvironment();
			// 默认环境none
		default:
			return new StandardEnvironment();
		}
	}

由代码可知webApplicationType分为SERVLET,REACTIVE,NONE三种环境,根据此类型初始化环境
NONE:不需要再web容器的环境下运行,普通项目
SERVLET:基于servlet的web项目
REACTIVE:spring5版本开始的新特性

Environment接口提供了4种实现方式:StandardEnvironment(NONE):普通程序StandardServletEnvironment(SERVLET):Web程序MockEnvironment(测试):测试程序的环境StandardReactiveWebEnvironment(REACTIVE):响应式web环境
解析:
这里就是初始化环境,SERVLET环境返回的就是StandardServletEnvironment对象,关于配置和环境的操作都基于此类
首先看一下类图:
在这里插入图片描述
我们可以看到StandardServletEnvironment的父类StandardEnvironment以及父父类AbstractEnvironment类,子类的实例化肯定代表着父类也实例化,看一看抽象类AbstractEnvironment的构造器
在这里插入图片描述
在这里插入图片描述
可以看到这里采用的是模板模式,调用的是实例对象的自定义逻辑,添加了web相关servletConfigInitParams和servletContextInitParams:
在这里插入图片描述
在这里插入图片描述
super.customizePropertySources(propertySources)调用父类StandardEnvironment的customizePropertySources方法,添加系统变量systemProperties和系统环境变量systemEnvironment
在这里插入图片描述

总体概括:
在返回return new StandardServletEnvironment()对象的时候,会完成一系列初始化动作,
1.将运行机器的系统变量和环境变量,加入到其父类AbstractEnvironment定义的对象MutablePropertySources中的集合propertySourceList中
2.解析项目中的配置文件
3.执行系统环境初始化完成事件listeners.environmentPrepared(environment);

配置环境configureEnvironment
configureEnvironment(environment, applicationArguments.getSourceArgs());
	protected void configureEnvironment(ConfigurableEnvironment environment,
			String[] args) {
		if (this.addConversionService) {
			ConversionService conversionService = ApplicationConversionService
					.getSharedInstance();
			environment.setConversionService(
					(ConfigurableConversionService) conversionService);
		}
		// 加载启动命令行配置属性
		configurePropertySources(environment, args);
		// 设置active属性
		configureProfiles(environment, args);
	}

configurePropertySources
1.若默认配置不为空,则环境中添加默认配置defaultProperties
2.添加命令行配置信息到环境

	protected void configurePropertySources(ConfigurableEnvironment environment,
			String[] args) {
		// 获取环境中的配置信息
		MutablePropertySources sources = environment.getPropertySources();
		// 若默认配置不为空,则添加默认配置defaultProperties
		if (this.defaultProperties != null && !this.defaultProperties.isEmpty()) {
			sources.addLast(
					new MapPropertySource("defaultProperties", this.defaultProperties));
		}
		// 加载命令行的配置
		if (this.addCommandLineProperties && args.length > 0) {
			String name = CommandLinePropertySource.COMMAND_LINE_PROPERTY_SOURCE_NAME;
			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));
			}
		}
	}

在这里插入图片描述
我们可以看到环境的配置信息是MutablePropertySources对象接收的,这里的信息就是之前new StandardServletEnvironment时,存入environment的

configureProfiles设置active属性

	protected void configureProfiles(ConfigurableEnvironment environment, String[] args) {
		environment.getActiveProfiles(); // ensure they are initialized
		// But these ones should go first (last wins in a property key clash)
		Set<String> profiles = new LinkedHashSet<>(this.additionalProfiles);
		profiles.addAll(Arrays.asList(environment.getActiveProfiles()));
		environment.setActiveProfiles(StringUtils.toStringArray(profiles));
	}

该方法将SpringBootApplication中指定的additionalProfiles文件加载到environment中,一般默认为空。
该变量的用法,在项目启动类中,需要显示创建SpringApplication实例,如下:

		SpringApplication springApplication = new SpringApplication(MyApplication.class);
		//设置profile变量
        springApplication.setAdditionalProfiles("prd");
        springApplication.run(MyApplication.class,args);

事件发布,加载配置文件listeners.environmentPrepared(environment)

通过ApplicationEnvironmentPreparedEvent事件发布,执行对应的监听器事件。监听器事件发布流程之前的文章已这里不作介绍。

在这里插入图片描述

	@Override
	public void environmentPrepared(ConfigurableEnvironment environment) {
		this.initialMulticaster.multicastEvent(new ApplicationEnvironmentPreparedEvent(
				this.application, this.args, environment));
	}
	@Override
	public void multicastEvent(ApplicationEvent event) {
		multicastEvent(event, resolveDefaultEventType(event));
	}

	@Override
	public void multicastEvent(final ApplicationEvent event, @Nullable ResolvableType eventType) {
		ResolvableType type = (eventType != null ? eventType : resolveDefaultEventType(event));
		for (final ApplicationListener<?> listener : getApplicationListeners(event, type)) {
			Executor executor = getTaskExecutor();
			if (executor != null) {
				executor.execute(() -> invokeListener(listener, event));
			}
			else {
				invokeListener(listener, event);
			}
		}
	}

在这里插入图片描述
这里可以看到环境准备过程有7个监听器,意味着要执行7个监听器事件,其中这一环节最重要的就是ConfigFileApplicationListener这个监听器,下面看一看它的执行流程:

	private void onApplicationEnvironmentPreparedEvent(
			ApplicationEnvironmentPreparedEvent event) {
		// 加载spring.factories配置文件中的EnvironmentPostProcessor信息
		// CloudFoundryVcapEnvironmentPostProcessor
		// SpringApplicationJsonEnvironmentPostProcessor
		// SystemEnvironmentPropertySourceEnvironmentPostProcessor
		List<EnvironmentPostProcessor> postProcessors = loadPostProcessors();
		// 添加监听器ConfigFileApplicationListener到postProcessors
		postProcessors.add(this);
		AnnotationAwareOrderComparator.sort(postProcessors);
		// 执行EnvironmentPostProcessor和监听器事件逻辑
		for (EnvironmentPostProcessor postProcessor : postProcessors) {
			postProcessor.postProcessEnvironment(event.getEnvironment(),
					event.getSpringApplication());
		}
	}

在这里插入图片描述
跟进ConfigFileApplicationListener的处理逻辑:

	protected void addPropertySources(ConfigurableEnvironment environment,
			ResourceLoader resourceLoader) {
		RandomValuePropertySource.addToEnvironment(environment);
		new Loader(environment, resourceLoader).load();
	}

这里可以看到一个new Loader的实例化,Loader就是加载配置文件的核心类,他是ConfigFileApplicationListener的内部类,下面看一下核心逻辑:

		public void load() {
			// LIFO队列
			this.profiles = new LinkedList<>();
			// 已经处理的文件
			this.processedProfiles = new LinkedList<>();
			// 已激活文件
			this.activatedProfiles = false;
			this.loaded = new LinkedHashMap<>();
			// 初始化
			initializeProfiles();
			// 解析文件
			while (!this.profiles.isEmpty()) {
				Profile profile = this.profiles.poll();
				// 添加已激活配置文件到环境
				if (profile != null && !profile.isDefaultProfile()) {
					addProfileToEnvironment(profile.getName());
				}
				// 获取默认配置文件路径,循环加载配置文件(不检查是否已经存在)
				load(profile, this::getPositiveProfileFilter,
						addToLoaded(MutablePropertySources::addLast, false));
				this.processedProfiles.add(profile);
			}
			// 重置已经处理的文件
			resetEnvironmentProfiles(this.processedProfiles);
			// 对加载过的配置文件进行排序(排序就会检查是否存在)
			load(null, this::getNegativeProfileFilter,
					addToLoaded(MutablePropertySources::addFirst, true));
			// 添加解析完的配置文件到environment中的集合对象MutablePropertySources中
			addLoadedPropertySources();
		}

初始化initializeProfiles()
1.取得当前环境中激活的配置文件,形如如spring.profiles.active指定的application.yaml文件。如没有则创建一个默认的配置文件,形如application-default.yml、application-default.properties
2.添加一个null的profile,主要用来加载没有指定profile的配置文件,比如:application.properties
因为 profiles 采用了 LIFO 队列,后进先出。所以会先加载profile为null的配置文件,也就是匹配application.properties、application.yml。

		private void initializeProfiles() {
			this.profiles.add(null);
			// 取得已激活配置文件,如spring.profiles.active指定的application.yaml文件
			Set<Profile> activatedViaProperty = getProfilesActivatedViaProperty();
			this.profiles.addAll(getOtherActiveProfiles(activatedViaProperty));
			addActiveProfiles(activatedViaProperty);
			// 如没有已激活配置文件,则新建一个默认的配置文件
			if (this.profiles.size() == 1) { // only has null profile
				for (String defaultProfileName : this.environment.getDefaultProfiles()) {
					Profile defaultProfile = new Profile(defaultProfileName, true);
					this.profiles.add(defaultProfile);
				}
			}
		}
		// 取得已经指定的配置文件,如spring.profiles.active指定的yaml文件
		private Set<Profile> getProfilesActivatedViaProperty() {
			// 环境中没有spring.profiles.active和spring.profiles.include指定的文件,返回空
			if (!this.environment.containsProperty(ACTIVE_PROFILES_PROPERTY)
					&& !this.environment.containsProperty(INCLUDE_PROFILES_PROPERTY)) {
				return Collections.emptySet();
			}
			// 返回active已经激活的配置文件
			Binder binder = Binder.get(this.environment);
			Set<Profile> activeProfiles = new LinkedHashSet<>();
			activeProfiles.addAll(getProfiles(binder, INCLUDE_PROFILES_PROPERTY));
			activeProfiles.addAll(getProfiles(binder, ACTIVE_PROFILES_PROPERTY));
			return activeProfiles;
		}

加载配置路径下的配置文件,如yaml或者properties文件
load(profile, this::getPositiveProfileFilter,addToLoaded(MutablePropertySources::addLast, false));
1.获取配置文件的路径
2.循环遍历路径,解析路径下的文件

		private void load(Profile profile, DocumentFilterFactory filterFactory,
				DocumentConsumer consumer) {
			// 获取默认的配置文件路径
			getSearchLocations().forEach((location) -> {
				boolean isFolder = location.endsWith("/");
				Set<String> names = isFolder ? getSearchNames() : NO_SEARCH_NAMES;
				names.forEach(
						// 根据yaml,properties的不同来解析配置文件
						(name) -> load(location, name, profile, filterFactory, consumer));
			});
		}

在这里插入图片描述

将加载完的yaml或者properties文件放入StandardServletEnvironment中集合对象MutablePropertySources,可以看到有默认的application.properties文件最后加载。

在这里插入图片描述

总结

springboot中准备环境:
1.初始化环境:SERVLET,REACTIVE,NONE
2.配置环境
3.加载配置文件.通过ApplicationEnvironmentPreparedEvent事件发布,执行对应的监听器事件
4.绑定环境到SpringApplication

注意:加载完的配置文件存在MutablePropertySources中,优先级越高的在越前面。如图:
servletConfigInitParams > servletContextInitParams > systemProperties > application.yaml或者application.properties

参考学习博文:https://blog.csdn.net/woshilijiuyi/article/details/82720478

下一篇:Spring boot源码深入学习(五) | 准备环境时application配置文件的加载优先级

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值