关于Spring属性处理器PropertyResolver以及应用运行环境Environment的深度分析,强大的StringValueResolver使用和解析【享学Spring】
前言若直接说PropertyResolver或者StringValueResolver可能很多人都会非常的陌生,但是我若提到EmbeddedValueResolverAware这个Spring为我们提供的感知接口,相信很多小伙伴就会感到亲切了。我们的任何一个Bean实现了EmbeddedValueResolverAware接口的话,Spring容器再启动的时候就会自动给我们set进来一个Str..
每篇一句
见贤思齐,见不贤而内自省。 所以建议可多看看你身边同事那“屎”一样代码~
前言
若直接提PropertyResolver
或者StringValueResolver
可能很小伙伴会觉得非常的陌生,但是我若提Environment
和EmbeddedValueResolverAware
这个感知接口,相信大部分小伙伴就能感受到一种亲切感了~。
我们的任何一个Spring Bean若实现了EmbeddedValueResolverAware
接口,Spring容器在启动的时候就会自动给我们我们的Bean注入进来一个StringValueResolver
。然后我们借用这个resolver就能处理一系列字符串的逻辑比如:占位符解释、SpEL计算等等~
本文就是以StringValueResolver
为引子,去剖析它的底层依赖逻辑:PropertyResolver
和Environment
PropertyResolver
org.springframework.core.env.PropertyResolver
此接口用于在底层源之上解析一系列的属性值:例如properties文件,yaml文件,甚至是一些nosql(因为nosql也是k-v形式)。
接口中定义了一系列读取,解析,判断是否包含指定属性的方法:
// @since 3.1 出现得还是相对较晚的 毕竟SpEL也3.0之后才出来嘛~~~
public interface PropertyResolver {
// 查看规定指定的key是否有对应的value 注意:若对应值是null的话 也是返回false
boolean containsProperty(String key);
// 如果没有则返回null
@Nullable
String getProperty(String key);
// 如果没有则返回defaultValue
String getProperty(String key, String defaultValue);
// 返回指定key对应的value,会解析成指定类型。如果没有对应值则返回null(而不是抛错~)
@Nullable
<T> T getProperty(String key, Class<T> targetType);
<T> T getProperty(String key, Class<T> targetType, T defaultValue);
// 若不存在就不是返回null了 而是抛出异常~ 所以不用担心返回值是null
String getRequiredProperty(String key) throws IllegalStateException;
<T> T getRequiredProperty(String key, Class<T> targetType) throws IllegalStateException;
// 解析${...}这种类型的占位符,把他们替换为使用getProperty方法返回的结果,解析不了并且没有默认值的占位符会被忽略(原样输出)
String resolvePlaceholders(String text);
// 解析不了就抛出异常~
String resolveRequiredPlaceholders(String text) throws IllegalArgumentException;
}
它的继承链可以描述如下:PropertyResolver -> ConfigurablePropertyResolver -> AbstractPropertyResolver -> PropertySourcesPropertyResolver
当然还有
Environment
分支,这里放在下面Environment
章节继续解释说明~
ConfigurablePropertyResolver
顾名思义,它是一个可配置的处理器。这个方法不仅有父接口所有功能,还扩展定义类型转换、属性校验、前缀、后缀、分隔符
等一些列的功能,这个在具体实现类里有所体现~
public interface ConfigurablePropertyResolver extends PropertyResolver {
// 返回在解析属性时使用的ConfigurableConversionService。此方法的返回值可被用户定制化set
// 例如可以移除或者添加Converter cs.addConverter(new FooConverter());等等
ConfigurableConversionService getConversionService();
// 全部替换ConfigurableConversionService的操作(不常用) 一般还是get出来操作它内部的东东
void setConversionService(ConfigurableConversionService conversionService);
// 设置占位符的前缀 后缀 默认是${}
void setPlaceholderPrefix(String placeholderPrefix);
void setPlaceholderSuffix(String placeholderSuffix);
// 默认值的分隔符 默认为冒号:
void setValueSeparator(@Nullable String valueSeparator);
// 是否忽略解析不了的占位符,默认是false 表示不忽略~~~(解析不了就抛出异常)
void setIgnoreUnresolvableNestedPlaceholders(boolean ignoreUnresolvableNestedPlaceholders);
/**
* Specify which properties must be present, to be verified by
* {@link #validateRequiredProperties()}.
*/
void setRequiredProperties(String... requiredProperties);
void validateRequiredProperties() throws MissingRequiredPropertiesException;
}
ConfigurableXXX
成了Spring的一种命名规范,或者说是一种设计模式。它表示课配置的,所以都会提供大量的set方法
Spring很多接口都是读写分离的,最顶层接口一般都只会提供只读方法,这是Spring框架设计的一般规律之一
AbstractPropertyResolver
它是对ConfigurablePropertyResolver
的一个抽象实现,实现了了所有的接口方法,并且只提供一个抽象方法给子类去实现~~~
// @since 3.1
public abstract class AbstractPropertyResolver implements ConfigurablePropertyResolver {
@Nullable
private volatile ConfigurableConversionService conversionService;
// PropertyPlaceholderHelper是一个极其独立的类,专门用来解析占位符 我们自己项目中可议拿来使用 因为它不依赖于任何其他类
@Nullable
private PropertyPlaceholderHelper nonStrictHelper;
@Nullable
private PropertyPlaceholderHelper strictHelper;
private boolean ignoreUnresolvableNestedPlaceholders = false;
private String placeholderPrefix = SystemPropertyUtils.PLACEHOLDER_PREFIX;
private String placeholderSuffix = SystemPropertyUtils.PLACEHOLDER_SUFFIX;
@Nullable
private String valueSeparator = SystemPropertyUtils.VALUE_SEPARATOR;
private final Set<String> requiredProperties = new LinkedHashSet<>();
// 默认值使用的DefaultConversionService
@Override
public ConfigurableConversionService getConversionService() {
// Need to provide an independent DefaultConversionService, not the
// shared DefaultConversionService used by PropertySourcesPropertyResolver.
ConfigurableConversionService cs = this.conversionService;
if (cs == null) {
synchronized (this) {
cs = this.conversionService;
if (cs == null) {
cs = new DefaultConversionService();
this.conversionService = cs;
}
}
}
return cs;
}
... // 省略get/set
@Override
public void setRequiredProperties(String... requiredProperties) {
for (String key : requiredProperties) {
this.requiredProperties.add(key);
}
}
// 校验这些key~
@Override
public void validateRequiredProperties() {
MissingRequiredPropertiesException ex = new MissingRequiredPropertiesException();
for (String key : this.requiredProperties) {
if (this.getProperty(key) == null) {
ex.addMissingRequiredProperty(key);
}
}
if (!ex.getMissingRequiredProperties().isEmpty()) {
throw ex;
}
}
... //get/set property等方法省略 直接看处理占位符的方法即可
@Override
public String resolvePlaceholders(String text) {
if (this.nonStrictHelper == null) {
this.nonStrictHelper = createPlaceholderHelper(true);
}
return doResolvePlaceholders(text, this.nonStrictHelper);
}
private PropertyPlaceholderHelper createPlaceholderHelper(boolean ignoreUnresolvablePlaceholders) {
return new PropertyPlaceholderHelper(this.placeholderPrefix, this.placeholderSuffix, this.valueSeparator, ignoreUnresolvablePlaceholders);
}
// 此处:最终都是委托给PropertyPlaceholderHelper去做 而getPropertyAsRawString是抽象方法 根据key返回一个字符串即可~
private String doResolvePlaceholders(String text, PropertyPlaceholderHelper helper) {
return helper.replacePlaceholders(text, this::getPropertyAsRawString);
}
}
最终,处理占位的核心逻辑在PropertyPlaceholderHelper
身上,这个类不可小觑,是一个与业务无关非常强大的工具类,我们可以直接拿来主义~
PropertyPlaceholderHelper
将字符串里的占位符内容,用我们配置的properties里的替换。这个是一个单纯的类,没有继承没有实现,而且简单无依赖,没有依赖Spring框架其他的任何类。
个人感觉自己项目中可以拿来模仿或者直接使用。
// @since 3.0 Utility class for working with Strings that have placeholder values in them
public class PropertyPlaceholderHelper {
// 这里保存着 通用的熟悉的 开闭的符号们~~~
private static final Map<String, String> wellKnownSimplePrefixes = new HashMap<>(4);
static {
wellKnownSimplePrefixes.put("}", "{");
wellKnownSimplePrefixes.put("]", "[");
wellKnownSimplePrefixes.put(")", "(");
}
private final String placeholderPrefix;
private final String placeholderSuffix;
private final String simplePrefix;
@Nullable
private final String valueSeparator;
private final boolean ignoreUnresolvablePlaceholders; // 是否采用严格模式~~
// 从properties里取值 若你有就直接从Properties里取值了~~~
public String replacePlaceholders(String value, final Properties properties) {
Assert.notNull(properties, "'properties' must not be null");
return replacePlaceholders(value, properties::getProperty);
}
// @since 4.3.5 抽象类提供这个类型转换的方法~ 需要类型转换的会调用它
// 显然它是委托给了ConversionService,而这个类在前面文章已经都重点分析过了~
@Nullable
protected <T> T convertValueIfNecessary(Object value, @Nullable Class<T> targetType) {
if (targetType == null) {
return (T) value;
}
ConversionService conversionServiceToUse = this.conversionService;
if (conversionServiceToUse == null) {
// Avoid initialization of shared DefaultConversionService if
// no standard type conversion is needed in the first place...
if (ClassUtils.isAssignableValue(targetType, value)) {
return (T) value;
}
conversionServiceToUse = DefaultConversionService.getSharedInstance();
}
return conversionServiceToUse.convert(value, targetType);
}
// 这里会使用递归,根据传入的符号,默认值等来处理~~~~
protected String parseStringValue(String value, PlaceholderResolver placeholderResolver, Set<String> visitedPlaceholders) { ... }
}
这个工具类不仅仅用在此处,在
ServletContextPropertyUtils
、SystemPropertyUtils
、PropertyPlaceholderConfigurer
里都是有使用到它的
PropertySourcesPropertyResolver
从上面知道AbstractPropertyResolver
封装了解析占位符的具体实现。PropertySourcesPropertyResolver
作为它的子类它只需要提供数据源,所以它主要是负责提供数据源。
// @since 3.1 PropertySource:就是我们所说的数据源,它是Spring一个非常重要的概念,比如可以来自Map,来自命令行、来自自定义等等~~~
public class PropertySourcesPropertyResolver extends AbstractPropertyResolver {
// 数据源们~
@Nullable
private final PropertySources propertySources;
// 唯一构造函数:必须制定数据源~
public PropertySourcesPropertyResolver(@Nullable PropertySources propertySources) {
this.propertySources = propertySources;
}
@Override
public boolean containsProperty(String key) {
if (this.propertySources != null) {
for (PropertySource<?> propertySource : this.propertySources) {
if (propertySource.containsProperty(key)) {
return true;
}
}
}
return false;
}
...
//最终依赖的都是propertySource.getProperty(key);方法拿到如果是字符串的话
//就继续交给 value = resolveNestedPlaceholders((String) value);处理
@Nullable
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);
if (value != null) {
// 若值是字符串,那就处理一下占位符~~~~~~ 所以我们看到所有的PropertySource都是支持占位符的
if (resolveNestedPlaceholders && value instanceof String) {
value = resolveNestedPlaceholders((String) value);
}
logKeyFound(key, propertySource, value);
return convertValueIfNecessary(value, targetValueType);
}
}
}
return null;
}
...
}
PropertySources
和PropertySource
属性源是Spring里一个非常重要的概念设计,涉及到Spring属性配置的非常重要的优先级关系、以及它支持的配置类型。
Environment
这个接口代表了当前应用正在运行的环境,为应用的两个重要方面建立抽象模型 【profiles
】和【properties
】。关于属性访问的方法通过父接口PropertyResolver
暴露给客户端使用,本接口主要是扩展出访问【profiles
】相关的接口。
对于他俩,我愿意这么来翻译:
profiles
:配置。它代表应用在一启动时注册到context中bean definitions的命名的逻辑分组。properties
:属性。几乎在所有应用中都扮演着重要角色,他可能源自多种源头。例如属性文件,JVM系统属性,系统环境变量,JNDI,servlet上下文参数,Map等等,Environment对象和其相关的对象一起提供给用户一个方便用来配置和解析属性的服务。
// @since 3.1 可见Spring3.x版本是Spirng一次极其重要的跨越、升级
// 它继承自PropertyResolver,所以是对属性的一个扩展~
public interface Environment extends PropertyResolver {
// 就算被激活 也是支持同时激活多个profiles的~
// 设置的key是:spring.profiles.active
String[] getActiveProfiles();
// 默认的也可以有多个 key为:spring.profiles.default
String[] getDefaultProfiles();
// 看看传入的profiles是否是激活的~~~~ 支持!表示不激活
@Deprecated
boolean acceptsProfiles(String... profiles);
// Spring5.1后提供的 用于替代上面方法 Profiles是Spring5.1才有的一个函数式接口~
boolean acceptsProfiles(Profiles profiles);
}
我们可以通过实现接口EnvironmentAware
或者直接@Autowired
可以很方便的得到当前应用的环境:Environment
。
稍微解释一下:若你是普通的Spring MVC环境(非Boot)也只会有一个
Environment
的,因为Spring容器内部会判断若你已经实例化过Environment
就不会再重复实例化了~
它有如下实现:
ConfigurableEnvironment
扩展出了修改
和配置profiles的一系列方法,包括用户自定义的和系统相关的属性。所有的环境实现类也都是它的实现~
// @since 3.1
public interface ConfigurableEnvironment extends Environment, ConfigurablePropertyResolver {
void setActiveProfiles(String... profiles);
void addActiveProfile(String profile);
void setDefaultProfiles(String... profiles);
// 获取到所有的属性源~ MutablePropertySources表示可变的属性源们~~~ 它是一个聚合的 持有List<PropertySource<?>>
// 这样获取出来后,我们可以add或者remove我们自己自定义的属性源了~
MutablePropertySources getPropertySources();
// 这里两个哥们应该非常熟悉了吧~~~
Map<String, Object> getSystemProperties();
Map<String, Object> getSystemEnvironment();
// 合并两个环境配置信息~ 此方法唯一实现在AbstractEnvironment上
void merge(ConfigurableEnvironment parent);
}
它会有两个分支:
ConfigurableWebEnvironment
:显然它和web环境有关,提供方法void initPropertySources(@Nullable ServletContext servletContext, @Nullable ServletConfig servletConfig)
让web自己做资源初始化~AbstractEnvironment
:这个是重点,如下~
AbstractEnvironment
它是对环境的一个抽象实现,很重要。
public abstract class AbstractEnvironment implements ConfigurableEnvironment {
public static final String IGNORE_GETENV_PROPERTY_NAME = "spring.getenv.ignore";
public static final String ACTIVE_PROFILES_PROPERTY_NAME = "spring.profiles.active";
public static final String DEFAULT_PROFILES_PROPERTY_NAME = "spring.profiles.default";
// 保留的默认的profile值 protected final属性,证明子类可以访问
protected static final String RESERVED_DEFAULT_PROFILE_NAME = "default";
private final Set<String> activeProfiles = new LinkedHashSet<>();
// 显然这个里面的值 就是default这个profile了~~~~
private final Set<String> defaultProfiles = new LinkedHashSet<>(getReservedDefaultProfiles());
// 这个很关键,直接new了一个 MutablePropertySources来管理属性源们
// 并且是用的PropertySourcesPropertyResolver来处理里面可能的占位符~~~~~
private final MutablePropertySources propertySources = new MutablePropertySources();
private final ConfigurablePropertyResolver propertyResolver = new PropertySourcesPropertyResolver(this.propertySources);
// 唯一构造方法 customizePropertySources是空方法,交由子类去实现,对属性源进行定制~
// Spring对属性配置分出这么多曾经,在SpringBoot中有着极其重要的意义~~~~
public AbstractEnvironment() {
customizePropertySources(this.propertySources);
}
// 该方法,StandardEnvironment实现类是有复写的~
protected void customizePropertySources(MutablePropertySources propertySources) {
}
// 若你想改变默认default这个值,可以复写此方法~~~~
protected Set<String> getReservedDefaultProfiles() {
return Collections.singleton(RESERVED_DEFAULT_PROFILE_NAME);
}
// 下面开始实现接口的方法们~~~~~~~
@Override
public String[] getActiveProfiles() {
return StringUtils.toStringArray(doGetActiveProfiles());
}
protected Set<String> doGetActiveProfiles() {
synchronized (this.activeProfiles) {
if (this.activeProfiles.isEmpty()) {
// 若目前是empty的,那就去获取:spring.profiles.active
String profiles = getProperty(ACTIVE_PROFILES_PROPERTY_NAME);
if (StringUtils.hasText(profiles)) {
//支持,分隔表示多个~~~且空格啥的都无所谓
setActiveProfiles(StringUtils.commaDelimitedListToStringArray(
StringUtils.trimAllWhitespace(profiles)));
}
}
return this.activeProfiles;
}
}
@Override
public void setActiveProfiles(String... profiles) {
synchronized (this.activeProfiles) {
this.activeProfiles.clear(); // 因为是set方法 所以情况已存在的吧
for (String profile : profiles) {
// 简单的valid,不为空且不以!打头~~~~~~~~
validateProfile(profile);
this.activeProfiles.add(profile);
}
}
}
// default profiles逻辑类似,也是不能以!打头~
@Override
@Deprecated
public boolean acceptsProfiles(String... profiles) {
for (String profile : profiles) {
// 此处:如果该profile以!开头,那就截断出来 把后半段拿出来看看 它是否在active行列里~~~
// 此处稍微注意:若!表示一个相反的逻辑~~~~~请注意比如!dev表示若dev是active的,我反倒是不生效的
if (StringUtils.hasLength(profile) && profile.charAt(0) == '!') {
if (!isProfileActive(profile.substring(1))) {
return true;
}
} else if (isProfileActive(profile)) {
return true;
}
}
return false;
}
// 采用函数式接口处理 就非常的优雅了~
@Override
public boolean acceptsProfiles(Profiles profiles) {
Assert.notNull(profiles, "Profiles must not be null");
return profiles.matches(this::isProfileActive);
}
// 简答的说要么active包含,要门是default 这个profile就被认为是激活的
protected boolean isProfileActive(String profile) {
validateProfile(profile);
Set<String> currentActiveProfiles = doGetActiveProfiles();
return (currentActiveProfiles.contains(profile) ||
(currentActiveProfiles.isEmpty() && doGetDefaultProfiles().contains(profile)));
}
@Override
public MutablePropertySources getPropertySources() {
return this.propertySources;
}
public Map<String, Object> getSystemProperties() {
return (Map) System.getProperties();
}
public Map<String, Object> getSystemEnvironment() {
// 这个判断为:return SpringProperties.getFlag(IGNORE_GETENV_PROPERTY_NAME);
// 所以我们是可以通过在`spring.properties`这个配置文件里spring.getenv.ignore=false关掉不暴露环境变量的~~~
if (suppressGetenvAccess()) {
return Collections.emptyMap();
}
return (Map) System.getenv();
}
// Append the given parent environment's active profiles, default profiles and property sources to this (child) environment's respective collections of each.
// 把父环境的属性合并进来~~~~
// 在调用ApplicationContext.setParent方法时,会把父容器的环境合并进来 以保证父容器的属性对子容器都是可见的
@Override
public void merge(ConfigurableEnvironment parent) {
for (PropertySource<?> ps : parent.getPropertySources()) {
if (!this.propertySources.contains(ps.getName())) {
this.propertySources.addLast(ps); // 父容器的属性都放在最末尾~~~~
}
}
// 合并active
String[] parentActiveProfiles = parent.getActiveProfiles();
if (!ObjectUtils.isEmpty(parentActiveProfiles)) {
synchronized (this.activeProfiles) {
for (String profile : parentActiveProfiles) {
this.activeProfiles.add(profile);
}
}
}
// 合并default
String[] parentDefaultProfiles = parent.getDefaultProfiles();
if (!ObjectUtils.isEmpty(parentDefaultProfiles)) {
synchronized (this.defaultProfiles) {
this.defaultProfiles.remove(RESERVED_DEFAULT_PROFILE_NAME);
for (String profile : parentDefaultProfiles) {
this.defaultProfiles.add(profile);
}
}
}
}
// 其余方法全部委托给内置的propertyResolver属性,因为它就是个`PropertyResolver`
...
}
该抽象类完成了对active、default等相关方法的复写处理。它内部持有一个MutablePropertySources
引用来管理属性源。
So,留给子类的活就不多了:只需要把你的属性源注册给我就OK了
StandardEnvironment
这个是Spring应用在非web容器运行的环境。从名称上解释为:标准实现
public class StandardEnvironment extends AbstractEnvironment {
// 这两个值定义着 就是在@Value注解要使用它们时的key~~~~~
/** System environment property source name: {@value}. */
public static final String SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME = "systemEnvironment";
/** JVM system properties property source name: {@value}. */
public static final String SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME = "systemProperties";
// 注册MapPropertySource和SystemEnvironmentPropertySource
// SystemEnvironmentPropertySource是MapPropertySource的子类~~~~
@Override
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()));
}
}
StandardServletEnvironment
这是在web容器(servlet容器)时候的应用的标准环境。
public class StandardServletEnvironment extends StandardEnvironment implements ConfigurableWebEnvironment {
public static final String SERVLET_CONTEXT_PROPERTY_SOURCE_NAME = "servletContextInitParams";
public static final String SERVLET_CONFIG_PROPERTY_SOURCE_NAME = "servletConfigInitParams";
public static final String JNDI_PROPERTY_SOURCE_NAME = "jndiProperties";
// 放置三个web相关的配置源~ StubPropertySource是PropertySource的一个public静态内部类~~~
@Override
protected void customizePropertySources(MutablePropertySources propertySources) {
propertySources.addLast(new StubPropertySource(SERVLET_CONFIG_PROPERTY_SOURCE_NAME));
propertySources.addLast(new StubPropertySource(SERVLET_CONTEXT_PROPERTY_SOURCE_NAME));
// 可以通过spring.properties配置文件里面的spring.jndi.ignore=true关闭对jndi的暴露 默认是开启的
if (JndiLocatorDelegate.isDefaultJndiEnvironmentAvailable()) {
propertySources.addLast(new JndiPropertySource(JNDI_PROPERTY_SOURCE_NAME));
}
super.customizePropertySources(propertySources);
}
// 注册servletContextInitParams和servletConfigInitParams到属性配置源头里
@Override
public void initPropertySources(@Nullable ServletContext servletContext, @Nullable ServletConfig servletConfig) {
WebApplicationContextUtils.initServletPropertySources(getPropertySources(), servletContext, servletConfig);
}
}
注意:这里addFirst和addLast等关系这顺序,进而都关乎着配置最终的生效的。
因此下面对比一下web环境和非web环境下属性源们的配置,各位要有感官上的一个认识~~~
非web环境:
web环境:
可见web相关配置的属性源的优先级是高于system相关的。
需要注意的是:若使用
@PropertySource
导入自定义配置,它会位于最底端(优先级最低)
另外附上SpringBoot的属性源们:
访问:http://localhost:8080/env
得到如下
EnvironmentCapable、EnvironmentAware
实现了此接口的类都应该有一个Environment类型的环境,并且可以通过getEnvironment
方法取得。
我们熟知的所有的Spring应用上下文都实现了这个接口,因为ApplictionContext
就实现了这个接口,表示每个应用上下文都是有自己的运行时环境的
还有HttpServletBean、GenericFilterBean
它们既实现了EnvironmentCapable
也实现了EnvironmentAware
用于获取到这个环境对象。
ClassPathScanningCandidateComponentProvider
也实现了它如下代码:
public class ClassPathScanningCandidateComponentProvider implements EnvironmentCapable, ResourceLoaderAware {
...
@Override
public final Environment getEnvironment() {
if (this.environment == null) {
this.environment = new StandardEnvironment();
}
return this.environment;
}
...
}
StringValueResolver
分析完了PropertyResolver
和Environment
之后,来到我们今天的主菜
:StringValueResolver
。
先说说StringValueResolver
本身,Spring对它的定义为:一个处理字符串的简单策略接口。
// @since 2.5 该接口非常简单,就是个函数式接口~
@FunctionalInterface
public interface StringValueResolver {
@Nullable
String resolveStringValue(String strVal);
}
唯一public
实现类为:EmbeddedValueResolver
EmbeddedValueResolver
帮助ConfigurableBeanFactory
处理placeholders占位符的。ConfigurableBeanFactory#resolveEmbeddedValue
处理占位符真正干活的间接的就是它~~
// @since 4.3 这个类出现得还是蛮晚的 因为之前都是用内部类的方式实现的~~~~这个实现类是最为强大的 只是SpEL
public class EmbeddedValueResolver implements StringValueResolver {
// BeanExpressionResolver之前有非常详细的讲解,简直不要太熟悉~ 它支持的是SpEL 可以说非常的强大
// 并且它有BeanExpressionContext就能拿到BeanFactory工厂,就能使用它的`resolveEmbeddedValue`来处理占位符~~~~
// 双重功能都有了~~~拥有了和@Value一样的能力,非常强大~~~
private final BeanExpressionContext exprContext;
@Nullable
private final BeanExpressionResolver exprResolver;
public EmbeddedValueResolver(ConfigurableBeanFactory beanFactory) {
this.exprContext = new BeanExpressionContext(beanFactory, null);
this.exprResolver = beanFactory.getBeanExpressionResolver();
}
@Override
@Nullable
public String resolveStringValue(String strVal) {
// 先使用Bean工厂处理占位符resolveEmbeddedValue
String value = this.exprContext.getBeanFactory().resolveEmbeddedValue(strVal);
// 再使用el表达式参与计算~~~~
if (this.exprResolver != null && value != null) {
Object evaluated = this.exprResolver.evaluate(value, this.exprContext);
value = (evaluated != null ? evaluated.toString() : null);
}
return value;
}
}
关于Bean工厂resolveEmbeddedValue
的实现,我们这里也顺带看看:
public abstract class AbstractBeanFactory extends FactoryBeanRegistrySupport implements ConfigurableBeanFactory {
...
@Override
@Nullable
public String resolveEmbeddedValue(@Nullable String value) {
if (value == null) {
return null;
}
String result = value;
// embeddedValueResolvers是个复数:因为我们可以自定义处理器添加到bean工厂来,增强它的能力
for (StringValueResolver resolver : this.embeddedValueResolvers) {
result = resolver.resolveStringValue(result);
// 只要处理结果不为null,所以的处理器都会执行到~~~~
if (result == null) {
return null;
}
}
return result;
}
...
}
而Bean工厂的处理器都怎么添加进去的呢????
public abstract class AbstractApplicationContext extends DefaultResourceLoader
implements ConfigurableApplicationContext {
protected void finishBeanFactoryInitialization(ConfigurableListableBeanFactory beanFactory) {
...
// 如果从来没有注册过,Spring容器默认会给注册一个这样的内部类
// 可以看到,它最终还是委托给了Environment去干这件事~~~~~~
// 显然它最终就是调用PropertyResolver#resolvePlaceholders
if (!beanFactory.hasEmbeddedValueResolver()) {
beanFactory.addEmbeddedValueResolver(strVal -> getEnvironment().resolvePlaceholders(strVal));
}
...
}
}
由此可见,解析占位符最终都返璞归真
,真正最终的处理类,处理方法方法是:AbstractPropertyResolver#resolvePlaceholders
,这就是我们非常熟悉了,上面也有详细讲解,最终都是委托给了PropertyPlaceholderHelper
去处理的~
由此可见,若我们通过实现感知接口EmbeddedValueResolverAware
得到一个StringValueResolver
来处理我们的占位符、SpEL计算。根本原因是:
class ApplicationContextAwareProcessor implements BeanPostProcessor {
// 这里new的一个EmbeddedValueResolver,它持有对beanFactory的引用~~~
// 所以调用者直接使用的是EmbeddedValueResolver:它支持解析占位符(依赖于Enviroment上面有说到)并且支持SpEL的解析 非常强的
public ApplicationContextAwareProcessor(ConfigurableApplicationContext applicationContext) {
this.applicationContext = applicationContext;
this.embeddedValueResolver = new EmbeddedValueResolver(applicationContext.getBeanFactory());
}
}
Spring对这个感知接口的命名也很实在,我们通过实现EmbeddedValueResolverAware
这个接口得到的实际上是一个EmbeddedValueResolver
,提供处理占位符和SpEL等高级功能。
另外StringValueResolver
还有个实现类是PropertyPlaceholderConfigurer
的private内部类实现,PlaceholderResolvingStringValueResolver
逻辑也非常的简单,此处就不展开了。
关于PropertyPlaceholderConfigurer
本身,我们在当初xml导入配置的时候经常看到如下配置:
<bean id="propertyConfigurer" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<property name="location">
<!-- classpath:conf/jdbc.properties -->
<value>conf/jdbc.properties</value>
</property>
<property name="fileEncoding">
<value>UTF-8</value>
</property>
</bean>
而在注解时代,一般建议使用@PropertySource
代替~
关于SpEL的讲解和BeanExpressionContext
以及BeanExpressionResolver
我之前文章有非常详细的讲解,强烈建议参考博文:
【小家Spring】Spring中@Value注解有多强大?从原理层面去剖析为何它有如此大的“能耐“
PropertyResolver的resolvePlaceholders()
和getProperty()
的区别
这个区别其实很多人都并不能明白,举个例子:
public class Main {
public static void main(String[] args) {
StandardEnvironment environment = new StandardEnvironment();
MutablePropertySources mutablePropertySources = environment.getPropertySources();
MapPropertySource mapPropertySource = new MapPropertySource("diy", new HashMap<String, Object>() {{
put("app.name", "fsx");
put("app.key", "${user.home1}"); // 注意这里是user.home1 特意让系统属性里不存在的
put("app.full", "${app.key} + ${app.name}");
}});
mutablePropertySources.addFirst(mapPropertySource);
// 正常使用
String s = environment.resolvePlaceholders("${app.full}");
System.out.println(s);
s = environment.getProperty("app.full");
System.out.println(s);
}
}
结果为:
${user.home1} + fsx
Exception in thread "main" java.lang.IllegalArgumentException: Could not resolve placeholder 'user.home1' in value "${user.home1}"
at org.springframework.util.PropertyPlaceholderHelper.parseStringValue(PropertyPlaceholderHelper.java:172)
at org.springframework.util.PropertyPlaceholderHelper.parseStringValue(PropertyPlaceholderHelper.java:160)
由此可以得出如下几点结论:
注意:他俩最终解析的都是
${app.key} + ${app.name}
这个字符串,只是两种方法get取值的方式不一样~
properties
里面可以书写通配符如${app.name}
,但需要注意:
1.properties
里的内容都原封不动的被放进了PropertySource
里(或者说是环境里),而是只有在需要用的时候才会解析它
2. 可以引用系统属性、环境变量等,设置引用被的配置文件里都是ok的(只要保证在同一Environment就成)resolvePlaceholders()
它的入参是${}
一起也包含进来的。它有如下特点:
1. 若${}
里面的key不存在,就原样输出,不报错
。若存在就使用值替换
2. key必须用${}
包着,否则原样输出~~
3. 若是resolveRequiredPlaceholders()
方法,那key不存在就会抛错~getProperty()
指定的是key本身,并不需要包含${}
,
1. 若key不存在返回null
,但是若key的值里还有占位符,那就就继续解析。若出现占位符里的key不存在时,就抛错
2.getRequiredProperty()
方法若key不存在就直接报错了~
注意:@Value
注解我们一般这么使用@Value("${app.full}")
来读取配置文件里的值,所以它即使出现了如上占位符不存在也原样输出不会报错(当然你的key必须存在啊),因为已经对@Value分析过多次:DefaultListableBeanFactory
解析它的时候,最终会把表达式先交给StringValueResolver
们去处理占位符,调用的就是resolver.resolveStringValue(result)
方法。而最终执行它的见:
beanFactory.addEmbeddedValueResolver(strVal -> getEnvironment().resolvePlaceholders(strVal));
所以最终是委托给Environment
的resolvePlaceholders()
方法去处理的,所以由上结论可知,即是存在占位符的key不存在,原样输出即可嘛。
备注最终解析都是交给了
PropertyPlaceholderHelper
,它默认支持{}
、[]
、()
等占位符。而我们最为常用的就是${}
,注意它的placeholderPrefix=${
(而不是单单的{
),后缀是}
占位符使用小技巧
例如一般我们的web程序的application.properties
配置端口如下:
server.port=8080
而打包好后我们可以通过启动参数:--server.port=9090
来改变此端口。但是若我们配置文件这么写:
server.port=${port:8080}
那我们启动参数可以变短了,这样写就成:--port=9090
。相信了解了上面原理的小伙伴,理解这个小技巧是非常简单的事咯~~~
${port:8080}
表示没有port这个key,就用8080。 有这个key就用对应的值~
总结
PropertyResolver
作为一个处理底层属性源的接口,可能很少有人熟悉。但是Environment
作为一个上层应用接口,代表着Spring应用运行环境属性信息,可以说还是非常的重要的。毕竟平时开发中,我们也不乏少用~
另外它和Spring的属性源:PropertySource
也有非常大的关联,而属性源这个概念在任何一个框架设计中我认为都是很重要的考量,Spring、SpringBoot
尤甚。因此了解了Env的理论能够奠定一个很好的框架设计基础~
关注A哥
Author | A哥(YourBatman) |
---|---|
个人站点 | www.yourbatman.cn |
yourbatman@qq.com | |
微 信 | fsx641385712 |
活跃平台 | |
公众号 | BAT的乌托邦(ID:BAT-utopia) |
知识星球 | BAT的乌托邦 |
每日文章推荐 | 每日文章推荐 |
更多推荐
所有评论(0)