欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

SpringBoot源碼 PropertySource解析

 更新時(shí)間:2023年01月09日 15:53:46   作者:起風(fēng)哥  
PropertySource是spring中對于鍵值屬性的一種抽象,主要是name和sourcePropertyResolver是對PropertySource提供對外的統(tǒng)一數(shù)據(jù)處理,對于占位符的處理委托于PropertyPlaceholderHelper,對Springboot?源碼 PropertySource相關(guān)知識感興趣的朋友一起看看吧

SpringBoot源碼 PropertySource以及解析

介紹

PropertySource是spring中對于鍵值屬性的一種抽象,主要是name和source。source是任意對象,可以是對象,可以是Map,可以是List等等。但是一般常用的卻不是source,而是抽象的方法getProperty。根據(jù)name,獲取source中的值,也就是說由繼承類來實(shí)現(xiàn)getProperty然后返回?cái)?shù)據(jù)。例如如果souce是Map,那就從map中獲取value值,如果Source是List,那可能就是找到遍歷找到值,如果是對象就直接get獲得值等等。接下來看下源碼。

源碼

PropertySource

public abstract class PropertySource<T> {
 
	protected final Log logger = LogFactory.getLog(getClass());
 
	protected final String name;
 
	protected final T source;
 
	public PropertySource(String name, T source) {
		Assert.hasText(name, "Property source name must contain at least one character");
		Assert.notNull(source, "Property source must not be null");
		this.name = name;
		this.source = source;
	}
 
	@SuppressWarnings("unchecked")
	public PropertySource(String name) {
		this(name, (T) new Object());
	}
 
 
	public String getName() {
		return this.name;
	}
 
	public T getSource() {
		return this.source;
	}
 
	public abstract Object getProperty(String name);
 
	@Override
	public boolean equals(Object obj) {
		return (this == obj || (obj instanceof PropertySource &&
				ObjectUtils.nullSafeEquals(this.name, ((PropertySource<?>) obj).name)));
	}
 
 
	@Override
	public int hashCode() {
		return ObjectUtils.nullSafeHashCode(this.name);
	}
 
	public static PropertySource<?> named(String name) {
		return new ComparisonPropertySource(name);
	}
 
	public static class StubPropertySource extends PropertySource<Object> {
 
		public StubPropertySource(String name) {
			super(name, new Object());
		}
 
		/**
		 * Always returns {@code null}.
		 */
		@Override
		public String getProperty(String name) {
			return null;
		}
	}
 
 
	static class ComparisonPropertySource extends StubPropertySource {
 
		private static final String USAGE_ERROR =
				"ComparisonPropertySource instances are for use with collection comparison only";
 
		public ComparisonPropertySource(String name) {
			super(name);
		}
 
		@Override
		public Object getSource() {
			throw new UnsupportedOperationException(USAGE_ERROR);
		}
 
		@Override
		public boolean containsProperty(String name) {
			throw new UnsupportedOperationException(USAGE_ERROR);
		}
 
		@Override
		public String getProperty(String name) {
			throw new UnsupportedOperationException(USAGE_ERROR);
		}
	}
}

在PropertySource中有兩個(gè)內(nèi)部類需要了解下,一個(gè)是StubPropertySource還有一個(gè)是ComparisonPropertySource;這兩個(gè)內(nèi)部類有什么作用呢?

StubPropertySource:其實(shí)一般PropertySource在使用的時(shí)候都是使用PropertySource的集合,所以在集合中就會(huì)有先后順序的作用,這個(gè)StubPropertySource就是用作占位用的。

ComparisonPropertySource:既然PropertySource一般用作集合中,那如果要查找某個(gè)名稱的PropertySource的位置(indexOf),那必須要是PropertySource才行,所以這時(shí)候就 new ComparisonPropertySource(name),就像named那個(gè)方法一樣。 僅僅是用作在集合中的比較而已,通過源碼中看到重寫的equals和hashCode都是對于名稱來計(jì)算的。

PropertyResolver

但是由于繼承PropertySource的類很多,為了統(tǒng)一封裝對外出口,所以spring就提供了一個(gè)PropertyResolver接口,實(shí)現(xiàn)PropertyResolver統(tǒng)一獲取PropertySource的信息。

PropertyResolver是對于PropertySource對外提供數(shù)據(jù)的統(tǒng)一封裝,并且其中還提供了類型轉(zhuǎn)換的功能。由spring內(nèi)部提供ConversionService對取得的數(shù)據(jù)進(jìn)行相應(yīng)的轉(zhuǎn)換,轉(zhuǎn)換成期望的數(shù)據(jù)類型。

