spring5.0.5初始化源码学习三部曲之二:setConfigLocations方法

本章是学习spring5.0.5初始化源码的第二章,前一章《spring5.0.5初始化源码三部曲之一:AbstractApplicationContext构造方法》对AbstractApplicationContext的初始化做了分析,本章我们聚焦ClassPathXmlApplicationContext.setConfigLocation()方法;

整体概括

本章会涉及到多个类的细节,所有先从整体概括AbstractRefreshableConfigApplicationContext方法的主要功能,后面的细节都是基于这些总结展开的:

  1. setConfigLocations主要工作有两个:创建环境对象ConfiguableEnvironment,处理ClassPathXmlApplicationContext传入的字符串的占位符;
  2. 环境对象ConfiguableEnvironment中包含了当前JVM的profile配置信息,环境变量,java进程变量;
  3. 处理占位符的关键是ConfiguableEnvironment,PropertyResolver,PropertyPlaceHolderHelper之间的配合:
名称作用
ConfiguableEnvironment1:创建PropertyResolver对象;
2:向PropertyResolver提供环境变量,java进程变量;
PropertyResolver1:创建PropertyPlaceHolderHelper对象;
2:定位占位符的前缀与后缀(PlaceHolderPrefix,PlaceHodlerSuffix)
3:提供getPropertyAsRawString方法给PropertyPlaceHolderHelper方法调用,用来获取指定key对应的环境;
PropertyPlaceHolderHelper1:找到字符串中的占位符;
2:用环境变量的值替换占位符;

用思维导图来辅助:
在这里插入图片描述

展开详情

接下来去阅读setConfiguableLocations方法内部的细节代码:

  1. 跟踪该方法,找到是在类AbstractRefreshableConfigApplicationContext中实现的:
public void setConfigLocations(@Nullable String... locations) {
		if (locations != null) {
			Assert.noNullElements(locations, "Config locations must not be null");
			this.configLocations = new String[locations.length];
			for (int i = 0; i < locations.length; i++) {
//				resolvePath为同一个类中将字符串解析为路劲的方法
				this.configLocations[i] = resolvePath(locations[i]).trim();
			}
		}
		else {
			this.configLocations = null;
		}
	}

从上述代码可以发现,本章要重点学习的是resolvePath(locations[i]),结合上一章demo的入参,此处应该是方法resolvePath(“classpath:applicationContext.xml”)

  1. 跟踪到AbstractRefreshableConfigApplicationContext类,这个方法的目的是替换到path字符串中的占位符${xxxx}这样的内容:
protected String resolvePath(String path) {
		return getEnvironment().resolveRequiredPlaceholders(path);
	}
  1. 先看getEnvironment()方法:
@Override
	public ConfigurableEnvironment getEnvironment() {
		if (this.environment == null) {
			this.environment = createEnvironment();
		}
		return this.environment;
	}
  1. 关于ConfigurableEnvironment 接口,我们先来看看它的声明方法和继承关系:
    在这里插入图片描述
    从上图可见:ConfiguableEnvironment接口有两个重要组成部分:Profile和Property;
    Profile是对测试,生产等不同环境下bean的配置,这里我们没有特殊设置,所有用到Profile是AbstractApplicationContext类的defualtProfile;
    接下来Property资源是如何产生的;

  2. 顺着调用一路看下去,发现最终会调用AbstractEnvironment类的构造方法:

public AbstractEnvironment() {
        this.propertySources = new MutablePropertySources(this.logger);
        this.propertyResolver = new PropertySourcesPropertyResolver(this.propertySources);
        this.customizePropertySources(this.propertySources);
        if (this.logger.isDebugEnabled()) {
            this.logger.debug("Initialized " + this.getClass().getSimpleName() + " with PropertySources " + this.propertySources);
        }

    }
  1. 上面的customizePropertySources(this.propertySources)是在StandardEnvironment类实现的:
@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()));
	}

上述代码中,可以把propertySources理解成一个容器(其对象内部核心成员propertySourceList是CopyOnWriteArrayList实例);
首先向propertySourceList添加一组属性,来自java进程变量(getSystemProperties()内是(Map) System.getProperties()方法);
接着向propertySourceList再添加一组属性,来自系统环境变量(getSystemEnvironment()内是Map) System.getenv()方法);
getSystemPropertys和getSystemEnvironment有个相同的细节需要注意,在获取进程变量或系统环境变量的时候,都有可能因为安全限制而抛出异常,这时候返回一个ReadOnlySystemAttributesMap实现类,外部调用get方法的时候,在去尝试获取进程变量或系统环境变量对应的值,取不到则返回null,代码如下:

