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的一个封装。

更多精彩内容请关注我的微信公众号:
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值