我個人在閱讀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 {
* @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 {
* @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 {
* @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*},
* {@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);
}
不細講了。
藉助別人的一張圖
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);
}
從上面的代碼可以看出 MapPropertySource
和 SystemEnvironmentPropertySource
其實就是一個單個的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的一個封裝。
更多精彩內容請關注我的微信公衆號: