亲宝软件园·资讯

展开

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

起风哥 人气:0

在上一篇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总体数据结构体系设计下回分解。

加载全部内容

相关教程
猜你喜欢
用户评论