接下來看下PropertyResolver的接口和具體實(shí)現(xiàn)

public interface PropertyResolver {
 
	boolean containsProperty(String key);
 
    // 獲取數(shù)據(jù)
	String getProperty(String key);
 
    // 獲取數(shù)據(jù),如果是空,默認(rèn)使用defaultValue
	String getProperty(String key, String defaultValue);
 
    // 轉(zhuǎn)化成指定類型的數(shù)據(jù)
	<T> T getProperty(String key, Class<T> targetType);
 
    // 轉(zhuǎn)化成指定類型的數(shù)據(jù),如果是空,默認(rèn)使用defaultValue
	<T> T getProperty(String key, Class<T> targetType, T defaultValue);
 
	@Deprecated
	<T> Class<T> getPropertyAsClass(String key, Class<T> targetType);
 
 
	String getRequiredProperty(String key) throws IllegalStateException;
 
	<T> T getRequiredProperty(String key, Class<T> targetType) throws IllegalStateException;
 
    // 獲取占位符的數(shù)據(jù)
	String resolvePlaceholders(String text);
 
    // 占位符數(shù)據(jù)不允許為空,空就拋異常
	String resolveRequiredPlaceholders(String text) throws IllegalArgumentException;
 
}

  PropertySourcesPropertyResolver

public class PropertySourcesPropertyResolver extends AbstractPropertyResolver {
 
	@Nullable
	private final PropertySources propertySources;
 
 
	public PropertySourcesPropertyResolver(@Nullable PropertySources propertySources) {
		this.propertySources = propertySources;
	}
 
 
	@Override
	public boolean containsProperty(String key) {
		if (this.propertySources != null) {
			for (PropertySource<?> propertySource : this.propertySources) {
				if (propertySource.containsProperty(key)) {
					return true;
				}
			}
		}
		return false;
	}
 
	@Override
	@Nullable
	public String getProperty(String key) {
		return getProperty(key, String.class, true);
	}
 
	@Override
	@Nullable
	public <T> T getProperty(String key, Class<T> targetValueType) {
		return getProperty(key, targetValueType, true);
	}
 
    // 回調(diào)會(huì)用到
	@Override
	@Nullable
	protected String getPropertyAsRawString(String key) {
		return getProperty(key, String.class, false);
	}
 
	@Nullable
	protected <T> T getProperty(String key, Class<T> targetValueType, boolean resolveNestedPlaceholders) {
		if (this.propertySources != null) {
            // 遍歷PropertySources獲取PropertySource
			for (PropertySource<?> propertySource : this.propertySources) {
				if (logger.isTraceEnabled()) {
					logger.trace("Searching for key '" + key + "' in PropertySource '" +
							propertySource.getName() + "'");
				}
                // 從PropertySource中獲取值
				Object value = propertySource.getProperty(key);
				if (value != null) {
					if (resolveNestedPlaceholders && value instanceof String) {
                        // 如果是允許解析占位符,解析占位符
						value = resolveNestedPlaceholders((String) value);
					}
					logKeyFound(key, propertySource, value);
                    // 轉(zhuǎn)化成期望的類型
					return convertValueIfNecessary(value, targetValueType);
				}
			}
		}
		if (logger.isDebugEnabled()) {
			logger.debug("Could not find key '" + key + "' in any property source");
		}
		return null;
	}
 
 
	protected void logKeyFound(String key, PropertySource<?> propertySource, Object value) {
		if (logger.isDebugEnabled()) {
			logger.debug("Found key '" + key + "' in PropertySource '" + propertySource.getName() +
					"' with value of type " + value.getClass().getSimpleName());
		}
	}
}

在PropertySourcesPropertyResolver主要是處理PropertySources,也就是PropertySource的集合,其實(shí)主要的就是getProperty的方法,從所有的PropertySource中獲取資源,如果獲取到了,并且允許解析占位符且值是String型,那就去解析占位符的值。解析了占位符之后,轉(zhuǎn)化成期望的數(shù)據(jù)類型。 可以看到這邊取到第一個(gè)滿足條件的數(shù)據(jù)之后就會(huì)返回,這也是有些數(shù)據(jù)排在前面,然后優(yōu)先的策略。比如命令行參數(shù)就比配置文件中參數(shù)優(yōu)先。這些后面也會(huì)說到。

對于占位符的解析和數(shù)據(jù)類型的轉(zhuǎn)化在在AbstractPropertyResolver方法中,繼續(xù)看AbstractPropertyResolver方法。

 
package org.springframework.core.env;
 
import java.util.LinkedHashSet;
import java.util.Set;
 
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
 
