Spring的Environment體系完全講解(涉及PropertySources,Placeholder,PropertyResolver)

我個人在閱讀spring的過程中將spring 分成了幾個體系,因爲我覺得Spring是要給非常優秀的框架,很多設計是可以給我們複用的。比如這裏講解的Spring中的Environment體系。

Environment接口

環境主要分類爲兩大部分:profile,properties

繼承uml圖如下:
在這裏插入圖片描述
圖片有點大而長。但是這樣才詳細。profile這個功能比較簡單這裏不介紹了。

Envirnment體系最重要的方法就是org.springframework.core.env.AbstractEnvironment#customizePropertySources
子類負責繼承這個方法之後進行定製自己的 PropertySources

這裏看看StandardEnvironment,超級簡單。

然後我們再回來看看 AbstractEnvironment 的customizePropertySources函數

	/**
	 * 裝飾方法模式 <br/>
	 * Customize (定做改造) the set of {@link PropertySource} objects to be searched by this
	 * {@code Environment} during calls to {@link #getProperty(String)} and related
	 * methods.
	 *
	 * <p>Subclasses that override this method are encouraged (促使) to add property
	 * sources using {@link MutablePropertySources#addLast(PropertySource)} such that
	 * further subclasses may call {@code super.customizePropertySources()} with
	 * predictable results. For example:
	 * <pre class="code">
	 * public class Level1Environment extends AbstractEnvironment {
	 *     &#064;Override
	 *     protected void customizePropertySources(MutablePropertySources propertySources) {
	 *         super.customizePropertySources(propertySources); // no-op from base class
	 *         propertySources.addLast(new PropertySourceA(...));
	 *         propertySources.addLast(new PropertySourceB(...));
	 *     }
	 * }
	 *
	 * public class Level2Environment extends Level1Environment {
	 *     &#064;Override
	 *     protected void customizePropertySources(MutablePropertySources propertySources) {
	 *         super.customizePropertySources(propertySources); // add all from superclass
	 *         propertySources.addLast(new PropertySourceC(...));
	 *         propertySources.addLast(new PropertySourceD(...));
	 *     }
	 * }
	 * </pre>
	 *
	 * 繼承鏈,能夠像棧一樣。後調用先執行。
	 * <br/>
	 *
	 * In this arrangement(約定), properties will be resolved against sources A, B, C, D in that
	 * order. That is to say that property source "A" has precedence over property source
	 * "D". If the {@code Level2Environment} subclass wished to give property sources C
	 * and D higher precedence than A and B, it could simply call
	 * {@code super.customizePropertySources} after, rather than before adding its own:
	 * <pre class="code">
	 * public class Level2Environment extends Level1Environment {
	 *     &#064;Override
	 *     protected void customizePropertySources(MutablePropertySources propertySources) {
	 *         propertySources.addLast(new PropertySourceC(...));
	 *         propertySources.addLast(new PropertySourceD(...));
	 *         super.customizePropertySources(propertySources); // add all from superclass
	 *     }
	 * }
	 * </pre>
	 * The search order is now C, D, A, B as desired.
	 *
	 * <p>Beyond these recommendations, subclasses may use any of the {@code add&#42;},
	 * {@code remove}, or {@code replace} methods exposed by {@link MutablePropertySources}
	 * in order to create the exact arrangement of property sources desired.
	 *
	 * <p>The base implementation registers no property sources.
	 *
	 * <p>Note that clients of any {@link ConfigurableEnvironment} may further customize
	 * property sources via the {@link #getPropertySources()} accessor, typically within
	 * an {@link org.springframework.context.ApplicationContextInitializer
	 * ApplicationContextInitializer}. For example:
	 * <pre class="code">
	 * ConfigurableEnvironment env = new StandardEnvironment();
	 * env.getPropertySources().addLast(new PropertySourceX(...));
	 * </pre>
	 *
	 * <h2>A warning about instance variable access</h2>
	 * <h2>關於實例對象變量的警告</h2>
	 *
	 * Instance variables(變量) declared in subclasses and having default initial values should
	 * <em>not</em> be accessed from within this method. Due to Java object creation
	 * lifecycle constraints, any initial value will not yet be assigned when this
	 * callback is invoked by the {@link #AbstractEnvironment()} constructor, which may
	 * lead to a {@code NullPointerException} or other problems. If you need to access
	 * default values of instance variables, leave this method as a no-op and perform
	 * property source manipulation and instance variable access directly within the
	 * subclass constructor. Note that <em>assigning</em> values to instance variables is
	 * not problematic; it is only attempting to read default values that must be avoided.
	 *
	 * @see MutablePropertySources
	 * @see PropertySourcesPropertyResolver
	 * @see org.springframework.context.ApplicationContextInitializer
	 */
	protected void customizePropertySources(MutablePropertySources propertySources) {
	}

上面的註解非常詳細了,而且生僻的單詞我都做了詳細的講解,這裏就不細說了。

然後根據上面的註解就可以看看StandardEnvironment

public class StandardEnvironment extends AbstractEnvironment {

	public static final String SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME = "systemEnvironment";

