springboot源码解析(四)

@TOC

一、综述

上一节分析了环境上下文准备整体逻辑逻辑,留下来了一个点就是SpringApplication发布ApplicationEnvironmentPreparedEvent事件,相关监听器收到事件执行响应,其中有一个监听器执行的逻辑很复杂,也很重要,它就是ConfigFileApplicationListener。
ConfigFileApplicationListener是我们springboot应用初始化外部配置然后加载到环境上下文对象里面来的核心逻辑。包括有名的appollo都是通过这个listener加载配置到应用中来的。

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

onApplicationEvent这个方法就是监听器执行逻辑的入口,由于这里是ApplicationEnvironmentPreparedEvent,所以进入到这个分支:

private void onApplicationEnvironmentPreparedEvent(
			ApplicationEnvironmentPreparedEvent event) {
			//加载所有的postProcessors
		List<EnvironmentPostProcessor> postProcessors = loadPostProcessors();
		postProcessors.add(this);
		AnnotationAwareOrderComparator.sort(postProcessors);
		//循环执行每一个processor的逻辑
		for (EnvironmentPostProcessor postProcessor : postProcessors) {
			postProcessor.postProcessEnvironment(event.getEnvironment(),
					event.getSpringApplication());
		}
	}

1、加载processors

	List<EnvironmentPostProcessor> loadPostProcessors() {
		return SpringFactoriesLoader.loadFactories(EnvironmentPostProcessor.class,
				getClass().getClassLoader());
	}

这个代码已经很熟悉了都分析了很多次了,这里简单说一下,它就是通过SpringFactoriesLoader把项目里面所有spring.factories文件中的实现了EnvironmentPostProcessor接口的类加载进jvm并实例化,然后返回

2、循环执行每一个processor的逻辑

系统里面加载了多少processor呢?我通过debug打一个断点来看看就知道了:

在这里插入图片描述
一般来说有四个processor,如果依赖了appollo,就还应该有一个processor,它就是ApolloApplicationContextInitializer,这里我就不解析apollo的这个processor了,有兴趣的朋友可以自己去阅读相关源码,思想都类似。

1、SystemEnvironmentPropertySourceEnvironmentPostProcessor

二、SystemEnvironmentPropertySourceEnvironmentPostProcessor逻辑分析

	public void postProcessEnvironment(ConfigurableEnvironment environment,
			SpringApplication application) {
			// 这个sourceName的值是‘systemEnvironment’
		String sourceName = StandardEnvironment.SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME;
		//这个PropertySource上节中分析过,根据名称‘systemEnvironment’ 获取对应的PropertySource
		PropertySource<?> propertySource = environment.getPropertySources()
				.get(sourceName);
		if (propertySource != null) {
		//替换原来的值
			replacePropertySource(environment, sourceName, propertySource);
		}
	}

这个方法到底做了什么事情?

先进入replacePropertySource方法,只要搞清楚了它做了什么,几乎就可以了解这个方法到底要做什么了

private void replacePropertySource(ConfigurableEnvironment environment,
			String sourceName, PropertySource<?> propertySource) {
			// 获取source(source是一个泛型,这里是一个map)
		Map<String, Object> originalSource = (Map<String, Object>) propertySource
				.getSource();
				// 从新包装之前的source
		SystemEnvironmentPropertySource source = new OriginAwareSystemEnvironmentPropertySource(
				sourceName, originalSource);

//用包装后的PropertySource把原来环境上下文中名称为'systemEnvironment'的PropertySource替换掉
		environment.getPropertySources().replace(sourceName, source);
	}

其实这个从新包装,没有做任何其他事情,就是用了一个新的功能更强大的子类来包装了原来的数据,如果大家有兴趣可以去读源码,我这里就不在赘述。
至此,SystemEnvironmentPropertySourceEnvironmentPostProcessor的所有逻辑就分析完了。

三、SpringApplicationJsonEnvironmentPostProcessor源码解析

	public void postProcessEnvironment(ConfigurableEnvironment environment,
			SpringApplication application) {
		MutablePropertySources propertySources = environment.getPropertySources();
		StreamSupport.stream(propertySources.spliterator(), false)
				.map(JsonPropertyValue::get).filter(Objects::nonNull).findFirst()
				.ifPresent((v) -> processJson(environment, v));
	}

这个方法只有两行代码,核心逻辑在第二行的processJson方法里面。