import org.springframework.core.convert.ConversionService;
import org.springframework.core.convert.support.ConfigurableConversionService;
import org.springframework.core.convert.support.DefaultConversionService;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.PropertyPlaceholderHelper;
import org.springframework.util.SystemPropertyUtils;
 
 
public abstract class AbstractPropertyResolver implements ConfigurablePropertyResolver {
 
	protected final Log logger = LogFactory.getLog(getClass());
 
	@Nullable
	private volatile ConfigurableConversionService conversionService;
 
	@Nullable
	private PropertyPlaceholderHelper nonStrictHelper;
 
	@Nullable
	private PropertyPlaceholderHelper strictHelper;
 
	private boolean ignoreUnresolvableNestedPlaceholders = false;
 
	private String placeholderPrefix = SystemPropertyUtils.PLACEHOLDER_PREFIX;
 
	private String placeholderSuffix = SystemPropertyUtils.PLACEHOLDER_SUFFIX;
 
	@Nullable
	private String valueSeparator = SystemPropertyUtils.VALUE_SEPARATOR;
 
	private final Set<String> requiredProperties = new LinkedHashSet<>();
 
 
	@Override
	public ConfigurableConversionService getConversionService() {
		// Need to provide an independent DefaultConversionService, not the
		// shared DefaultConversionService used by PropertySourcesPropertyResolver.
		ConfigurableConversionService cs = this.conversionService;
		if (cs == null) {
			synchronized (this) {
				cs = this.conversionService;
				if (cs == null) {
					cs = new DefaultConversionService();
					this.conversionService = cs;
				}
			}
		}
		return cs;
	}
 
	@Override
	public void setConversionService(ConfigurableConversionService conversionService) {
		Assert.notNull(conversionService, "ConversionService must not be null");
		this.conversionService = conversionService;
	}
 
 
	@Override
	public void setPlaceholderPrefix(String placeholderPrefix) {
		Assert.notNull(placeholderPrefix, "'placeholderPrefix' must not be null");
		this.placeholderPrefix = placeholderPrefix;
	}
 
	@Override
	public void setPlaceholderSuffix(String placeholderSuffix) {
		Assert.notNull(placeholderSuffix, "'placeholderSuffix' must not be null");
		this.placeholderSuffix = placeholderSuffix;
	}
 
 
	@Override
	public void setValueSeparator(@Nullable String valueSeparator) {
		this.valueSeparator = valueSeparator;
	}
 
 
	@Override
	public void setIgnoreUnresolvableNestedPlaceholders(boolean ignoreUnresolvableNestedPlaceholders) {
		this.ignoreUnresolvableNestedPlaceholders = ignoreUnresolvableNestedPlaceholders;
	}
 
	@Override
	public void setRequiredProperties(String... requiredProperties) {
		for (String key : requiredProperties) {
			this.requiredProperties.add(key);
		}
	}
 
	@Override
	public void validateRequiredProperties() {
		MissingRequiredPropertiesException ex = new MissingRequiredPropertiesException();
		for (String key : this.requiredProperties) {
			if (this.getProperty(key) == null) {
				ex.addMissingRequiredProperty(key);
			}
		}
		if (!ex.getMissingRequiredProperties().isEmpty()) {
			throw ex;
		}
	}
 
	@Override
	public boolean containsProperty(String key) {
		return (getProperty(key) != null);
	}
 
	@Override
	@Nullable
	public String getProperty(String key) {
		return getProperty(key, String.class);
	}
 
	@Override
	public String getProperty(String key, String defaultValue) {
		String value = getProperty(key);
		return (value != null ? value : defaultValue);
	}
 
	@Override
	public <T> T getProperty(String key, Class<T> targetType, T defaultValue) {
		T value = getProperty(key, targetType);
		return (value != null ? value : defaultValue);
	}
 
	@Override
	public String getRequiredProperty(String key) throws IllegalStateException {
		String value = getProperty(key);
		if (value == null) {
			throw new IllegalStateException("Required key '" + key + "' not found");
		}
		return value;
	}
 
	@Override
	public <T> T getRequiredProperty(String key, Class<T> valueType) throws IllegalStateException {
		T value = getProperty(key, valueType);
		if (value == null) {
			throw new IllegalStateException("Required key '" + key + "' not found");
		}
		return value;
	}
 
	@Override
	public String resolvePlaceholders(String text) {
		if (this.nonStrictHelper == null) {
			this.nonStrictHelper = createPlaceholderHelper(true);
		}
		return doResolvePlaceholders(text, this.nonStrictHelper);
	}
 
