SpringBoot配置的加载流程详细分析

在上一篇Springboot启动流程解析中我们没有张开对配置的解析,因为篇幅过大,需要单独在起一篇文章来讲。

且看一下两行代码:

ApplicationArguments applicationArguments = new DefaultApplicationArguments(
					args);
			ConfigurableEnvironment environment = prepareEnvironment(listeners,
					applicationArguments);

第一行代码是对控制台入参做了解析顺着代码跟进去我们发现

先调用SimpleCommandLineArgsParser来解析对应的控制台入参,解析完之后在丢给父类进行处理

public SimpleCommandLinePropertySource(String... args) {
		super(new SimpleCommandLineArgsParser().parse(args));
	}

所以接下来我们看这个parse方法:

public CommandLineArgs parse(String... args) {
		//构建个缓存,将解析的参数分别放入两个容器中,分为两种类型的熟悉选项参数和非选项参数,选项参数使用 --开头
		CommandLineArgs commandLineArgs = new CommandLineArgs();
		for (String arg : args) {
			if (arg.startsWith("--")) {
				String optionText = arg.substring(2, arg.length());
				String optionName;
				String optionValue = null;
				if (optionText.contains("=")) {
					optionName = optionText.substring(0, optionText.indexOf('='));
					optionValue = optionText.substring(optionText.indexOf('=')+1, optionText.length());
				}
				else {
					optionName = optionText;
				}
				if (optionName.isEmpty() || (optionValue != null && optionValue.isEmpty())) {
					throw new IllegalArgumentException("Invalid argument syntax: " + arg);
				}
				commandLineArgs.addOptionArg(optionName, optionValue);
			}
			else {
				commandLineArgs.addNonOptionArg(arg);
			}
		}
		return commandLineArgs;
	}

以上代码就做了件很简单的事情,将遍历所有的入参,然后判断key是否包含"–“如果包含丢到OptionArg 中 ,不包含就丢到NonOptionArg中,并且将commandLineArgs返回。就做了个分类动作含”–"的写法标准注解也给出来了 ,必须按下面这种写法

--foo
--foo=bar
--foo="bar then baz"
--foo=bar,baz,biz

好此时我们已经获得了一个分好类的参数对象,丢给父类在加工,发现父类又丢给了父类,但是我们发现 父类已经是一个PropertySource的子类,所以最后这里被封装成了一个PropertySource对象,实际上就是把返回的commandLineArgs对象缓存起来,最终通过提供的抽象方法,可以获取到对应的属性。

public CommandLinePropertySource(T source) {
		super(COMMAND_LINE_PROPERTY_SOURCE_NAME, source);
	}

所以我们回过头看SimpleCommandLinePropertySource 和DefaultApplicationArguments即可。从简单的代码上我们可以很容易看出来,无非最后就是从两个集合当中取key value,所以直接把它当做一个map就好了,代码也不贴了。

接着看第二行代码,这个才是我们的主菜

发现没有,写代码的层次结构,思维思想,都是一个模式

一个复杂的过程就是 先prepare -->init -->createA–>creatB–>complete.优秀的人写代码就跟写文章一样。一看就很好懂得那种。

private ConfigurableEnvironment prepareEnvironment(
			SpringApplicationRunListeners listeners,
			ApplicationArguments applicationArguments) {
		//进来第一步先声明一个可配置的环境变量容器
		ConfigurableEnvironment environment = getOrCreateEnvironment();
		//然后配置它
		configureEnvironment(environment, applicationArguments.getSourceArgs());
		//然后发布给事件出去告诉所有listener 环境配置完成
		listeners.environmentPrepared(environment);
		//把环境绑定给springboot
		bindToSpringApplication(environment);
		if (!this.isCustomEnvironment) {
			environment = new EnvironmentConverter(getClassLoader())
					.convertEnvironmentIfNecessary(environment, deduceEnvironmentClass());
		}
		ConfigurationPropertySources.attach(environment);
		return environment;
	}

针对以上代码我们接着一行行展开创建,这里就是根据不同容器创建不同的对象。

	private ConfigurableEnvironment getOrCreateEnvironment() {
		if (this.environment != null) {
			return this.environment;
		}
		switch (this.webApplicationType) {
		case SERVLET:
			return new StandardServletEnvironment();
		case REACTIVE:
			return new StandardReactiveWebEnvironment();
		default:
			return new StandardEnvironment();
		}
	}

根据Environment的继承关系我们不难看出在对象创建时就调用了customizePropertySources(this.propertySources);方法

此时也就是往容器中初始化了两个对象 一个时 system一个时env,这两个变量的参数就是系统和jvm级别的变量对象

@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()));
	}

