文章详情

短信预约-IT技能 免费直播动态提醒

请输入下面的图形验证码

提交验证

短信预约提醒成功

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

2023-01-06 12:03

关注

在上一篇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配置加载过程内容请搜索编程网以前的文章或继续浏览下面的相关文章希望大家以后多多支持编程网!

阅读原文内容投诉

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

软考中级精品资料免费领

  • 历年真题答案解析
  • 备考技巧名师总结
  • 高频考点精准押题
  • 2024年上半年信息系统项目管理师第二批次真题及答案解析(完整版)

    难度     801人已做
    查看
  • 【考后总结】2024年5月26日信息系统项目管理师第2批次考情分析

    难度     348人已做
    查看
  • 【考后总结】2024年5月25日信息系统项目管理师第1批次考情分析

    难度     311人已做
    查看
  • 2024年上半年软考高项第一、二批次真题考点汇总(完整版)

    难度     432人已做
    查看
  • 2024年上半年系统架构设计师考试综合知识真题

    难度     220人已做
    查看

相关文章

发现更多好内容

猜你喜欢

AI推送时光机
位置:首页-资讯-后端开发
咦!没有更多了?去看看其它编程学习网 内容吧
首页课程
资料下载
问答资讯