	@Override
	public String resolveRequiredPlaceholders(String text) throws IllegalArgumentException {
		if (this.strictHelper == null) {
			this.strictHelper = createPlaceholderHelper(false);
		}
		return doResolvePlaceholders(text, this.strictHelper);
	}
 
    // 是否允許占位符數(shù)據(jù)為空,創(chuàng)建不同的解析
	protected String resolveNestedPlaceholders(String value) {
		return (this.ignoreUnresolvableNestedPlaceholders ?
				resolvePlaceholders(value) : resolveRequiredPlaceholders(value));
	}
 
	private PropertyPlaceholderHelper createPlaceholderHelper(boolean ignoreUnresolvablePlaceholders) {
		return new PropertyPlaceholderHelper(this.placeholderPrefix, this.placeholderSuffix,
				this.valueSeparator, ignoreUnresolvablePlaceholders);
	}
 
	private String doResolvePlaceholders(String text, PropertyPlaceholderHelper helper) {
		return helper.replacePlaceholders(text, this::getPropertyAsRawString);
	}
 
 
	@SuppressWarnings("unchecked")
	@Nullable
	protected <T> T convertValueIfNecessary(Object value, @Nullable Class<T> targetType) {
		if (targetType == null) {
			return (T) value;
		}
		ConversionService conversionServiceToUse = this.conversionService;
		if (conversionServiceToUse == null) {
			// Avoid initialization of shared DefaultConversionService if
			// no standard type conversion is needed in the first place...
			if (ClassUtils.isAssignableValue(targetType, value)) {
				return (T) value;
			}
			conversionServiceToUse = DefaultConversionService.getSharedInstance();
		}
		return conversionServiceToUse.convert(value, targetType);
	}
	@Nullable
	protected abstract String getPropertyAsRawString(String key);
 
}

上面AbstractPropertyResolver這么大一段,我們先看對于占位符的解析一塊,resolveNestedPlaceholders方法中ignoreUnresolvableNestedPlaceholders默認(rèn)是false,所以默認(rèn)占位符是不允許為空的。所以調(diào)用resolveRequiredPlaceholders方法。

對于占位符的解析,AbstractPropertyResolver主要是調(diào)用PropertyPlaceholderHelper的通用處理方法對于占位符解析。這邊再來看看PropertyPlaceholderHelper的方法對于占位符的解析;

public class PropertyPlaceholderHelper {
 
	private static final Log logger = LogFactory.getLog(PropertyPlaceholderHelper.class);
 
	private static final Map<String, String> wellKnownSimplePrefixes = new HashMap<>(4);
 
	static {
		wellKnownSimplePrefixes.put("}", "{");
		wellKnownSimplePrefixes.put("]", "[");
		wellKnownSimplePrefixes.put(")", "(");
	}
 
 
	private final String placeholderPrefix;
 
	private final String placeholderSuffix;
 
	private final String simplePrefix;
 
	@Nullable
	private final String valueSeparator;
 
	private final boolean ignoreUnresolvablePlaceholders;
 
 
 
	public PropertyPlaceholderHelper(String placeholderPrefix, String placeholderSuffix) {
		this(placeholderPrefix, placeholderSuffix, null, true);
	}
 
 
	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;
	}
 
 
	public String replacePlaceholders(String value, final Properties properties) {
		Assert.notNull(properties, "'properties' must not be null");
		return replacePlaceholders(value, properties::getProperty);
	}
 
 
	public String replacePlaceholders(String value, PlaceholderResolver placeholderResolver) {
		Assert.notNull(value, "'value' must not be null");
		return parseStringValue(value, placeholderResolver, new HashSet<>());
	}
 
	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) {
                // 去除包裝的字符,得到真正占位符
                //1 比如${abc}得到 abc
				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");
				}
                //2 遞歸調(diào)用
				placeholder = parseStringValue(placeholder, placeholderResolver, visitedPlaceholders);
				//3 獲取配置中的值
				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);        
                        //4 獲取分隔符后的默認(rèn)值
						String defaultValue = placeholder.substring(separatorIndex + this.valueSeparator.length());
						propVal = placeholderResolver.resolvePlaceholder(actualPlaceholder);
						if (propVal == null) {
							propVal = defaultValue;
						}
					}
				}
				if (propVal != null) {
					// 遞歸調(diào)用
					propVal = parseStringValue(propVal, placeholderResolver, visitedPlaceholders);
                    // 將要解析的值把占位符后的值替換成默認(rèn)值
                    //5 如 ${abc} 取到的值是123 那么 ${abc}被替換成123
					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) {
					// 占位符的值允許為空,繼續(xù)向后查找
					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();
	}
 
	private int findPlaceholderEndIndex(CharSequence buf, int startIndex) {
		int index = startIndex + this.placeholderPrefix.length();
		int withinNestedPlaceholder = 0;
		while (index < buf.length()) {
            // 一個(gè)字符一個(gè)字符向后查找
			if (StringUtils.substringMatch(buf, index, this.placeholderSuffix)) {
				if (withinNestedPlaceholder > 0) {
					withinNestedPlaceholder--;
					index = index + this.placeholderSuffix.length();
				}
				else {
					return index;
				}
			}
			else if (StringUtils.substringMatch(buf, index, this.simplePrefix)) {
				withinNestedPlaceholder++;
				index = index + this.simplePrefix.length();
			}
			else {
				index++;
			}
		}
		return -1;
	}
	@FunctionalInterface
	public interface PlaceholderResolver {
 
		@Nullable
		String resolvePlaceholder(String placeholderName);
	}
}

