我个人在阅读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的一个封装。
更多精彩内容请关注我的微信公众号: