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

 更新时间:2023年01月06日 11:48:21   作者:起风哥  
了解内部原理是为了帮助我们做扩展,同时也是验证了一个人的学习能力,如果你想让自己的职业道路更上一层楼,这些底层的东西你是必须要会的,这篇文章主要介绍了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配置加载过程内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • 最有价值的50道java面试题 适用于准入职Java程序员

    最有价值的50道java面试题 适用于准入职Java程序员

    这篇文章主要为大家分享了最有价值的50道java面试题,涵盖内容全面,包括数据结构和算法相关的题目、经典面试编程题等,对hashCode方法的设计、垃圾收集的堆和代进行剖析,感兴趣的小伙伴们可以参考一下
    2016-05-05
  • Spring Data JPA 设置字段默认值方式

    Spring Data JPA 设置字段默认值方式

    这篇文章主要介绍了Spring Data JPA设置字段默认值方式,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2021-11-11
  • SpringBoot使用Mybatis注解实现分页动态sql开发教程

    SpringBoot使用Mybatis注解实现分页动态sql开发教程

    这篇文章主要为大家介绍了SpringBoot使用Mybatis注解实现分页及动态sql开发教程,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2022-03-03
  • 一次排查@CacheEvict注解失效的经历及解决

    一次排查@CacheEvict注解失效的经历及解决

    这篇文章主要介绍了一次排查@CacheEvict注解失效的经历及解决方案,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2021-12-12
  • 深入浅析drools中Fact的equality modes

    深入浅析drools中Fact的equality modes

    这篇文章主要介绍了drools中Fact的equality modes的相关知识,本文通过图文实例代码相结合给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2022-05-05
  • java ArrayBlockingQueue的方法及缺点分析

    java ArrayBlockingQueue的方法及缺点分析

    在本篇内容里小编给大家整理的是一篇关于java ArrayBlockingQueue的方法及缺点分析,对此有兴趣的朋友们可以跟着学习下。
    2021-01-01
  • Gitlab CI-CD自动化部署SpringBoot项目的方法步骤

    Gitlab CI-CD自动化部署SpringBoot项目的方法步骤

    本文主要记录如何通过Gitlab CI/CD自动部署SpringBoot项目jar包。文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2021-07-07
  • Java中可以实现负载均衡的算法详解

    Java中可以实现负载均衡的算法详解

    这篇文章主要介绍了Java中可以实现负载均衡的算法详解,在Java中,有多种算法可以实现负载均衡,下面是两个常见的算法示例,随机算法和轮询算法,需要的朋友可以参考下
    2023-08-08
  • idea创建spring boot工程及配置文件(最新推荐)

    idea创建spring boot工程及配置文件(最新推荐)

    本文给大家介绍idea创建spring boot工程及配置文件,本文通过图文并茂的形式给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友参考下吧
    2023-11-11
  • Spring JPA之find拓展方法示例详解

    Spring JPA之find拓展方法示例详解

    这篇文章主要为大家介绍了Spring JPA之find拓展方法示例详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2023-04-04

最新评论