PropertyPlaceholderHelper對于占位符的解析簡單說下,就是查找有沒有被占位符,一般占位符需要有前后包裝才可以,比如${abc}這樣的。

1.去掉占位符的包裝之后,得到內(nèi)部的占位符,但是可能里面還嵌套占位符。

2.遞歸調(diào)用,直到拿到?jīng)]有占位符為止。

3.根據(jù)占位符的值去配置中獲取

4如果獲取不到,占位符中有分隔符,那就拿分隔符后面的值,然后再去分隔符前面的值去取值。取到就用取到的值,取不到就用默認(rèn)的值

5.然后把占位符換成獲取到的值

這邊說的可能有點(diǎn)繞,舉個(gè)例子說下,比如要解析的是${abc:${def:gg}}這樣嵌套的,

那么根據(jù)上面解析就是第一次獲得abc:${def:gg},然后遞歸調(diào)用得到def:gg,那么就根據(jù)def:gg去PropertySource(這個(gè)是AbstractPropertyResolver之前方法中傳遞進(jìn)來的,也就是那個(gè)getPropertyAsRawString方法)中去取數(shù)據(jù),如果取到就用取到的值,如果取不到,就得到分隔符 : 后的gg這個(gè)默認(rèn)值,然后再根據(jù)def去取,取不到就用gg這個(gè)值。然后再去把分隔符后面的值更新成取到的值如gg。那此時(shí)的值就是abc:gg。然后退出遞歸再次用abc:gg繼續(xù)走流程。就和剛才def:gg那種一樣。然后最后或得解析出來的值。

這就是PropertyPlaceholderHelper對于占位符的解析了。

總結(jié)

PropertySource是對鍵值的抽象,PropertyResolver是對PropertySource提供對外的統(tǒng)一數(shù)據(jù)處理,對于占位符的處理委托于PropertyPlaceholderHelper。介紹了這么多主要是為了繼續(xù)下去springBoot源碼啟動(dòng)里面的environment做準(zhǔn)備的。因?yàn)閑nvironment就是包裝PropertySource以及配置文件數(shù)據(jù)的地方。在spring中占有非常重要的地位。

PS:Springboot中PropertySource的數(shù)據(jù)結(jié)構(gòu)及加載過程

記得之前寫過一篇文章分析spring BeanFactory的時(shí)候說過的spring當(dāng)中設(shè)計(jì)很經(jīng)典的一個(gè)點(diǎn)就是 “讀寫分離” 模式。使用這個(gè)模式可以很好的區(qū)分開框架與業(yè)務(wù)的使用上的側(cè)重點(diǎn)。業(yè)務(wù)層不應(yīng)該具有修改框架的特性。

所以講Propertysource我們從Environment開始講。我們知道我們平時(shí)在項(xiàng)目中拿到的Environment對象是只讀,但是它可以被轉(zhuǎn)換成可寫的對象。

在springboot中當(dāng)我們啟動(dòng)一個(gè)servlet應(yīng)用的時(shí)候在prepareEnvironment 階段實(shí)際上是new了一個(gè)StandardServletEnvironment
此時(shí)調(diào)用構(gòu)造函數(shù)放了四個(gè)propertysource進(jìn)去

protected void customizePropertySources(MutablePropertySources propertySources) {
		propertySources.addLast(new StubPropertySource(SERVLET_CONFIG_PROPERTY_SOURCE_NAME));
		propertySources.addLast(new StubPropertySource(SERVLET_CONTEXT_PROPERTY_SOURCE_NAME));
		if (JndiLocatorDelegate.isDefaultJndiEnvironmentAvailable()) {
			propertySources.addLast(new JndiPropertySource(JNDI_PROPERTY_SOURCE_NAME));
		}
		super.customizePropertySources(propertySources);
	}

