SpringBoot2 | SpringBoot Environment源码分析(四)

版权声明:本文为博主原创文章,转载请标明出处。


SpringBoot2 | SpringBoot启动流程源码分析(一)

SpringBoot2 | SpringBoot启动流程源码分析(二)

SpringBoot2 | @SpringBootApplication注解 自动化配置流程源码分析(三)

SpringBoot2 | SpringBoot Environment源码分析(四)

SpringBoot2 | 自定义AutoConfiguration | SpringBoot自定义starter(五)


本篇文章主要涉及的是springBoot的配置文件解析,可以理解为是关于application的解析。而springCloud的配置文件解析,则是在此基础上做了扩展。在springBoot解析逻辑之前,添加了bootstrap配置,通过监听器BootstrapApplicationListener实现。后续有详细介绍。

一、概述

Environment是 spring 为运行环境提供的高度抽象接口,项目运行中的所有相关配置都基于此接口。
本篇主要分析springBoot在项目启动时对environment处理。先来看一个简单的应用。

@org.springframework.boot.autoconfigure.SpringBootApplication
@RestController
public class SpringBootApplication {

	@Autowired
	Environment environment;

	@RequestMapping(value = "/environment", method = RequestMethod.GET)
	public String environment() {
		//输出environment 类型
		System.out.println(environment.getClass());
		return JSON.toJSONString(environment);
	}

}

可以猜测上面输出的environment类型是什么呢?


二、源码分析

前面springBoot启动流程中,我们提到了有个prepareEnvironment方法:

ConfigurableEnvironment environment = prepareEnvironment(listeners,
					applicationArguments);
	private ConfigurableEnvironment prepareEnvironment(
			SpringApplicationRunListeners listeners,
			ApplicationArguments applicationArguments) {
		// Create and configure the environment
		//1、初始化environment
		ConfigurableEnvironment environment = getOrCreateEnvironment();
		//2、加载默认配置
		configureEnvironment(environment, applicationArguments.getSourceArgs());
		//3、通知环境监听器,加载项目中的配置文件
		listeners.environmentPrepared(environment);
		bindToSpringApplication(environment);
		if (this.webApplicationType == WebApplicationType.NONE) {
			environment = new EnvironmentConverter(getClassLoader())
					.convertToStandardEnvironmentIfNecessary(environment);
		}
		ConfigurationPropertySources.attach(environment);
		return environment;
	}

接下类对上面三步进行详细分析:

1、初始化environment

	private ConfigurableEnvironment getOrCreateEnvironment() {
		if (this.environment != null) {
			return this.environment;
		}
		//springboot应用返回的environment
		if (this.webApplicationType == WebApplicationType.SERVLET) {
			return new StandardServletEnvironment();
		}
		return new StandardEnvironment();
	}

可以看到根据类型进行匹配 environment,获取到StandardServletEnvironment,这个是整个springboot应用运行环境的实现类,后面所有关于配置和环境的操作都基于此类。看一下该类的结构:
这里写图片描述
首先,environment继承PropertyResolver接口StandardEnvironment接口,也就是同时拥有了配置文件解析和标准环境接口的功能。
重点来看StandardServletEnvironment初始化逻辑:
该类的初始化必定会导致父类方法的初始化:AbstractEnvironment:

	public AbstractEnvironment() {
		//从名字可以看出加载我们的自定义配置文件
		customizePropertySources(this.propertySources);
		if (logger.isDebugEnabled()) {
			logger.debug("Initialized " + getClass().getSimpleName() + " with PropertySources " + this.propertySources);
		}
	}
	@Override
	protected void customizePropertySources(MutablePropertySources propertySources) {
		propertySources.addLast(new StubPropertySource(SERVLET_CONFIG_PROPERTY_SOURCE_NAME));
		propertySources.addLast(new StubPropertySource(SERVLET_CONTEXT_PROPERTY_SOURCE_NAME));
		if (JndiLocatorDelegate.isDefaultJndiEnvironmentAvailable()) {
			propertySources.addLast(new JndiPropertySource(JNDI_PROPERTY_SOURCE_NAME));
		}
		super.customizePropertySources(propertySources);
	}

因为该配置类是基于web环境,所以先加载和 servlet有关的参数,addLast放在最后:

	/** System environment property source name: {@value} */
	public static final String SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME = "systemEnvironment";

	/** JVM system properties property source name: {@value} */
	public static final String SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME = "systemProperties";

该类又是对StandardEnvironment的扩展,这里会调用super.customizePropertySources(propertySources);

	@Override
	protected void customizePropertySources(MutablePropertySources propertySources) {
		propertySources.addLast(new MapPropertySource(SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME, getSystemProperties()));
		propertySources.addLast(new SystemEnvironmentPropertySource(SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME, getSystemEnvironment()));
	}
	/** System environment property source name: {@value} */
	public static final String SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME = "systemEnvironment";

	/** JVM system properties property source name: {@value} */
	public static final String SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME = "systemProperties";

可以看到放入顺序是永远放在最后面,也就是先加入的在前面。systemEnvironment是在systemProperties前面,这点很重要。因为前面的配置会覆盖后面的配置,也就是说系统变量中的配置比系统环境变量中的配置优先级更高。如下:
这里写图片描述