private void processJson(ConfigurableEnvironment environment,
			JsonPropertyValue propertyValue) {
		try {
			JsonParser parser = JsonParserFactory.getJsonParser();
			Map<String, Object> map = parser.parseMap(propertyValue.getJson());
			if (!map.isEmpty()) {
				addJsonPropertySource(environment,
						new JsonPropertySource(propertyValue, flatten(map)));
			}
		}
		catch (Exception ex) {
			logger.warn("Cannot parse JSON for spring.application.json: "
					+ propertyValue.getJson(), ex);
		}
	}
    这个方法的代码逻辑都很简单,就是把配置中json格式的字符串解析后放入到ConfigurableEnvironment。注意其中有行代码是把json解析成了map封装到了JsonPropertySource对象里面,这个对象里面有很多类似于map的方法,可以直接根据key获取到对应的值。

总结起来说就是 SpringApplicationJsonEnvironmentPostProcessor就是把我们配置的spring.application.json属性的值通过json解析放到了ConfigurableEnvironment对象里面,后续可以很方便的通过json字段直接获取到字段对应的值。
至此,SpringApplicationJsonEnvironmentPostProcessor的源码解析就结束了。

四、ConfigFileApplicationListener源码解析

   第三个processor由于我们很少用到,我这儿就不做源码分析,而且它的源码也很简单,大家可以自行阅读,直接解析ConfigFileApplicationListener。
   ConfigFileApplicationListener就是监听器本身,它既实现了listener接口,又实现了processor接口。
@Override
	public void postProcessEnvironment(ConfigurableEnvironment environment,
			SpringApplication application) {
		addPropertySources(environment, application.getResourceLoader());
	}
protected void addPropertySources(ConfigurableEnvironment environment,
			ResourceLoader resourceLoader) {
//添加随机属性,在生产开发中根本用不到,就不详细分析了	RandomValuePropertySource.addToEnvironment(environment);
//加载配置文件中的属性
		new Loader(environment, resourceLoader).load();
	}

直接进入到load方法

		public void load() {
			this.profiles = new LinkedList<>();
			this.processedProfiles = new LinkedList<>();
			this.activatedProfiles = false;
			this.loaded = new LinkedHashMap<>();
			// 初始化profiles
			initializeProfiles();
			while (!this.profiles.isEmpty()) {
				Profile profile = this.profiles.poll();
				if (profile != null && !profile.isDefaultProfile()) {
				//把profile加入到环境中	addProfileToEnvironment(profile.getName());
				}
				// 根据profile加载配置
				load(profile, this::getPositiveProfileFilter,
						addToLoaded(MutablePropertySources::addLast, false));
				this.processedProfiles.add(profile);
			}
//从新设置环境的profile
		resetEnvironmentProfiles(this.processedProfiles);
		//加载null profile的属性
			load(null, this::getNegativeProfileFilter,
					//把加载过的属性都添加到列表中
					addToLoaded(MutablePropertySources::addFirst, true));
			addLoadedPropertySources();
		}

这个方法主要做的事情其实就是,根据你配置的profile,加载环境下所有符合条件的属性和属性值,当然这里面也包含空profile,空profile相当于application.properties,所以我们进入这个load方法

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(
						(name) -> load(location, name, profile, filterFactory, consumer));
			});
		}

先进入getSearchLocations()这个方法看看到底是得到了什么集合

	private Set<String> getSearchLocations() {
	// 是否存在‘spring.config.location’属性值
			if (this.environment.containsProperty(CONFIG_LOCATION_PROPERTY)) {
				return getSearchLocations(CONFIG_LOCATION_PROPERTY);
			}
			Set<String> locations = getSearchLocations(
					CONFIG_ADDITIONAL_LOCATION_PROPERTY);
			locations.addAll(
					asResolvedSet(ConfigFileApplicationListener.this.searchLocations,
							DEFAULT_SEARCH_LOCATIONS));
			return locations;
		}

	// Note the order is from least to most specific (last one wins)
	private static final String DEFAULT_SEARCH_LOCATIONS = "classpath:/,classpath:/config/,file:./,file:./config/";
/**
	 * The "config additional location" property name.
	 */
	public static final String CONFIG_ADDITIONAL_LOCATION_PROPERTY = "spring.config.additional-location";

这个方法大致要做的事情就是:

1、如果环境中配置了‘spring.config.location’的值,就到这个配置值所对应的目录去找配置文件
2、如果不存在这个配置,就再找到‘spring.config.additional-location’这个配置的值所对应目录找配置文件
3、把‘classpath:/,classpath:/config/,file:./,file:./config/’这些目录下的对应配置文件路径加入到locations中返回。