super.customizePropertySources(propertySources)

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()));
	}

對應(yīng)的名稱分別為

  • servletContextInitParams
  • servletConfigInitParams
  • jndiProperties 可選
  • systemEnvironment
  • systemProperties

對早期項(xiàng)目熟悉的同學(xué)可能,通過這幾個(gè)參數(shù)能立馬知道他們是如何演變過來的。

早期的servlet項(xiàng)目中有個(gè)web.xml配置(那么springboot是如何讓它消失的呢?思考下)。這個(gè)配置中有這樣的標(biāo)簽

 <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>/WEB-INF/springMVC-servlet.xml</param-value>
 </context-param>

<servlet>
        <servlet-name>springMVC</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <init-param>
                 <param-name>home-page</param-name>
                 <param-value>home.jsp</param-value>
        </init-param>
  </servlet>

這些參數(shù)是被servlet容器所解析的,同時(shí)也對spring進(jìn)行了映射,包括jndi配置,即你在容器層面做的配置最終也會(huì)被映射到environment中。此處不是我們當(dāng)前的重點(diǎn)不展開。
現(xiàn)在我們先來看看PropertySource這個(gè)類

PropertySource是個(gè)抽象類代表name/value鍵值對的一個(gè)資源,使用了泛型可以代表任意對象類型,例如可以是java.util.Properties,也可以是java.util.Map等
PropertySource對象通常不單獨(dú)使用,而是通過對象聚合資源屬性,結(jié)合PropertyResolver實(shí)現(xiàn)來解析資源對象,并根據(jù)優(yōu)先級進(jìn)行搜索。
可以使用@PropertySource 注解將對應(yīng)的PropertySource 加入到Enviroment

public abstract class PropertySource<T> {
	protected final Log logger = LogFactory.getLog(getClass());
	protected final String name;
	protected final T source;
	public PropertySource(String name, T source) {
		Assert.hasText(name, "Property source name must contain at least one character");
		Assert.notNull(source, "Property source must not be null");
		this.name = name;
		this.source = source;
	}
	@SuppressWarnings("unchecked")
	public PropertySource(String name) {
		this(name, (T) new Object());
	}
	public String getName() {
		return this.name;
	}
	public T getSource() {
		return this.source;
	}


	public boolean containsProperty(String name) {
		return (getProperty(name) != null);
	}

	@Nullable
	public abstract Object getProperty(String name);

	@Override
	public boolean equals(Object other) {
		return (this == other || (other instanceof PropertySource &&
				ObjectUtils.nullSafeEquals(this.name, ((PropertySource<?>) other).name)));
	}

	@Override
	public int hashCode() {
		return ObjectUtils.nullSafeHashCode(this.name);
	}

	@Override
	public String toString() {
		if (logger.isDebugEnabled()) {
			return getClass().getSimpleName() + "@" + System.identityHashCode(this) +
					" {name='" + this.name + "', properties=" + this.source + "}";
		}
		else {
			return getClass().getSimpleName() + " {name='" + this.name + "'}";
		}
	}
	
	public static PropertySource<?> named(String name) {
		return new ComparisonPropertySource(name);
	}

	//StubPropertySource內(nèi)部類,存根PropertySouece 目的是為了,延遲加載。
	//即有些propertysource使用了一些占位符號,不能早于application context 加載,此時(shí)需要進(jìn)行存根。
	//等到對應(yīng)的資源加載之后再加載當(dāng)前的propertysource。占位符會(huì)在容器的refresh階段被替換,
	//具體解析可以查看AbstractApplicationContext#initPropertySources()
	public static class StubPropertySource extends PropertySource<Object> {

		public StubPropertySource(String name) {
			super(name, new Object());
		}

		/**
		 * Always returns {@code null}.
		 */
		@Override
		@Nullable
		public String getProperty(String name) {
			return null;
		}
	}
	//靜態(tài)內(nèi)部類為了named方法使用,僅僅用戶比較,調(diào)用其它方法會(huì)報(bào)異常,這里是個(gè)適配器模式。
	static class ComparisonPropertySource extends StubPropertySource {

		private static final String USAGE_ERROR =
				"ComparisonPropertySource instances are for use with collection comparison only";

		public ComparisonPropertySource(String name) {
			super(name);
		}

		@Override
		public Object getSource() {
			throw new UnsupportedOperationException(USAGE_ERROR);
		}

		@Override
		public boolean containsProperty(String name) {
			throw new UnsupportedOperationException(USAGE_ERROR);
		}

		@Override
		@Nullable
		public String getProperty(String name) {
			throw new UnsupportedOperationException(USAGE_ERROR);
		}
	}
}

我們可以看到它預(yù)留了一個(gè)抽象方法getProperty 給子類實(shí)現(xiàn),而此方法就是如何獲取每個(gè)propertysource中的屬性的value,此時(shí)就可以有各種各樣的實(shí)現(xiàn)方式

  • 例如:在SystemEnvironmentPropertySource 中 調(diào)用父類MapPropertySource 的getProperty方法實(shí)際是調(diào)用map.get方法獲取對應(yīng)的屬性值
  • 例如:CommandLinePropertySource中實(shí)際是調(diào)用CommandLineArgs的getNonOptionArgs()與getOptionValues(name)方法獲取對應(yīng)的屬性值
  • 例如:apollo實(shí)現(xiàn)的ConfigPropertySource實(shí)際上是調(diào)用System.getProperty(key);

以及Properties對象的get方法獲取的屬性值。

了解完這個(gè)結(jié)構(gòu)之后我們后面再去看配置中心的實(shí)現(xiàn),看起來就容易理解多了,此處按下不表。

其它的不多介紹,具體的類層次結(jié)構(gòu)大家自行觀察。大體上最后的數(shù)據(jù)結(jié)構(gòu)基本上都是從hash表中獲取對應(yīng)的鍵值對。

了解完propertysource的數(shù)據(jù)結(jié)構(gòu)之后,那么問題來了springboot什么時(shí)候加載了配置文件呢?又是如何解析成對應(yīng)的propertysource呢?帶著這個(gè)問題我們將整個(gè)流程貫穿起來看看就知道了。

所以我們先來看看ConfigFileApplicationListener這個(gè)類,如果你問我為什么看這個(gè)類,我會(huì)告訴你你可以全局內(nèi)容搜索application.properties,當(dāng)然最好是你有初略過了一遍springboot源碼在來看會(huì)比較好。

ConfigFileApplicationListener實(shí)現(xiàn)了EnvironmentPostProcessor以及SmartApplicationListener這兩個(gè)接口。我們知道實(shí)現(xiàn)了ApplicationListener接口的類會(huì)在spring啟動(dòng)階段接收到各個(gè)環(huán)節(jié)的事件,所以我們直接查看onApplicationEvent方法

public void onApplicationEvent(ApplicationEvent event) {
		if (event instanceof ApplicationEnvironmentPreparedEvent) {
			onApplicationEnvironmentPreparedEvent(
					(ApplicationEnvironmentPreparedEvent) event);
		}
		if (event instanceof ApplicationPreparedEvent) {
			onApplicationPreparedEvent(event);
		}
	}

我們發(fā)現(xiàn)做了兩個(gè)環(huán)節(jié)的處理,一個(gè)是環(huán)境裝備完成的時(shí)候處理了一次,一個(gè)是容器準(zhǔn)備完成時(shí)處理了一次,這兩次的事件的執(zhí)行時(shí)機(jī)分別如下

  • ApplicationEnvironmentPreparedEvent:prepareEnvironment
  • onApplicationPreparedEvent:prepareContext

在啟動(dòng)過程中prepareEnvironment 先執(zhí)行所以這個(gè)事件的執(zhí)行順序?yàn)榇a的邏輯順序,先進(jìn)第一個(gè)if條件再進(jìn)第二個(gè)條件。

具體來看這兩個(gè)方法
先看onApplicationEnvironmentPreparedEvent,遍歷調(diào)用了一輪postProcessor.postProcessEnvironment

private void onApplicationEnvironmentPreparedEvent(
			ApplicationEnvironmentPreparedEvent event) {
		List<EnvironmentPostProcessor> postProcessors = loadPostProcessors();
		postProcessors.add(this);
		AnnotationAwareOrderComparator.sort(postProcessors);
		for (EnvironmentPostProcessor postProcessor : postProcessors) {
			postProcessor.postProcessEnvironment(event.getEnvironment(),
					event.getSpringApplication());
		}
	}

當(dāng)前類的postPorcessEnvironment方法添加了個(gè)RandomValuePropertySource,并并且new Loader 調(diào)用load方法在load方法中加載了application.properties文件,其它的邏輯就是如何找到這個(gè)文件以及如何加載這個(gè)文件具體細(xì)節(jié)自行研究,不多解釋,加載的時(shí)候用到了PropertySourceLoader,對應(yīng)的PropertySourceLoader有不同的實(shí)現(xiàn),擴(kuò)展名properties,xml使用PropertiesPropertySourceLoader 解析,而
“yml”, "yaml"使用YamlPropertySourceLoader加載,加載完成后就包裝成MapPropertySource子類。并且將其設(shè)置給Environment。