2)加载默认配置:

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

这里接收的参数是ConfigurableEnvironment,也就是StandardServletEnvironment的父类。
继续跟进configurePropertySources方法:

	protected void configurePropertySources(ConfigurableEnvironment environment,
			String[] args) {
		//获取配置存储集合
		MutablePropertySources sources = environment.getPropertySources();
		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

public class MutablePropertySources implements PropertySources {

	private final Log logger;

	private final List<PropertySource<?>> propertySourceList = new CopyOnWriteArrayList<>();
}

而该类又是如何使用的呢?

这里写图片描述
这里的设计很巧妙,将配置文件集合传递到文件解析器中,而AbstractEnvironment又实现了文件解析接口ConfigurablePropertyResolver,所以AbstractEnvironment就有了文件解析的功能。所以StandardServletEnvironment文件解析功能实际委托给了PropertySourcesPropertyResolver来实现


3、通知环境监听器,加载项目中的配置文件

触发监听器:

listeners.environmentPrepared(environment);

SpringBoot2 | SpringBoot启动流程源码分析(一)中提到了该方法通知的监听器,和配置文件有关的监听器类型为ConfigFileApplicationListener,监听到事件时执行的方法:

	@Override
	public void postProcessEnvironment(ConfigurableEnvironment environment,
			SpringApplication application) {
		//加载项目中的配置文件
		addPropertySources(environment, application.getResourceLoader());
		configureIgnoreBeanInfo(environment);
		bindToSpringApplication(environment, application);
	}

继续跟进去,会发现一个核心内部类 Loader ,配置文件加载也就委托给该内部类来处理:

private class Loader {

		private final Log logger = ConfigFileApplicationListener.this.logger;
		//当前环境
		private final ConfigurableEnvironment environment;
		//类加载器,可以在项目启动时通过 SpringApplication 构造方法指定,默认采用 Launcher.AppClassLoader加载器
		private final ResourceLoader resourceLoader;
		//资源加载工具类
		private final List<PropertySourceLoader> propertySourceLoaders;
		//LIFO队列
		private Queue<String> profiles;
		//已处理过的文件
		private List<String> processedProfiles;
		private boolean activatedProfiles;

		Loader(ConfigurableEnvironment environment, ResourceLoader resourceLoader) {
			this.environment = environment;
			//获取类加载器
			this.resourceLoader = resourceLoader == null ? new DefaultResourceLoader()
					: resourceLoader;
			//获取propertySourceLoaders
			this.propertySourceLoaders = SpringFactoriesLoader.loadFactories(
					PropertySourceLoader.class, getClass().getClassLoader());
		}
		//......
}		

上面propertySourceLoaders通过 SpringFactoriesLoader 获取当前项目中类型为 PropertySourceLoader 的所有实现类,默认有两个实现类,如下图:
这里写图片描述

继续来看主要解析方法:load()

		public void load() {
			this.profiles = Collections.asLifoQueue(new LinkedList<Profile>());
			this.processedProfiles = new LinkedList<>();
			this.activatedProfiles = false;
			this.loaded = new LinkedHashMap<>();
			//初始化逻辑
			initializeProfiles();
			while (!this.profiles.isEmpty()) {
				Profile profile = this.profiles.poll();
				load(profile, this::getPositiveProfileFilter,
						addToLoaded(MutablePropertySources::addLast, false));
				this.processedProfiles.add(profile);
			}
			load(null, this::getNegativeProfileFilter,
					addToLoaded(MutablePropertySources::addFirst, true));
			addLoadedPropertySources();
		}

跟进去上面初始化方法:

	private void initializeProfiles() {
			Set<Profile> initialActiveProfiles = initializeActiveProfiles();
			this.profiles.addAll(getUnprocessedActiveProfiles(initialActiveProfiles));
			//如果为空,添加默认的profile
			if (this.profiles.isEmpty()) {
				for (String defaultProfileName : this.environment.getDefaultProfiles()) {
					Profile defaultProfile = new Profile(defaultProfileName, true);
					if (!this.profiles.contains(defaultProfile)) {
						this.profiles.add(defaultProfile);
					}
				}
			}
			// The default profile for these purposes is represented as null. We add it
			// last so that it is first out of the queue (active profiles will then
			// override any settings in the defaults when the list is reversed later).
			//这里添加一个为null的profile,主要是加载默认的配置文件
			this.profiles.add(null);
		}

上面主要做了两件事情:
1)判断是否指定了profile,如果没有,添加默认环境:default
2)添加一个null的profile,主要用来加载默认的配置文件,比如:application.properties
因为 profiles 采用了 LIFO 队列,所以会先加载profile为null的配置文件

先梳理一下解析流程:
1)获取默认的配置文件路径,有4种。
2)遍历所有的路径,拼装配置文件名称。
3)再遍历解析器,选择yml或者properties解析,将解析结果添加到集合MutablePropertySources当中。

以下是加载逻辑:

	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()方法:
这里写图片描述

获取路径之后,会拼接配置文件名称,选择合适解析器进行解析:
(name) -> load(location, name, profile, filterFactory, consumer)
比较简单,这里不再赘述。


猜你喜欢

转载自blog.csdn.net/woshilijiuyi/article/details/82720478