	public static final String SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME = "systemProperties";

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

一個非常典型的創建環境的接口。我們在這裏一般在外面直接使用StandardEnvironment (比較典型的裝飾模式),怎麼使用我們待會再說(先挖坑)。我們先看getSystemProperties()和getSystemEnvironment()是什麼。

其中getSystemProperties()和getSystemEnvironment()是父類AbstractEnvironment的。

org.springframework.core.env.AbstractEnvironment#getSystemProperties
    
@Override
@SuppressWarnings({"rawtypes", "unchecked"})
public Map<String, Object> getSystemProperties() {
    try {
        return (Map) System.getProperties(); // 重點是這個方法
    }
    catch (AccessControlException ex) {
        return (Map) new ReadOnlySystemAttributesMap() {
            @Override
            @Nullable
            protected String getSystemAttribute(String attributeName) {
                try {
                    return System.getProperty(attributeName); // 重點是這個方法
                }
                catch (AccessControlException ex) {
                    if (logger.isInfoEnabled()) {
                        logger.info("Caught AccessControlException when accessing system property '" +
                                    attributeName + "'; its value will be returned [null]. Reason: " + ex.getMessage());
                    }
                    return null;
                }
            }
        };
    }
} 

@Override
@SuppressWarnings({"rawtypes", "unchecked"})
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;
                }
            }
        };
    }
}

從上面的代碼大意就是所謂的getSystemProperties()和getSystemEnvironment()其實是調用System的方法getProperties()getenv()

這裏的實現很有意思,如果安全管理器阻止獲取全部的系統屬性,那麼會嘗試獲取單個屬性的可能性,如果還不行就拋異常了。

System.getProperties() 註解如下:

public static Properties getProperties() {
    SecurityManager sm = getSecurityManager();
    if (sm != null) {
        sm.checkPropertiesAccess();
    }

    return props;
}

在這裏插入圖片描述在這裏插入圖片描述
這是上面的類的說明。

getSystemEnvironment方法也是一個套路,不過最終調用的是System.getenv,可以獲取jvm和OS的一些版本信息。

和 System.getenv()

public static java.util.Map<String,String> getenv() {
    SecurityManager sm = getSecurityManager();
    if (sm != null) {
        sm.checkPermission(new RuntimePermission("getenv.*"));
    }

    return ProcessEnvironment.getenv();
}

看明白了getSystemProperties()和getSystemEnvironment(),那麼我們就要看後面的大餐

public static final String SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME = "systemEnvironment";
public static final String SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME = "systemProperties";
// 省略了這裏不關注的代碼
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()));
}

new PropertiesPropertySource()new SystemEnvironmentPropertySource()到底是什麼鬼呢?看下面!

PropertySources接口

MutablePropertySources和PropertySource,組合模式。
先看看什麼是PropertySources

public interface PropertySources extends Iterable<PropertySource<?>> {

可以看出它是一個含有多個PropertySource 的對象。
那麼什麼是 PropertySource 呢?

public abstract class PropertySource<T> {

	protected final Log logger = LogFactory.getLog(getClass());

	protected final String name;

	protected final T source;