public void load() {
			this.profiles = new LinkedList<>();
			this.processedProfiles = new LinkedList<>();
			this.activatedProfiles = false;
			this.loaded = new LinkedHashMap<>();
			//初始化默認(rèn)的profile=default
			initializeProfiles();
			while (!this.profiles.isEmpty()) {
				Profile profile = this.profiles.poll();
				if (profile != null && !profile.isDefaultProfile()) {
					addProfileToEnvironment(profile.getName());
				}
				load(profile, this::getPositiveProfileFilter,
						addToLoaded(MutablePropertySources::addLast, false));
				this.processedProfiles.add(profile);
			}
			resetEnvironmentProfiles(this.processedProfiles);
			load(null, this::getNegativeProfileFilter,
					addToLoaded(MutablePropertySources::addFirst, true));
			addLoadedPropertySources();
		}

接著我們來看第二個(gè)事件的方法
第二個(gè)事件的方法添加了一個(gè)BeanFactoryPostProcessor 為PropertySourceOrderingPostProcessor,而BeanFactoryPostProcessor 是再refresh的InvokeBeanFactoryPostProcessor 階段執(zhí)行的。我們先看看它是如何執(zhí)行的,postProcessBeanFactory調(diào)用了如下方法

private void reorderSources(ConfigurableEnvironment environment) {
			PropertySource<?> defaultProperties = environment.getPropertySources()
					.remove(DEFAULT_PROPERTIES);
			if (defaultProperties != null) {
				environment.getPropertySources().addLast(defaultProperties);
			}
		}

這個(gè)方法做了一件神奇的事情,因?yàn)槟J(rèn)配置是最先被放到環(huán)境容器中的,所以它在最前面,所以后續(xù)往里又添加了很多其它的propertysource之后,需要將它移動(dòng)到最后,做一個(gè)兜底策略,最終就是取不到配置了再去取默認(rèn)配置。

在結(jié)合開始的時(shí)候的數(shù)據(jù)結(jié)構(gòu),大概我們就可以總結(jié)出如下過程
1、環(huán)境準(zhǔn)備階段,廣播了環(huán)境準(zhǔn)備完成事件
2、調(diào)用listener方法onApplicationEvent去初始化了application.properties文件
3、使用PropertySourceLoader解析對應(yīng)的文件并包裝成propertysource
4、將propertysource設(shè)置給environment
5、容器準(zhǔn)備階段,廣播了容器準(zhǔn)備完成事件
6、調(diào)用listener方法onApplicationEvent去設(shè)置了一個(gè)BeanfactoryPostProcessor
7、在refresh階段調(diào)用了這個(gè)postProcessor,調(diào)整了下默認(rèn)配置文件的順序。

具體的文件解析和占位符替換等等這些動(dòng)作這里先不介紹了。

到此這篇關(guān)于Springboot中PropertySource的數(shù)據(jù)結(jié)構(gòu)及加載過程的文章就介紹到這了,更多相關(guān)Springboot PropertySource數(shù)據(jù)結(jié)構(gòu)內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • Java中覆蓋finalize()方法實(shí)例代碼

    Java中覆蓋finalize()方法實(shí)例代碼

    這篇文章主要介紹了Java中覆蓋finalize()方法實(shí)例代碼,分享了相關(guān)代碼示例,小編覺得還是挺不錯(cuò)的,具有一定借鑒價(jià)值,需要的朋友可以參考下
    2018-02-02
  • Spring Cloud引入Eureka組件,完善服務(wù)治理

    Spring Cloud引入Eureka組件,完善服務(wù)治理

    這篇文章主要介紹了Spring Cloud引入Eureka組件,完善服務(wù)治理的過程詳解,幫助大家更好的理解和使用spring cloud,感興趣的朋友可以了解下
    2021-02-02
  • spring?boot?使用?@Scheduled?注解和?TaskScheduler?接口實(shí)現(xiàn)定時(shí)任務(wù)

    spring?boot?使用?@Scheduled?注解和?TaskScheduler?接口實(shí)現(xiàn)定時(shí)任務(wù)

    這篇文章主要介紹了spring?boot?使用?@Scheduled?注解和?TaskScheduler?接口實(shí)現(xiàn)定時(shí)任務(wù),本文通過實(shí)例代碼給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2023-06-06
  • 自動(dòng)配置@EnableAutoConfiguration問題

    自動(dòng)配置@EnableAutoConfiguration問題

    這篇文章主要介紹了自動(dòng)配置@EnableAutoConfiguration問題,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2023-06-06
  • 最新評論