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总体数据结构体系设计下回分解。
加载全部内容