Spring占位符Placeholder的實現原理解析

占位符Placeholder的使用

xml中的配置:

<?xml version="1.0" encoding="utf-8" ?>
<beans xmlns="http://www.springframework.org/schema/beans"
	  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	  xmlns:context="http://www.springframework.org/schema/context"
	  xsi:schemaLocation="
	http://www.springframework.org/schema/beans
	http://www.springframework.org/schema/beans/spring-beans.xsd
  http://www.springframework.org/schema/context
  http://www.springframework.org/schema/context/spring-context.xsd"
	  default-lazy-init="false">

	<context:property-placeholder location="classpath:application.properties"/>

	<bean id="user" class="com.morris.spring.entity.Author">
		<property name="name" value="${author.name}" />
	</bean>
</beans>

實現原理

前面在Spring中自定義標簽的解析中分析到context:property-placeholder這種自定義標簽的解析流程如下:

  • 基於SPI機制,掃描所有類路徑下jar中/META-INFO/spring.handlers文件,並將這些文件讀取為一個key為namespace,value為具體NameSpaceHandler的Map結構。
  • 根據bean標簽名獲得xml上方的namespace,然後根據namespace從第一步中的map中獲得具體的NameSpaceHandler。
  • 調用NameSpaceHandler的init()方法進行初始化,此方法一般會將負責解析各種localName的BeanDefinitionParser解析器註冊到一個map中。
  • 根據localName=property-placeholder從上一步中獲得具體的BeanDefinitionParser解析器,並調用其parse()方法進行解析。

在這裡NameSpaceHandler為ContextNamespaceHandler,而BeanDefinitionParser解析器為PropertyPlaceholderBeanDefinitionParser,所以我們觀察的重點為PropertyPlaceholderBeanDefinitionParser的parse()方法。

註冊PropertySourcesPlaceholderConfigurer

parse()方法位於父類AbstractBeanDefinitionParser,先來看下繼承關系,後面的代碼使用瞭大量的模板方法模式,將會在這幾個類中來回切換:

在這裡插入圖片描述

org.springframework.beans.factory.xml.AbstractBeanDefinitionParser#parse

public final BeanDefinition parse(Element element, ParserContext parserContext) {
	// 調用子類org.springframework.beans.factory.xml.AbstractSingleBeanDefinitionParser.parseInternal
	AbstractBeanDefinition definition = parseInternal(element, parserContext);
	if (definition != null && !parserContext.isNested()) {
		try {
			// 生成一個ID
			String id = resolveId(element, definition, parserContext);
			if (!StringUtils.hasText(id)) {
				parserContext.getReaderContext().error(
						"Id is required for element '" + parserContext.getDelegate().getLocalName(element)
								+ "' when used as a top-level tag", element);
			}
			String[] aliases = null;
			if (shouldParseNameAsAliases()) {
				String name = element.getAttribute(NAME_ATTRIBUTE);
				if (StringUtils.hasLength(name)) {
					aliases = StringUtils.trimArrayElements(StringUtils.commaDelimitedListToStringArray(name));
				}
			}
			BeanDefinitionHolder holder = new BeanDefinitionHolder(definition, id, aliases);
			// 註冊BD
			registerBeanDefinition(holder, parserContext.getRegistry());
			if (shouldFireEvents()) {
				BeanComponentDefinition componentDefinition = new BeanComponentDefinition(holder);
				postProcessComponentDefinition(componentDefinition);
				parserContext.registerComponent(componentDefinition);
			}
		}
		catch (BeanDefinitionStoreException ex) {
			String msg = ex.getMessage();
			parserContext.getReaderContext().error((msg != null ? msg : ex.toString()), element);
			return null;
		}
	}
	return definition;
}

org.springframework.beans.factory.xml.AbstractSingleBeanDefinitionParser#parseInternal

protected final AbstractBeanDefinition parseInternal(Element element, ParserContext parserContext) {
	BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition();
	String parentName = getParentName(element);
	if (parentName != null) {
		builder.getRawBeanDefinition().setParentName(parentName);
	}
	// 獲取子類PropertyPlaceholderBeanDefinitionParser返回的PropertySourcesPlaceholderConfigurer
	Class<?> beanClass = getBeanClass(element);
	if (beanClass != null) {
		builder.getRawBeanDefinition().setBeanClass(beanClass);
	}
	else {
		String beanClassName = getBeanClassName(element);
		if (beanClassName != null) {
			builder.getRawBeanDefinition().setBeanClassName(beanClassName);
		}
	}
	builder.getRawBeanDefinition().setSource(parserContext.extractSource(element));
	BeanDefinition containingBd = parserContext.getContainingBeanDefinition();
	if (containingBd != null) {
		// Inner bean definition must receive same scope as containing bean.
		builder.setScope(containingBd.getScope());
	}
	if (parserContext.isDefaultLazyInit()) {
		// Default-lazy-init applies to custom bean definitions as well.
		builder.setLazyInit(true);
	}
	// 又是一個模板方法模式
	/**
	 * @see org.springframework.context.config.PropertyPlaceholderBeanDefinitionParser#doParse(org.w3c.dom.Element, org.springframework.beans.factory.xml.ParserContext, org.springframework.beans.factory.support.BeanDefinitionBuilder)
	*/
	doParse(element, parserContext, builder);
	return builder.getBeanDefinition();
}

