Spring源码分析ClassPathXmlApplicationContext获取配置信息(refresh之前)

ClassPathXmlApplicationContext

// xml的一种bean配置方式
ApplicationContext context = new ClassPathXmlApplicationContext(new String[] { "applicationContext.xml" });

基于上述代码,分析获取配置信息部分,即refresh方法之前的部分。

继承关系

ClassPathXmlApplicationContext继承关系
部分说明如下

  • ClassPathXmlApplicationContext:Resource配置信息,基于本文配置方式不涉及针对Resource配置的构造方法。后续补充该方式配置。
  • AbstractRefreshableConfigApplicationContext:本文设计的配置信息存储在该类中(configLocations),同时此类实现BeanNameAware和InitializingBean接口,BeanNameAware作用是感知bean的id,InitializingBean的作用是初始化bean时自动调用afterPropertiesSet方法。
  • AbstractRefreshableApplicationContext:提供是否允许BeanDefinition覆盖和循环引用的开关,同时包含DefaultListableBeanFactory,该工厂实现批量、分层、自动装配等功能,是功能强大的BeanFactory。

构造方法

	public ClassPathXmlApplicationContext(
			String[] configLocations, boolean refresh, @Nullable ApplicationContext parent)
			throws BeansException {

		super(parent);
		setConfigLocations(configLocations);
		if (refresh) {
			refresh();
		}
	}

在本文配置方法中,refresh=true,parent=null。
本文主要解析setConfigLocations(configLocations); 这行代码,也就是refresh核心方法之前的处理。

构造方法向上追溯到AbstractApplicationContext

	public AbstractApplicationContext(@Nullable ApplicationContext parent) {
		this();
		setParent(parent);
	}

继续追踪this()默认构造

	public AbstractApplicationContext() {
		this.resourcePatternResolver = getResourcePatternResolver();
	}
	protected ResourcePatternResolver getResourcePatternResolver() {
		return new PathMatchingResourcePatternResolver(this);
	}

PathMatchingResourcePatternResolver为Ant风格资源解析器,三种Ant通配符如下:

通配符作用
匹配任何单字符
*匹配0或者任意数量的字符
**匹配0或者更多目录

获取配置信息

之前继承关系图中AbstractRefreshableConfigApplicationContext中可存储配置信息,因此该类实现setConfigLocations方法:

	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++) {
				this.configLocations[i] = resolvePath(locations[i]).trim();
			}
		}
		else {
			this.configLocations = null;
		}
	}

跟进解析路径方法resolvePath

	protected String resolvePath(String path) {
		return getEnvironment().resolveRequiredPlaceholders(path);
	}

根据方法名可知,首先获取环境信息,然后替代配置路径中的占位符,也就是解析路径。

获取环境信息

AbstractApplicationContext中实现getEnvironment方法

	public ConfigurableEnvironment getEnvironment() {
		if (this.environment == null) {
			this.environment = createEnvironment();
		}
		return this.environment;
	}

关注createEnvironment方法

	protected ConfigurableEnvironment createEnvironment() {
		return new StandardEnvironment();
	}

实际上是创建了一个StandardEnvironment对象。

StandardEnvironment

StandardEnvironment继承关系图
这里只分析StandardEnvironmentAbstractEnvironment两个类,目的是完成第一步getEnvironment获取环境信息。
创建StandardEnvironment首先看AbstractEnvironment抽象类构造方法:

	public AbstractEnvironment() {
		customizePropertySources(this.propertySources);
	}

propertySources为AbstractEnvironment中的定义的成员变量,用于存储属性资源,定义如下:

	private final MutablePropertySources propertySources = new MutablePropertySources();

MutablePropertySources理解为可变属性资源,实现了PropertySources接口,可以理解为属性资源的key-value对。其中资源存储采用CopyOnWriteArrayList

	private final List<PropertySource<?>> propertySourceList = new CopyOnWriteArrayList<>();

customizePropertySources

明白了在哪存储资源,再看如何向资源容器中添加资源:

	protected void customizePropertySources(MutablePropertySources propertySources) {
	}

AbstractEnvironment中声明该方法,在StandardEnvironment中实现:

	@Override
	protected void customizePropertySources(MutablePropertySources propertySources) {
		propertySources.addLast(
				new PropertiesPropertySource(SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME, getSystemProperties()));
		propertySources.addLast(
				new SystemEnvironmentPropertySource(SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME, getSystemEnvironment()));
	}
  • getSystemProperties方法最终调用System.getProperties(),获取java相关属性,包括jdk、jvm等信息。
  • getSystemEnvironment方法最终调用System.getenv(),获取系统环境变量信息。
  • 环境变量是在OS级别指定的。
  • Java属性通过将-D选项传递给JVM来指定(并且可以通过编程方式设置)。

至此,环境信息获取完成,然后进行解析,调用resolveRequiredPlaceholders方法。

解析路径

重新定位到AbstractRefreshableConfigApplicationContext类的resolvePath方法:

	protected String resolvePath(String path) {
		return getEnvironment().resolveRequiredPlaceholders(path);
	}

getEnvironment()已经分析完成,创建了一个StandardEnvironment对象,获取到环境信息。resolveRequiredPlaceholders方法在PropertyResolver接口中声明,接下来分析该方法在何处调用。

PropertyResolver类图

PropertyResolver类图
此图在之前StandardEnvironment继承关系的基础上延伸了属性解析器的具体实现类。

AbstractEnvironment

AbstractEnvironment中实现解析方法如下:

	@Override
	public String resolveRequiredPlaceholders(String text) throws IllegalArgumentException {
		return this.propertyResolver.resolveRequiredPlaceholders(text);
	}

此类中包含一个propertyResolver成员,定义如下:

	private final ConfigurablePropertyResolver propertyResolver =
			new PropertySourcesPropertyResolver(this.propertySources);

对应上面的类图,propertyResolver为属性解析器对象,属性资源propertySources即为前文中存储属性资源的MutablePropertySources对象。

AbstractPropertyResolver

有了属性解析器propertySource(PropertySourcesPropertyResolver类),也有了属性资源propertySource(MutablePropertySources类),可以开始执行资源解析,解析操作在AbstractPropertyResolver中来完成。

	@Override
	public String resolveRequiredPlaceholders(String text) throws IllegalArgumentException {
		if (this.strictHelper == null) {
			this.strictHelper = createPlaceholderHelper(false);
		}
		return doResolvePlaceholders(text, this.strictHelper);
	}

该类中包含一个工具类PropertyPlaceholderHelper,作用是处理属性的占位符,占位符是什么东西,继续跟创建该工具类的方法createPlaceholderHelper

	private PropertyPlaceholderHelper createPlaceholderHelper(boolean ignoreUnresolvablePlaceholders) 		{
		return new PropertyPlaceholderHelper(this.placeholderPrefix, this.placeholderSuffix,
				this.valueSeparator, ignoreUnresolvablePlaceholders);
	}

此处调用了PropertyPlaceholderHelper的构造器,传入的参数信息如下:

参数名参数值作用
placeholderPrefix${占位符前缀
placeholderSuffix}占位符后缀
valueSeparator:冒号,后接占位符指代内容
ignoreUnresolvablePlaceholdersfalse是否忽略不可解析的占位符

接下来看一下PropertyPlaceholderHelper的构造器:

	public PropertyPlaceholderHelper(String placeholderPrefix, String placeholderSuffix,
			@Nullable String valueSeparator, boolean ignoreUnresolvablePlaceholders) {

		Assert.notNull(placeholderPrefix, "'placeholderPrefix' must not be null");
		Assert.notNull(placeholderSuffix, "'placeholderSuffix' must not be null");
		this.placeholderPrefix = placeholderPrefix;
		this.placeholderSuffix = placeholderSuffix;
		String simplePrefixForSuffix = wellKnownSimplePrefixes.get(this.placeholderSuffix);
		if (simplePrefixForSuffix != null && this.placeholderPrefix.endsWith(simplePrefixForSuffix)) {
			this.simplePrefix = simplePrefixForSuffix;
		}
		else {
			this.simplePrefix = this.placeholderPrefix;
		}
		this.valueSeparator = valueSeparator;
		this.ignoreUnresolvablePlaceholders = ignoreUnresolvablePlaceholders;
	}

其中simplePrefix参数表示基本的占位符类型前缀,在wellKnownSimplePrefixes中定义了三种常见占位符类型:

	private static final Map<String, String> wellKnownSimplePrefixes = new HashMap<>(4);

	static {
		wellKnownSimplePrefixes.put("}", "{");
		wellKnownSimplePrefixes.put("]", "[");
		wellKnownSimplePrefixes.put(")", "(");
	}

有了属性占位符解析工具PropertyPlaceholderHelper,下面开始解析,首先进入doResolvePlacehoders方法:

	private String doResolvePlaceholders(String text, PropertyPlaceholderHelper helper) {
		return helper.replacePlaceholders(text, this::getPropertyAsRawString);
	}

这里发现具体的解析操作时交给解析工具PropertyPlaceholderHelper完成,参数包括需要解析的内容text和一个lambda表达式的方法getPropertyAsRawString(此方法后面分析,这里先不用管)。

PropertyPlaceholderHelper

	public String replacePlaceholders(String value, PlaceholderResolver placeholderResolver) {
		Assert.notNull(value, "'value' must not be null");
		return parseStringValue(value, placeholderResolver, null);
	}

这里参数为PlaceholderResolver,这是一个策略接口,用于解析占位符中需要替换的值。
看一下PlaceholderResolver接口的定义:

	/**
	 * Strategy interface used to resolve replacement values for placeholders contained in Strings.
	 */
	@FunctionalInterface
	public interface PlaceholderResolver {

		/**
		 * Resolve the supplied placeholder name to the replacement value.
		 * @param placeholderName the name of the placeholder to resolve
		 * @return the replacement value, or {@code null} if no replacement is to be made
		 */
		@Nullable
		String resolvePlaceholder(String placeholderName);
	}

@FunctinalInterface表示这是一个函数式接口,只能有一个抽象方法,这里结合lambda表达式调用,后面可以看到。

继续分析parseStringValue方法:

	protected String parseStringValue(
			String value, PlaceholderResolver placeholderResolver, @Nullable Set<String> visitedPlaceholders) {

		int startIndex = value.indexOf(this.placeholderPrefix);
		if (startIndex == -1) {
			return value;
		}

		StringBuilder result = new StringBuilder(value);
		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 == null) {
					visitedPlaceholders = new HashSet<>(4);
				}
				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();
	}

这里是具体的解析算法,采用递归解析的方式,对算法感兴趣可以详细看一下。我们重点关注调用的resolvePlaceholder方法,实际上就是调用的AbstractPropertyResolver类中的getPropertyAsRawString方法,该方法在PropertySourcesPropertyResolver中具体实现:

	@Override
	@Nullable
	protected String getPropertyAsRawString(String key) {
		return getProperty(key, String.class, false);
	}

继续看getProperty方法,通过占位符名字,从属性资源中获取对应的值:

	@Nullable
	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;
	}

从这里可以看出,因为之前获取的环境资源存储在PropertySourcesPropertyResolver类中的PropertySources类型的成员变量中,所以在这里进行具体的占位符名称的替换操作。

最终,将解析好的字符串存储在AbstractRefreshableConfigApplicationContext中:

	@Nullable
	private String[] configLocations;

后续的refresh部分,会从这里拿配置信息进行注册Bean等操作。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值