而传入的MutablePropertySources 直接由父类抽象类直接new出来的,通过查看这个类我们可很清楚的知道它也是一个数据存储结构,缓存了一个Propertysource的列表。剩下的就是对它的增删改等处理操作。

所以我们来看这一行

configureEnvironment(environment,applicationArguments.getSourceArgs());

在这段代码中放了个类型转换服务,这个类型转换服务,也是一整套的体系,内置了各种各样的类型转换,比如你在配置文件写了个 时间 100ms 它到底是怎么被识别成100毫秒的,都是通过这个类型转换服务转换的。有兴趣可以自行拓展开

protected void configureEnvironment(ConfigurableEnvironment environment,
			String[] args) {
		if (this.addConversionService) {
			ConversionService conversionService = ApplicationConversionService
					.getSharedInstance();
			environment.setConversionService(
					(ConfigurableConversionService) conversionService);
		}
		configurePropertySources(environment, args);
		configureProfiles(environment, args);
	}

接着往里走

configurePropertySources(environment, args);

这里代码还是将拿到上面配置的缓存往里面在塞propertysource对象

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));
			}
		}
	}

从代码可以看出,获取了一个defaultProperties 的map把它也加入到list中。而这个map也是在main函数进行设置的,而这个属性是出了系统属性的之外最早加载的propertysource对象

public static void main(String[] args) {
        SpringApplicationBuilder builder = new SpringApplicationBuilder();
        builder.properties(map);
        builder.run(Application.class,args);
    }

然后我们回过头来看configureProfiles方法,此方法等以上配置完成之后,先从配置中抽取出profiles 并将其作为单独的属性设置回去。抽取规则看如下代码

	protected Set<String> doGetActiveProfiles() {
		synchronized (this.activeProfiles) {
			if (this.activeProfiles.isEmpty()) {
				String profiles = getProperty(ACTIVE_PROFILES_PROPERTY_NAME);
				if (StringUtils.hasText(profiles)) {
					setActiveProfiles(StringUtils.commaDelimitedListToStringArray(
							StringUtils.trimAllWhitespace(profiles)));
				}
			}
			return this.activeProfiles;
		}
	}

最终所有的getProperty都走到如下代码,而这段代码也很简单就是遍历所有的propertysource ,如果取到则终止,也就给我们营造了一个假象,就是同一个配置被覆盖的假象。不是真真的被覆盖,而是放在不同的propertysource中,并且propertysource有顺序而已。

protected <T> T getProperty(String key, Class<T> targetValueType, boolean resolveNestedPlaceholders) {
		if (this.propertySources != null) {
			for (PropertySource<?> propertySource : this.propertySources) {
				if (logger.isTraceEnabled()) {
					logger.trace("Searching for key '" + key + "' in PropertySource '" +
							propertySource.getName() + "'");
				}
				Object value = propertySource.getProperty(key);
				if (value != null) {
					if (resolveNestedPlaceholders && value instanceof String) {
						value = resolveNestedPlaceholders((String) value);
					}
					logKeyFound(key, propertySource, value);
					return convertValueIfNecessary(value, targetValueType);
				}
			}
		}
		if (logger.isTraceEnabled()) {
			logger.trace("Could not find key '" + key + "' in any property source");
		}
		return null;
	}

通过以上的代码分析我们可以知道一件事情放在越底层的propertysource 会被上层的覆盖,通过巧妙的利用这一点,我们就以通过不同入参方式进行不同环境的变量覆盖,比如在项目中配置了配置中心为 测试环境,发布到生产是不是可以使用环境变量放在它的上层,就达到覆盖效果。而不用在打包的时候去改配置。

关于propertysource总体数据结构体系设计下回分解。