所以综上,就是在所有符合条件的路径下找到所有的文件,然后用于循环匹配符合条件的文件。这个匹配逻辑和文件加载逻辑就在循环体内,我们只要搞清楚匹配规则和加载逻辑,加载顺序。这个配置文件解析的逻辑就全部搞清楚了

1、匹配逻辑

在这里插入图片描述
很显然,这里的所有locations都应该是文件夹,所以都会进入到getSearchNames()这个方法:

	private Set<String> getSearchNames() {
	//在环境中找'spring.config.name'属性的值
			if (this.environment.containsProperty(CONFIG_NAME_PROPERTY)) {
				String property = this.environment.getProperty(CONFIG_NAME_PROPERTY);
				return asResolvedSet(property, null);
			}
			return asResolvedSet(ConfigFileApplicationListener.this.names, DEFAULT_NAMES);
		}

一般来说我们都没有配置‘spring.config.name’这个属性,所以会走默认配置文件名的逻辑:
在这里插入图片描述
所以springboot为啥会默认找名字为application的配置文件了吧?

2、加载配置文件

进入到load(location, name, profile, filterFactory, consumer));

		private void load(String location, String name, Profile profile,
				DocumentFilterFactory filterFactory, DocumentConsumer consumer) {
				//name肯定有值,不走这个分支
			if (!StringUtils.hasText(name)) {
				for (PropertySourceLoader loader : this.propertySourceLoaders) {
					if (canLoadFileExtension(loader, location)) {
						load(loader, location, profile,
								filterFactory.getDocumentFilter(profile), consumer);
						return;
					}
				}
			}
			Set<String> processed = new HashSet<>();
			//不同的SourceLoader加载不同文件结尾的文件,这里有两个sourceLoader,一个加载properties,一个加载yaml文件,下面我有对应的截图
			for (PropertySourceLoader loader : this.propertySourceLoaders) {
				for (String fileExtension : loader.getFileExtensions()) {
					if (processed.add(fileExtension)) {
						loadForFileExtension(loader, location + name, "." + fileExtension,
								profile, filterFactory, consumer);
					}
				}
			}
		}

在这里插入图片描述
进入到loadForFileExtension(loader, location + name, "." + fileExtension, profile, filterFactory, consumer);
这个方法看个究竟吧:

		private void loadForFileExtension(PropertySourceLoader loader, String prefix,
				String fileExtension, Profile profile,
				DocumentFilterFactory filterFactory, DocumentConsumer consumer) {
			DocumentFilter defaultFilter = filterFactory.getDocumentFilter(null);
			DocumentFilter profileFilter = filterFactory.getDocumentFilter(profile);
			if (profile != null) {
				// Try profile-specific file & profile section in profile file (gh-340)
				String profileSpecificFile = prefix + "-" + profile + fileExtension;
				load(loader, profileSpecificFile, profile, defaultFilter, consumer);
				load(loader, profileSpecificFile, profile, profileFilter, consumer);
				// Try profile specific sections in files we've already processed
				for (Profile processedProfile : this.processedProfiles) {
					if (processedProfile != null) {
						String previouslyLoaded = prefix + "-" + processedProfile
								+ fileExtension;
						load(loader, previouslyLoaded, profile, profileFilter, consumer);
					}
				}
			}
			// Also try the profile-specific section (if any) of the normal file
			load(loader, prefix + fileExtension, profile, profileFilter, consumer);
		}

在这里插入图片描述
上图是我debug调试的时候的截图,大家看明白了吗?

如果profile不为空,那么程序会加载我们的配置文件名是这样拼接出来的。

后面就是通过resource把文件通过流读入内存加载到环境上下文中,后面的逻辑就不再解析了。
至此,ConfigFileApplicationListener的processor逻辑就解析完成了

总结

springboot发布ApplicationEnvironmentPreparedEvent事件后,对应的所有监听器处理的逻辑非常重要,是springboot配置环境上下文环境配置变量的关键。它主要做了以下事情:

1、SystemEnvironmentPropertySourceEnvironmentPostProcessor把系统属性用一个新的类包装了以下然后替换了原来的属性
2、SpringApplicationJsonEnvironmentPostProcessor就是把我们配置的spring.application.json属性的值通过json解析放到了ConfigurableEnvironment对象里面,后续可以很方便的通过json字段直接获取到字段对应的值
3、ConfigFileApplicationListener就是把系统中的配置文件的属性值全部读取到ConfigurableEnvironment对象中,以便后续好使用

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值