	/**
	 * Create a new {@code PropertySource} with the given name and source object.
	 * @param name the associated name
	 * @param source the source object
	 */
	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;
	}
// 省略以下代碼

PropertySource 是一個name/value 的property對。

在這裏插入圖片描述
PropertySources 接口實際上是PropertySource的容器,默認的MutablePropertySources實現內部含有一個CopyOnWriteArrayList作爲存儲載體。
所以對MutablePropertySources的操作都是對propertySourceList的操作。
比如

public void addFirst(PropertySource<?> propertySource) {
	removeIfPresent(propertySource);
	this.propertySourceList.add(0, propertySource);
}

public void addLast(PropertySource<?> propertySource) {
	removeIfPresent(propertySource);
	this.propertySourceList.add(propertySource);
}

不細講了。
藉助別人的一張圖
PropertySource接口代表了鍵值對的Property來源。繼承體系:

public class MapPropertySource extends EnumerablePropertySource<Map<String, Object>> {
	public MapPropertySource(String name, Map<String, Object> source) {
		super(name, source);
	}
    // ... 省略了代碼
}

public abstract class EnumerablePropertySource<T> extends PropertySource<T> {
	public EnumerablePropertySource(String name, T source) {
		super(name, source);
	}
	// ... 省略了代碼

public class SystemEnvironmentPropertySource extends MapPropertySource {
	public SystemEnvironmentPropertySource(String name, Map<String, Object> source) {
		super(name, source);
	}

從上面的代碼可以看出 MapPropertySourceSystemEnvironmentPropertySource 其實就是一個單個的key-Map<name, properties>的結構。

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

結論:整個 customizePropertySources 函數(如下)的作用就是將System.getProperties(), System.getEnv()的key-value放入到MutablePropertySources的propertySourceList(CopyOnWriteArrayList類型)之中。即Spring對System.getProperties(), System.getEnv()封裝了一層。

Spring大費周章的封裝了System.getProperties(), System.getEnv(),那我們怎麼使用呢?接着看:

Placeholder

路徑Placeholder處理。
AbstractEnvironment.resolveRequiredPlaceholders

@Override
public String resolveRequiredPlaceholders(String text) throws IllegalArgumentException {
    //text即配置文件路徑,比如classpath:config.xml
    return this.propertyResolver.resolveRequiredPlaceholders(text);
}

propertyResolver是一個PropertySourcesPropertyResolver對象:

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

PropertyResolver接口

PropertyResolver繼承體系(排除在Environment分支):
在這裏插入圖片描述
此接口正是用來解析PropertyResource 的。

解析

AbstractPropertyResolver.resolveRequiredPlaceholders:

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

private PropertyPlaceholderHelper createPlaceholderHelper(boolean ignoreUnresolvablePlaceholders) {
    //三個參數分別是${, }, :
    return new PropertyPlaceholderHelper(this.placeholderPrefix, this.placeholderSuffix,
        this.valueSeparator, ignoreUnresolvablePlaceholders);
}

doResolvePlaceholders:

private String doResolvePlaceholders(String text, PropertyPlaceholderHelper helper) {
    //PlaceholderResolver接口依然是策略模式的體現
    return helper.replacePlaceholders(text, new PropertyPlaceholderHelper.PlaceholderResolver() {
        @Override
        public String resolvePlaceholder(String placeholderName) {
            return getPropertyAsRawString(placeholderName);
        }
    });
}

其實代碼執行到這裏的時候還沒有進行xml配置文件的解析,那麼這裏的解析placeHolder是什麼意思呢,原因在於我們寫一些配置可以這麼寫:

System.setProperty("spring", "classpath");
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("${spring}:config.xml");
SimpleBean bean = context.getBean(SimpleBean.class);

這樣就可以正確解析。placeholder的替換其實就是字符串操作,這裏只說一下正確的屬性是怎麼來的。實現的關鍵在於PropertySourcesPropertyResolver.getProperty:

@Override
protected String getPropertyAsRawString(String key) {
    return getProperty(key, String.class, false);
}
protected <T> T getProperty(String key, Class<T> targetValueType, boolean resolveNestedPlaceholders) {
    if (this.propertySources != null) {
        for (PropertySource<?> propertySource : this.propertySources) {
            Object value = propertySource.getProperty(key);
            return value;
        }
    }
    return null;
}

很明顯了,就是從System.getProperty和System.getenv獲取,但是由於環境變量是無法自定義的,所以其實此處只能通過System.setProperty指定。

佔位符的真正實現在這裏:

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

注意,classpath:XXX這種寫法的classpath前綴到目前爲止還沒有被處理。

佔位符體系嘗試

PropertyPlaceholderHelper propertyPlaceholderHelper = new PropertyPlaceholderHelper("${", "}");
MutablePropertySources propertySources = new MutablePropertySources();
Properties properties = new Properties();
properties.put("test", "ydonghao");
System.out.println(propertyPlaceholderHelper.replacePlaceholders("${test}", properties));

output:

this is a placeholder : ydonghao

然後試試environment使用方法:

public class EsEnvironment extends AbstractEnvironment{

	private static final String WDPH_ES_PROPERTIES = "wdphEsProperties";

	@Override
	protected void customizePropertySources(MutablePropertySources propertySources) {
		Properties properties = new Properties();
		properties.put("test", "ydonghao");
		properties.put("test1", "ydonghao1");
		properties.put("test2", "ydonghao2");
		propertySources.addLast(
				new PropertiesPropertySource(WDPH_ES_PROPERTIES, properties));

	}

	public static void main(String[] args) {
		EsEnvironment esEnvironment = new EsEnvironment();
		System.out.println(esEnvironment.resolvePlaceholders("This is a placeholder : ${test33:wewrwe}..."));
		System.out.println(esEnvironment.resolveRequiredPlaceholders("This is a placeholder : ${test33:wewrwe}..."));
		System.out.println(esEnvironment.resolveRequiredPlaceholders("This is a placeholder : ${test33}..."));   //這個會拋出異常。
	}

}

output

This is a placeholder : wewrwe...
This is a placeholder : wewrwe...
Exception in thread "main" java.lang.IllegalArgumentException: Could not resolve placeholder 'test33' in value "This is a placeholder : ${test33}..."
	at org.springframework.util.PropertyPlaceholderHelper.parseStringValue(PropertyPlaceholderHelper.java:178)
	at org.springframework.util.PropertyPlaceholderHelper.replacePlaceholders(PropertyPlaceholderHelper.java:124)
	at org.springframework.core.env.AbstractPropertyResolver.doResolvePlaceholders(AbstractPropertyResolver.java:239)
	at org.springframework.core.env.AbstractPropertyResolver.resolveRequiredPlaceholders(AbstractPropertyResolver.java:210)
	at org.springframework.core.env.AbstractEnvironment.resolveRequiredPlaceholders(AbstractEnvironment.java:578)
	at org.springframework.core.env.EsEnvironment.main(EsEnvironment.java:32)

在這裏插入圖片描述
結論:propertiesSources/propertiesSource是spring對jdk的property的一個封裝。

更多精彩內容請關注我的微信公衆號:
在這裏插入圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章