到此这篇关于SpringBoot配置的加载流程详细分析的文章就介绍到这了,更多相关SpringBoot配置加载过程内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • SpringBoot配置加载,各配置文件优先级对比方式

    目录 1.SpringBoot配置文件 以设置应用端口为例 2.配置文件目录 3.自定义配置属性 自定义配置提示 4.指定配置文件 @PropertySource使用 装配yaml配置文件 @ImportResource使用 其他问题 idea使用*.properties文件出现中文乱码问题? 解决方法: 1.SpringBoot配置文件 SpringBoot使用一个以application命名的配置文件作为默认的全局配置文件.支持properties后缀结尾的配置文件或者以yml/yaml后缀

  • SpringBoot配置的加载流程详细分析

    在上一篇Springboot启动流程解析中我们没有张开对配置的解析,因为篇幅过大,需要单独在起一篇文章来讲. 且看一下两行代码: ApplicationArguments applicationArguments = new DefaultApplicationArguments( args); ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments); 第一行代码是对控制台

  • 详细分析Fresco源码之图片加载流程

    一.概述 Fresco 是一个强大的图片加载组件.使用它之后,你不需要再去关心图片的加载和显示这些繁琐的事情! 支持 Android 2.3 及以后的版本.如果需要了解 Fresco 的使用可以访问 Fresco 使用文档. Fresco是一个功能完善的图片加载框架,在Android开发中有着广泛的应用,那么它作为一个图片加载框架,有哪些特色让它备受推崇呢? 完善的内存管理功能,减少图片对内存的占用,即便在低端机器上也有着不错的表现. 自定义图片加载的过程,可以先显示低清晰度图片或者缩略图,加载

  • SpringBoot配置文件加载方法详细讲解

    目录 配置文件的读取顺序 多坏境的配置文件 个性化配置 自定义配置文件名称和路径 加载yml文件 配置文件的读取顺序 根目录/config/application.properties 根目录/config/application.yml 根目录/application.properties 根目录/application.yml classpath目录/config/application.properties classpath目录/config/application.yml classp

  • SpringBoot bean查询加载顺序流程详解

    目录 背景 探索-源码 进一步思考 背景 SpringBoot bean 加载顺序如何查看,想看加载了哪些bean, 这些bean的加载顺序是什么? 实际加载顺序不受控制,但会有一些大的原则: 1.按照字母顺序加载(同一文件夹下按照字母数序:不同文件夹下,先按照文件夹命名的字母顺序加载)2.不同的bean声明方式不同的加载时机,顺序总结:@ComponentScan > @Import > @Bean   这里的ComponentScan指@ComponentScan及其子注解,Bean指的是

  • 浅谈SpringBoot资源初始化加载的几种方式

    目录 一.问题 二.资源初始化 一.问题 在平时的业务模块开发过程中,难免会需要做一些全局的任务.缓存.线程等等的初始化工作,那么如何解决这个问题呢?方法有多种,但具体又要怎么选择呢? 二.资源初始化 1.既然要做资源的初始化,那么就需要了解一下springboot启动过程(这里大体说下启动过程,详细:https://www.jb51.net/article/133648.htm) 按照前面的分析,Spring-boot容器启动流程总体可划分为2部分: 执行注解:扫描指定范围下的bean.载入自

  • SpringIOC BeanDefinition的加载流程详解

    目录 一.前言 二. BeanDefinition 的体系 2.1 体系概览 2.2 BeanDefinition 的作用 三. BeanDefinition 的载入 3.1 载入的入口 3.2 保存的逻辑 3.3 使用的方式 总结 一.前言 这一篇来看看 SpringIOC 里面的一个细节点 , 来简单看看 BeanDefinition 这个对象 , 以及有没有办法对其进行定制. CASE 备份 :  gitee.com/antblack/ca… 二. BeanDefinition 的体系 2

  • SpringBoot的reload加载器的方法

    背景 springboot越来越多的被大家所使用SpringBoot DevTool实现热部署 出现了相同类castException 分析 首先确定出现相同类的castException比如是由于classloader不同造成的. 一个class是否相同取决于两个因素 classloader相同 class文件相同 即不同classloader解释出来的class是不同的class 我们在学习jdbc的时候常见的使用 /** * Returns the {@code Class} object

  • springboot配置文件的加载顺序解析

    这篇文章主要介绍了springboot配置文件的加载顺序解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 springboot启动时会扫描一下位置的application.properties或者application.yml文件作为默认配置文件: file:./config/ file:./ classpath:/config/ classpath:/ 以上是按照优先级从高到低的顺序,所有位置的文件都会被加载,高优先级配置会覆盖低优先级配置

  • Mybatis对mapper的加载流程深入讲解

    今天来分析Configuration初始化的最后一部分mapper的加载.​ 加载方法mapperElement XMLConfigBuilder配置Configuration的parseConfiguration方法还剩最后一行解析代码:mapperElement(root.evalNode("mappers")); mapperElement方法源码与详解如下图: 从源码可以得出一些结论: mappers节点支持mapper和package两种类型子节点: package子节点只需

  • Flutter系统网络图片加载流程解析

    目录 一.从构造函数开始 二.图片下载入口 2.1.ScrollAwareImageProvider 2.2.ImageConfiguration 2.3.ImageStream 三.图片流和Key 四.根据key来处理图片流 4.1.ImageCache 4.2. load 五.图片下载 六.添加观察者实现界面更新 总结 Flutter原生支持在Image组件上显示网络图片,最简单的使用方式如下,调用Image的命名构造方法Image.network即可实现网络图片的下载显示. Widget

随机推荐