org.springframework.context.config.PropertyPlaceholderBeanDefinitionParser#getBeanClass

protected Class<?> getBeanClass(Element element) {
	if (SYSTEM_PROPERTIES_MODE_DEFAULT.equals(element.getAttribute(SYSTEM_PROPERTIES_MODE_ATTRIBUTE))) {
		return PropertySourcesPlaceholderConfigurer.class; // 新版返回這個
	}

	return org.springframework.beans.factory.config.PropertyPlaceholderConfigurer.class;
}

org.springframework.context.config.PropertyPlaceholderBeanDefinitionParser#doParse

protected void doParse(Element element, ParserContext parserContext, BeanDefinitionBuilder builder) {
	// 調用父類的doParse
	super.doParse(element, parserContext, builder);

	builder.addPropertyValue("ignoreUnresolvablePlaceholders",
			Boolean.valueOf(element.getAttribute("ignore-unresolvable")));

	String systemPropertiesModeName = element.getAttribute(SYSTEM_PROPERTIES_MODE_ATTRIBUTE);
	if (StringUtils.hasLength(systemPropertiesModeName) &&
			!systemPropertiesModeName.equals(SYSTEM_PROPERTIES_MODE_DEFAULT)) {
		builder.addPropertyValue("systemPropertiesModeName", "SYSTEM_PROPERTIES_MODE_" + systemPropertiesModeName);
	}

	if (element.hasAttribute("value-separator")) {
		builder.addPropertyValue("valueSeparator", element.getAttribute("value-separator"));
	}
	if (element.hasAttribute("trim-values")) {
		builder.addPropertyValue("trimValues", element.getAttribute("trim-values"));
	}
	if (element.hasAttribute("null-value")) {
		builder.addPropertyValue("nullValue", element.getAttribute("null-value"));
	}
}

org.springframework.context.config.AbstractPropertyLoadingBeanDefinitionParser#doParse

protected void doParse(Element element, ParserContext parserContext, BeanDefinitionBuilder builder) {
	// 解析<context:property-placeholder>標簽的各種屬性
	String location = element.getAttribute("location");
	if (StringUtils.hasLength(location)) {
		location = parserContext.getReaderContext().getEnvironment().resolvePlaceholders(location);
		String[] locations = StringUtils.commaDelimitedListToStringArray(location);
		builder.addPropertyValue("locations", locations);
	}

	String propertiesRef = element.getAttribute("properties-ref");
	if (StringUtils.hasLength(propertiesRef)) {
		builder.addPropertyReference("properties", propertiesRef);
	}

	String fileEncoding = element.getAttribute("file-encoding");
	if (StringUtils.hasLength(fileEncoding)) {
		builder.addPropertyValue("fileEncoding", fileEncoding);
	}

	String order = element.getAttribute("order");
	if (StringUtils.hasLength(order)) {
		builder.addPropertyValue("order", Integer.valueOf(order));
	}

	builder.addPropertyValue("ignoreResourceNotFound",
			Boolean.valueOf(element.getAttribute("ignore-resource-not-found")));

	builder.addPropertyValue("localOverride",
			Boolean.valueOf(element.getAttribute("local-override")));

	builder.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
}

總結一下,其實上面這麼多代碼調來調去,隻有一個目的,就是向spring容器中註入一個BeanDefinition,這個BeanDefinition有兩個最重要的屬性:

  • BeanClass為PropertySourcesPlaceholderConfigurer。
  • 有一個屬性為location,對應properties文件的位置。

PropertySourcesPlaceholderConfigurer的調用

上面向spring容器中註入一個PropertySourcesPlaceholderConfigurer類型BeanDefinition,先來看下這個類的繼承關系:

在這裡插入圖片描述

從上圖的繼承關系可以看出PropertySourcesPlaceholderConfigurer實現瞭BeanFactoryPostProcessor,所以這個類的核心方法為postProcessBeanFactory()。

public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
	if (this.propertySources == null) { // null
		this.propertySources = new MutablePropertySources();
		if (this.environment != null) {

			// environment中存儲的是系統屬性和環境變量
			this.propertySources.addLast(
				new PropertySource<Environment>(ENVIRONMENT_PROPERTIES_PROPERTY_SOURCE_NAME, this.environment) {
					@Override
					@Nullable
					public String getProperty(String key) {
						return this.source.getProperty(key);
					}
				}
			);
		}
		try {
			// 加載application.properties為Properties,包裝為PropertySource
			PropertySource<?> localPropertySource =
					new PropertiesPropertySource(LOCAL_PROPERTIES_PROPERTY_SOURCE_NAME, mergeProperties());
			if (this.localOverride) {
				this.propertySources.addFirst(localPropertySource);
			}
			else {
				this.propertySources.addLast(localPropertySource);
			}
		}
		catch (IOException ex) {
			throw new BeanInitializationException("Could not load properties", ex);
		}
	}

	// 處理占位符
	processProperties(beanFactory, new PropertySourcesPropertyResolver(this.propertySources));
	this.appliedPropertySources = this.propertySources;
}