@Override
	@SuppressWarnings({"unchecked", "rawtypes"})
	public Map<String, Object> getSystemEnvironment() {
		if (suppressGetenvAccess()) {
			return Collections.emptyMap();
		}
		try {
			return (Map) System.getenv();
		}
		catch (AccessControlException ex) {
			return (Map) new ReadOnlySystemAttributesMap() {
				@Override
				@Nullable
				protected String getSystemAttribute(String attributeName) {
					try {
						return System.getenv(attributeName);
					}
					catch (AccessControlException ex) {
						if (logger.isInfoEnabled()) {
							logger.info("Caught AccessControlException when accessing system environment variable '" +
									attributeName + "'; its value will be returned [null]. Reason: " + ex.getMessage());
						}
						return null;
					}
				}
			};
		}
	}
  1. StandardEnvironment对象创建成功后,接着看它的resolveRequiredPlaceholders(path)方法:
@Override
	public String resolveRequiredPlaceholders(String text) throws IllegalArgumentException {
		return this.propertyResolver.resolveRequiredPlaceholders(text);
	}

上面的propertyResolver从哪里来,以下代码可见,这个final型的成员变量在声明的时候就创建了,前面准备好的propertyResource集合通过构造方法传给了它,所有它已经获得了所有进程变量和系统环境变量:

 public AbstractEnvironment() {
        this.propertySources = new MutablePropertySources(this.logger);
        this.propertyResolver = new PropertySourcesPropertyResolver(this.propertySources);
  1. 跟踪this.propertyResolver.resolveRequiredPlaceholders(text),发现真正处理占位符的逻辑是在PropertyPlaceholderHelper.doResolvePlaceholders(text, this.strictHelper)方法:
private String doResolvePlaceholders(String text, PropertyPlaceholderHelper helper) {
		return helper.replacePlaceholders(text, this::getPropertyAsRawString);
	}
  1. 继续跟踪helper.replacePlaceholders(text, this::getPropertyAsRawString),到了PropertyPlaceholderHelper.parseStringValue(value, placeholderResolver, new HashSet<>()),这里逐一找出每个占位符去做替换:
public String replacePlaceholders(String value, PlaceholderResolver placeholderResolver) {
		Assert.notNull(value, "'value' must not be null");
		return parseStringValue(value, placeholderResolver, new HashSet<>());
	}
  1. parseStringValue方法中,找到占位符后,会调placeholderResolver.resolvePlaceholder(actualPlaceholder)方法,也就是步骤中8类的this::getPropertyAsRawString(实际上就是PropertySourcesPropertyResolver.getPropertyAsRawString方法),最终会在PropertySourcesPropertyResolver.getProperty方法中找出所有的属性来匹配占位符:
protected String parseStringValue(
			String value, PlaceholderResolver placeholderResolver, Set<String> visitedPlaceholders) {

		StringBuilder result = new StringBuilder(value);

		int startIndex = value.indexOf(this.placeholderPrefix);
		while (startIndex != -1) {
			int endIndex = findPlaceholderEndIndex(result, startIndex);
			if (endIndex != -1) {
				String placeholder = result.substring(startIndex + this.placeholderPrefix.length(), endIndex);
				String originalPlaceholder = placeholder;
				if (!visitedPlaceholders.add(originalPlaceholder)) {
					throw new IllegalArgumentException(
							"Circular placeholder reference '" + originalPlaceholder + "' in property definitions");
				}
				// Recursive invocation, parsing placeholders contained in the placeholder key.
//				递归调用,分析占位符键中包含的占位符
				placeholder = parseStringValue(placeholder, placeholderResolver, visitedPlaceholders);
				// Now obtain the value for the fully resolved key...
//				现在获取完全解析键的值
				String propVal = placeholderResolver.resolvePlaceholder(placeholder);
				if (propVal == null && this.valueSeparator != null) {
					int separatorIndex = placeholder.indexOf(this.valueSeparator);
					if (separatorIndex != -1) {
						String actualPlaceholder = placeholder.substring(0, separatorIndex);
						String defaultValue = placeholder.substring(separatorIndex + this.valueSeparator.length());
						propVal = placeholderResolver.resolvePlaceholder(actualPlaceholder);
						if (propVal == null) {
							propVal = defaultValue;
						}
					}
				}
				if (propVal != null) {
					// Recursive invocation, parsing placeholders contained in the
					// previously resolved placeholder value.
					propVal = parseStringValue(propVal, placeholderResolver, visitedPlaceholders);
					result.replace(startIndex, endIndex + this.placeholderSuffix.length(), propVal);
					if (logger.isTraceEnabled()) {
						logger.trace("Resolved placeholder '" + placeholder + "'");
					}
					startIndex = result.indexOf(this.placeholderPrefix, startIndex + propVal.length());
				}
				else if (this.ignoreUnresolvablePlaceholders) {
					// Proceed with unprocessed value.
					startIndex = result.indexOf(this.placeholderPrefix, endIndex + this.placeholderSuffix.length());
				}
				else {
					throw new IllegalArgumentException("Could not resolve placeholder '" +
							placeholder + "'" + " in value \"" + value + "\"");
				}
				visitedPlaceholders.remove(originalPlaceholder);
			}
			else {
				startIndex = -1;
			}
		}

		return result.toString();
	}

至此spring环境的初始化准备工作已经完成,下一章一起去看refresh()方法,那里聚集了spring初始化的核心操作,

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值