【Spring】从 PropertyResolver Environment 等类谈一种设计思维
本文已参与「新人创作礼」活动,一起开启掘金创作之路。
前言
我喜欢 Spring 最大的一个原因就是它的设计思维,回归了编程的本质而不是功能的堆砌。同时,精巧的接口、类设计为 Spring 的拓展提供了无限的可能
一直以来,我都尽可能地试图将这些设计思维用在自己的代码中,并加以理解与拓展
本文就 PropertyResolver Environment 相关类谈一种 Spring 的接口设计原则
PropertyResolver
public interface PropertyResolver {
// 属性是否存在
boolean containsProperty(String key);
// 获取属性相关
@Nullable
String getProperty(String key);
String getProperty(String key, String defaultValue);
@Nullable
<T> T getProperty(String key, Class<T> targetType);
<T> T getProperty(String key, Class<T> targetType, T defaultValue);
String getRequiredProperty(String key) throws IllegalStateException;
<T> T getRequiredProperty(String key, Class<T> targetType) throws IllegalStateException;
// 占位符解析
String resolvePlaceholders(String text);
String resolveRequiredPlaceholders(String text) throws IllegalArgumentException;
}
PropertyResolver,属性解析相关,顶层接口
- 首先作为顶层接口,它提供了这个
属性解析最基本的方法:判断属性是否存在、属性获取、占位符解析 - 不难猜测,暴露出这么多的
属性获取相关方法,最终实现肯定是收口到一个方法中的,这便是一种调用者友好的设计原则
ConfigurablePropertyResolver
public interface ConfigurablePropertyResolver extends PropertyResolver {
// 转换服务相关
ConfigurableConversionService getConversionService();
void setConversionService(ConfigurableConversionService conversionService);
// 前缀 后缀 默认值分隔符
void setPlaceholderPrefix(String placeholderPrefix);
void setPlaceholderSuffix(String placeholderSuffix);
void setValueSeparator(@Nullable String valueSeparator);
// 是否允许占位符解析失败
void setIgnoreUnresolvableNestedPlaceholders(boolean ignoreUnresolvableNestedPlaceholders);
// 必选项配置
void setRequiredProperties(String... requiredProperties);
// 必选项校验
void validateRequiredProperties() throws MissingRequiredPropertiesException;
}
Configurable:可配置的
- 这是
Spring最常用的接口设计模式之一:ConfigurableXXX - 可以看到,
PropertyResolver作为顶层接口只提供了最核心的基础API,而对于整个PropertyResolver组件的配置相关方法就被抽到子接口ConfigurablePropertyResolver中,职责分明 - 该接口允许配置
ConversionService相关,因为属性获取涉及类型转换(从PropertyResolver提供的方法也能看出来) - 允许配置
占位符相关属性,因为PropertyResolver支持占位符解析
关于 ConversionService,可以下文了解
AbstractPropertyResolver
这里也是 Spring 常用的一种设计原则:当对应的接口抽象出来后,Spring 通常会提供一个抽象类,将所有共有的部分实现出来,对于 “规范”实现 和 “默认”实现 一般会以 public 和 protected 语义标识
个人理解:
“规范”实现:原则上的最佳实践,一般情况下并不认为需要子类复写
“默认”实现:Spring 仅仅提供一种缺省实现,通常主张或允许子类去复写
比如该抽象实现中
- 首先定义了所有可配置属性,比如
ConfigurableConversionService占位符前后缀等,同时实现对应的配置方法 - 正如前面提到的,所有
getProperty方法(包括containsProperty)被最终收口到<T> T getProperty(String key, Class<T> targetType)中,不难理解,毕竟这是功能最强大的方法(同时也是调用最不“友好”的方法)。同时,该类并不实现该方法,而是由子类个性化实现:这一下子,该“死”的地方“死”,该“活”的地方“活”,有没有体会到这种设计的美感 - 占位符的解析收口到方法
doResolvePlaceholders,其本质是委托给PropertyPlaceholderHelper,并暴露抽象方法getPropertyAsRawString供子类自定义指定占位符解析数据源 - 此类额外提供方法
convertValueIfNecessary,即属性类型转换的默认实现:交给ConversionService实现,允许子类复写
关于 PropertyPlaceholderHelper,可以参考下文
【Spring】PropertyPlaceholderHelper —— 占位符解析工具类
关于上述细节的源码,此处不再张贴,可以自行查看
PropertySourcesPropertyResolver
再往下,职责就很清晰了,复写(实现)对应的方法,根据不同的需求实现不同的子类即可,对于此类,Spring 只提供了一个实现类 PropertySourcesPropertyResolver
- 指定了属性源为
PropertySources,本质就是一组有序的PropertySource - 对应的方法实现基于
PropertySources,见代码
// 基于 getProperty 实现
@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,返回第一个不为 null 的属性
for (PropertySource<?> propertySource : this.propertySources) {
if (logger.isTraceEnabled()) {
logger.trace("Searching for key '" + key + "' in PropertySource '" +
propertySource.getName() + "'");
}
// 委托给 PropertySource#getProperty
Object value = propertySource.getProperty(key);
if (value != null) {
/**
* 对于解析的属性是支持 占位符 解析的
* 当然 getPropertyAsRawString 是获取占位符元素属性的
* 不需要再次解析
*/
if (resolveNestedPlaceholders && value instanceof String) {
value = resolveNestedPlaceholders((String) value);
}
logKeyFound(key, propertySource, value);
// 最后对于类型转换由 convertValueIfNecessary 实现
return convertValueIfNecessary(value, targetValueType);
}
}
}
if (logger.isTraceEnabled()) {
logger.trace("Could not find key '" + key + "' in any property source");
}
return null;
}
getProperty方法本质是委托给PropertySource#getProperty,基于PropertySources的有序性,返回第一个不为null的解析结果- 从
PropertySources中获取的属性,会进行占位符解析 - 最后如果类型不匹配,由
convertValueIfNecessary继续转换 - 职责层层分离后,一个干净又强大的属性解析类
PropertySourcesPropertyResolver就实现了,如果我们有需要,自然也可以自定义拓展,让整个框架富有生命力
Environment
public interface Environment extends PropertyResolver {
String[] getActiveProfiles();
String[] getDefaultProfiles();
@Deprecated
boolean acceptsProfiles(String... profiles);
boolean acceptsProfiles(Profiles profiles);
}
Environment 也是 Spring 的一个核心分支,顶层接口在 PropertyResolver 的基础上拓展了 profile 相关方法
ConfigurableEnvironment
老套路了,Environment 的配置化拓展,其中 getPropertySources 值得一提:返回内部维护的 MutablePropertySources 属性集 ,因而我们可以自定义属性集,拓展 Spring 对应环境属性
AbstractEnvironment
再就是 Spring 提供的抽象实现类了,可以发现,这种设计模式是很实用的,因此 Spring 的应用不在少数
值得一提的是 AbstractEnvironment 的构造方法:
public AbstractEnvironment() {
customizePropertySources(this.propertySources);
}
它允许所有的子类通过复写 customizePropertySources 方法来自定义拓展 propertySources,也是一个让人眼前一亮的设计。比如最常用的 systemEnvironment systemProperties 属性集,就是子类 StandardEnvironment 维护的
总结
总结一下提到的 设计思维:
- 抽象出来的方法尽可能的对
调用者友好,具体的细节可以内部消化 ConfigurableXXX的层级设计,实现接口配置层的“读写分离”- 提供合适的
抽象类,定义方法地规范实现,同时提供良好的可拓展性 职责分明,具体的行为委托给对应的类实现,也是一种策略模式的应用
抛砖引玉,欢迎讨论