上面的方法的前面一大截的主要作用為將系統屬性、環境變量以及properties文件中的屬性整合到MutablePropertySources中,這樣就可以直接調用MutablePropertySources.getProperties()方法根據屬性名拿到對應的屬性值瞭。MutablePropertySources裡面其實是一個Map的鏈表,這樣就可以先遍歷鏈表,然後再根據屬性名從Map中找到對應的屬性值。

protected void processProperties(ConfigurableListableBeanFactory beanFactoryToProcess,
		final ConfigurablePropertyResolver propertyResolver) throws BeansException {

	propertyResolver.setPlaceholderPrefix(this.placeholderPrefix); // ${
	propertyResolver.setPlaceholderSuffix(this.placeholderSuffix); // }
	propertyResolver.setValueSeparator(this.valueSeparator); // :

	// 下面的doProcessProperties會回調這個lambda表達式
	// 真正的解析邏輯在resolveRequiredPlaceholders
	/**
	 * @see AbstractPropertyResolver#resolveRequiredPlaceholders(java.lang.String)
	 */
	StringValueResolver valueResolver = strVal -> {
		String resolved = (this.ignoreUnresolvablePlaceholders ?
				propertyResolver.resolvePlaceholders(strVal) :
				propertyResolver.resolveRequiredPlaceholders(strVal));
		if (this.trimValues) {
			resolved = resolved.trim();
		}
		return (resolved.equals(this.nullValue) ? null : resolved);
	};

	// 這裡會遍歷所有的BD,挨個處理占位符
	doProcessProperties(beanFactoryToProcess, valueResolver);
}

org.springframework.beans.factory.config.PlaceholderConfigurerSupport#doProcessProperties

protected void doProcessProperties(ConfigurableListableBeanFactory beanFactoryToProcess,
		StringValueResolver valueResolver) {

	BeanDefinitionVisitor visitor = new BeanDefinitionVisitor(valueResolver);

	String[] beanNames = beanFactoryToProcess.getBeanDefinitionNames();
	for (String curName : beanNames) {
		// Check that we're not parsing our own bean definition,
		// to avoid failing on unresolvable placeholders in properties file locations.
		if (!(curName.equals(this.beanName) && beanFactoryToProcess.equals(this.beanFactory))) {
			BeanDefinition bd = beanFactoryToProcess.getBeanDefinition(curName);
			try {
				// 遍歷BD
				visitor.visitBeanDefinition(bd);
			}
			catch (Exception ex) {
				throw new BeanDefinitionStoreException(bd.getResourceDescription(), curName, ex.getMessage(), ex);
			}
		}
	}

	// New in Spring 2.5: resolve placeholders in alias target names and aliases as well.
	beanFactoryToProcess.resolveAliases(valueResolver);

	// New in Spring 3.0: resolve placeholders in embedded values such as annotation attributes.
	beanFactoryToProcess.addEmbeddedValueResolver(valueResolver);
}

org.springframework.beans.factory.config.BeanDefinitionVisitor#visitBeanDefinition

public void visitBeanDefinition(BeanDefinition beanDefinition) {
	visitParentName(beanDefinition);
	visitBeanClassName(beanDefinition);
	visitFactoryBeanName(beanDefinition);
	visitFactoryMethodName(beanDefinition);
	visitScope(beanDefinition);
	if (beanDefinition.hasPropertyValues()) {
		// 遍歷所有的屬性
		visitPropertyValues(beanDefinition.getPropertyValues());
	}
	if (beanDefinition.hasConstructorArgumentValues()) {
		ConstructorArgumentValues cas = beanDefinition.getConstructorArgumentValues();
		visitIndexedArgumentValues(cas.getIndexedArgumentValues());
		visitGenericArgumentValues(cas.getGenericArgumentValues());
	}
}

org.springframework.beans.factory.config.BeanDefinitionVisitor#visitPropertyValues

protected void visitPropertyValues(MutablePropertyValues pvs) {
	PropertyValue[] pvArray = pvs.getPropertyValues();
	for (PropertyValue pv : pvArray) {
		// 解析占位符
		Object newVal = resolveValue(pv.getValue());
		if (!ObjectUtils.nullSafeEquals(newVal, pv.getValue())) {
			// 將新的value替換BD中舊的
			pvs.add(pv.getName(), newVal);
		}
	}
}

resolveValue()方法中會回調到之前的lambda表達式StringValueResolv真正開始解析,也就是根據屬性名從PropertySources中取值。

總結一下PropertySourcesPlaceholderConfigurer#postProcessBeanFactory()方法:這個方法會在Bean實例化之前完成對Spring容器中所有BeanDefinition中帶有占位符的屬性進行解析,這樣在Bean實例化後就能被賦予正確的屬性瞭。

到此這篇關於Spring占位符Placeholder的實現原理的文章就介紹到這瞭,更多相關Spring占位符Placeholder內容請搜索WalkonNet以前的文章或繼續瀏覽下面的相關文章希望大傢以後多多支持WalkonNet!

推薦閱讀: