spring 中的属性解析器 PropertyResolver

我们知道,在 spring 中可以使用占位符,格式如 "${}",大括号中间放置待替换的占位符,待使用值时根据配置的属性解析器进行解析。但具体是如何操作的,且看本文来进行分析。

PropertyResolver

这是一个针对任意底层资源进行属性解析的接口,内部定义了根据 key 获取属性和解析占位符的相关抽象方法。

// 方法1
String resolvePlaceholders(String text);
// 方法2
String resolveRequiredPlaceholders(String text) throws IllegalArgumentException;

方法1 和 方法2 都用来解析 text 字符串中的 "${...}" 占位符,区别在于遇到解析不了的占位符,方法1 不做处理,方法2 会抛出异常。

PropertyResolver 在 spring 中的实现类,主要有以下几个:

  • StandardEnvironment
  • StandardServletEnvironment  
  • PropertySourcesPropertyResolver

其中 StandardServletEnvironment 是在 spring MVC 中使用,继承 StandardEnvironment。下面来看看 StandardEnvironment 和 PropertySourcesPropertyResolver 中的具体实现。

StandardEnvironment

在创建 spring 应用时,会给 AbstractApplicationContext 中的属性 environment 进行赋值。

// AbstractApplicationContext
@Override
public ConfigurableEnvironment getEnvironment() {if (this.environment == null) {this.environment = createEnvironment();}return this.environment;
}protected ConfigurableEnvironment createEnvironment() {return new StandardEnvironment();
}

可以看到创建了一个 StandardEnvironment。类关系图如下:

继承 AbstractEnvironment,所以在执行构造方法时会调用父类的无参构造。

//StandardEnvironment 父类 AbstractEnvironment
public AbstractEnvironment() {this(new MutablePropertySources());
}protected AbstractEnvironment(MutablePropertySources propertySources) {this.propertySources = propertySources;this.propertyResolver = createPropertyResolver(propertySources);customizePropertySources(propertySources);
}
protected ConfigurablePropertyResolver createPropertyResolver(MutablePropertySources propertySources) {return new PropertySourcesPropertyResolver(propertySources);
}

 可以看到,创建了一个 PropertySourcesPropertyResolver 实例对象,并赋值给 AbstractEnvironment 中属性 propertyResolver。接着调用了方法 customizePropertySources,根据方法名可知,自定义属性源,由子类 StandardEnvironment 进行重写。

@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 定义的 PropertySources 只有两个,一个获取的是系统属性、一个获取的是系统环境变量。并且这个 propertySources 在创建 PropertySourcesPropertyResolver 时作为参数,传递给了 PropertySourcesPropertyResolver 中属性 propertySources。

查看 StandardEnvironment 中对 PropertyResolver 中接口方法的实现,都是在 AbstractEnvironemnt 类中,委托给属性 propertyResolver 来实现的。

@Override
@Nullable
public String getProperty(String key) {return this.propertyResolver.getProperty(key);
}@Override
public String resolvePlaceholders(String text) {return this.propertyResolver.resolvePlaceholders(text);
}@Override
public String resolveRequiredPlaceholders(String text) throws IllegalArgumentException {return this.propertyResolver.resolveRequiredPlaceholders(text);
}

这样,就实现了对属性解析的统一,即都是通过 PropertySourcesPropertyResolver 类来操作的

PropertySourcesPropertyResolver

继承 AbstractPropertyResolver,对占位符的解析,在 AbstractPropertyResolver 中实现。

@Override
public String resolvePlaceholders(String text) {if (this.nonStrictHelper == null) {this.nonStrictHelper = createPlaceholderHelper(true);}return doResolvePlaceholders(text, this.nonStrictHelper);
}@Override
public String resolveRequiredPlaceholders(String text) throws IllegalArgumentException {if (this.strictHelper == null) {this.strictHelper = createPlaceholderHelper(false);}return doResolvePlaceholders(text, this.strictHelper);
}

第一次调用时,创建不同的 PlaceholderHelper,并赋值。

private PropertyPlaceholderHelper createPlaceholderHelper(boolean ignoreUnresolvablePlaceholders) {return new PropertyPlaceholderHelper(this.placeholderPrefix, this.placeholderSuffix,this.valueSeparator, ignoreUnresolvablePlaceholders);
}private String doResolvePlaceholders(String text, PropertyPlaceholderHelper helper) {return helper.replacePlaceholders(text, this::getPropertyAsRawString);
}
public String replacePlaceholders(String value, PlaceholderResolver placeholderResolver) {Assert.notNull(value, "'value' must not be null");return parseStringValue(value, placeholderResolver, null);
}protected String parseStringValue(String value, PlaceholderResolver placeholderResolver, @Nullable Set<String> visitedPlaceholders) {// 判断是否存在占位符前缀,即 "${",不存在前缀,认为不存在占位符,直接返回参数 valueint 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 中的 占位符placeholder = parseStringValue(placeholder, placeholderResolver, visitedPlaceholders);// Now obtain the value for the fully resolved key...// 解析获取 propValString propVal = placeholderResolver.resolvePlaceholder(placeholder);// propVal 为 null,valueSeparator 不为 null,valueSeparator 默认 ":"// 此种格式即 "${xxx:123}",其中 xxx 为 actualPlaceholder,123 为 defaultValue,接着使用 placeholderResolver 解析 actualPlaceholder,// 不存在,将 defaultValue 赋值给 propValif (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 进行递归解析,处理 占位符嵌套,即 配置的 propVal 中也肯能存在占位符// 解析后 将 result 中 占位字符串 以解析出的 propVal 替换,接着更新索引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());}// ignoreUnresolvablePlaceholders 由创建 PropertyPlaceholderHelper 实例时参数传入// nonStrictHelper 中为 true,表示可以忽略无法解析的占位符// strictHelper 中为 false,此时如果 propVal 为 null,就会抛出异常else if (this.ignoreUnresolvablePlaceholders) {// Proceed with unprocessed value.// 向后查找,更新 startIndexstartIndex = result.indexOf(this.placeholderPrefix, endIndex + this.placeholderSuffix.length());}else {throw new IllegalArgumentException("Could not resolve placeholder '" +placeholder + "'" + " in value \"" + value + "\"");}visitedPlaceholders.remove(originalPlaceholder);}else {// 存在前缀,但不存在后缀,将前缀索引置为 -1,下一次跳出循环startIndex = -1;}}return result.toString();
}//找到 buf 中占位符后缀对应的最后一个索引
private int findPlaceholderEndIndex(CharSequence buf, int startIndex) {int index = startIndex + this.placeholderPrefix.length();int withinNestedPlaceholder = 0;while (index < buf.length()) {// placeholderSuffix "}"// // 匹配到了后缀,判断是否存在内嵌,存在,内嵌数量减 1,重置索引,不存在内嵌,直接返回if (StringUtils.substringMatch(buf, index, this.placeholderSuffix)) {if (withinNestedPlaceholder > 0) {withinNestedPlaceholder--;index = index + this.placeholderSuffix.length();}else {return index;}}// simplePrefix "{"// 判断是否存在内嵌的占位符else if (StringUtils.substringMatch(buf, index, this.simplePrefix)) {withinNestedPlaceholder++;index = index + this.simplePrefix.length();}else {index++;}}return -1;
}
public static boolean substringMatch(CharSequence str, int index, CharSequence substring) {if (index + substring.length() > str.length()) {return false;}// 逐个字符比较,当 substring 为 "}" 或 "{" 时,就是比较 str 中索引为 index 的字符和 substring 对应的字符是否相等for (int i = 0; i < substring.length(); i++) {if (str.charAt(index + i) != substring.charAt(i)) {return false;}}return true;
}

 可以看到,具体的解析是由 PropertyPlaceholderHelper 来完成的。先解析出占位符 placeholder,再通过传入的 lambda 表达式,作为 placeholderResolver,解析 placeholder,得到属性值 propVal。

下面看看这个传入的 lambda 表达式,是一个抽象方法,由子类 PropertySourcesPropertyResolver 实现。

@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) {for (PropertySource<?> propertySource : this.propertySources) {if (logger.isTraceEnabled()) {logger.trace("Searching for key '" + key + "' in PropertySource '" +propertySource.getName() + "'");}Object value = propertySource.getProperty(key);if (value != null) {if (resolveNestedPlaceholders && value instanceof String) {value = resolveNestedPlaceholders((String) value);}logKeyFound(key, propertySource, value);return convertValueIfNecessary(value, targetValueType);}}}if (logger.isTraceEnabled()) {logger.trace("Could not find key '" + key + "' in any property source");}return null;
}

遍历注册的 propertySources,获取 value,默认注册的只有系统属性和环境变量,所以占位符只有是在系统属性和环境变量中出现的 key,才可以解析出对应的 value。这么看来这个功能太鸡肋了,如果想丰富这个功能,必须增加 PropertySource。

PropertySourcesPlaceholderConfigurer

从类关系可以看到,这是一个 BeanFactoryPostProcessor 子类,提供了通过 spring Environment 中设置的 PropertySources 来解析 BeanDefinition 定义时的 PropertyValues 和 @Value 中的 "${...}" 占位符。

添加 PropertySourcesPlaceholderConfigurer 有两种方式:

<context:property-placeholder location="config.properties"/>
<bean id="placeholderConfigurer" class="org.springframework.context.support.PropertySourcesPlaceholderConfigurer"><property name="locations"><list><value>config.properties</value></list></property>
</bean>

这两种配置的结果是等价的,所不同的只是解析时的方式不同,第一种通过自定义标签解析,第二种通过默认标签解析。下面来看下第一种配置是如何操作的。

通过 spring 对于 XML 中自定义标签的解析,先去 spring-context 模块下 META-INF/spring.handlers 中找到 context 对应的 handler 为 ContextNamespaceHandler。在 ContextNamespaceHandler#init 方法中注册的 property-placeholder 解析器为 PropertyPlaceholderBeanDefinitionParser

property-placeholder 解析

解析入口在 AbstractBeanDefinitionParser#parse 中。

@Override
@Nullable
public final BeanDefinition parse(Element element, ParserContext parserContext) {// 解析 BeanDefinitionAbstractBeanDefinition definition = parseInternal(element, parserContext);if (definition != null && !parserContext.isNested()) {try {// 解析 idString id = resolveId(element, definition, parserContext);if (!StringUtils.hasText(id)) {...}String[] aliases = null;if (shouldParseNameAsAliases()) {// 解析 name,作为 bean 的别名String name = element.getAttribute(NAME_ATTRIBUTE);if (StringUtils.hasLength(name)) {aliases = StringUtils.trimArrayElements(StringUtils.commaDelimitedListToStringArray(name));}}// 封装 BeanDefinitionHolder,注册 BeanDefinition,存在 aliases,注册 aliasesBeanDefinitionHolder holder = new BeanDefinitionHolder(definition, id, aliases);registerBeanDefinition(holder, parserContext.getRegistry());if (shouldFireEvents()) {BeanComponentDefinition componentDefinition = new BeanComponentDefinition(holder);postProcessComponentDefinition(componentDefinition);parserContext.registerComponent(componentDefinition);}}catch (BeanDefinitionStoreException ex) {...}}return definition;
}

由 AbstractBeanDefinitionParser#parse 方法调入 AbstractSingleBeanDefinitionParser#parseInternal 方法中。 

@Override
protected final AbstractBeanDefinition parseInternal(Element element, ParserContext parserContext) {BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition();String parentName = getParentName(element);if (parentName != null) {builder.getRawBeanDefinition().setParentName(parentName);}Class<?> beanClass = getBeanClass(element);if (beanClass != null) {builder.getRawBeanDefinition().setBeanClass(beanClass);}else {String beanClassName = getBeanClassName(element);if (beanClassName != null) {builder.getRawBeanDefinition().setBeanClassName(beanClassName);}}builder.getRawBeanDefinition().setSource(parserContext.extractSource(element));BeanDefinition containingBd = parserContext.getContainingBeanDefinition();if (containingBd != null) {// Inner bean definition must receive same scope as containing bean.builder.setScope(containingBd.getScope());}if (parserContext.isDefaultLazyInit()) {// Default-lazy-init applies to custom bean definitions as well.builder.setLazyInit(true);}doParse(element, parserContext, builder);return builder.getBeanDefinition();
}

PropertyPlaceholderBeanDefinitionParser 重写了 getBeanClass 方法。

@Override
@SuppressWarnings("deprecation")
protected Class<?> getBeanClass(Element element) {// spring 3.1 之后,默认采用if (SYSTEM_PROPERTIES_MODE_DEFAULT.equals(element.getAttribute(SYSTEM_PROPERTIES_MODE_ATTRIBUTE))) {return PropertySourcesPlaceholderConfigurer.class;}// 适配老版本return org.springframework.beans.factory.config.PropertyPlaceholderConfigurer.class;
}

所以创建的 GenericBeanDefinition 中 beanClass 为 PropertySourcesPlaceholderConfigurer.class,设置完 beanClass 后,执行 doParser,进入 PropertyPlaceholderBeanDefinitionParser#doParser。

// PropertyPlaceholderBeanDefinitionParser
@Override
protected void doParse(Element element, ParserContext parserContext, BeanDefinitionBuilder builder) {super.doParse(element, parserContext, builder);builder.addPropertyValue("ignoreUnresolvablePlaceholders",Boolean.valueOf(element.getAttribute("ignore-unresolvable")));String systemPropertiesModeName = element.getAttribute(SYSTEM_PROPERTIES_MODE_ATTRIBUTE);if (StringUtils.hasLength(systemPropertiesModeName) &&!systemPropertiesModeName.equals(SYSTEM_PROPERTIES_MODE_DEFAULT)) {builder.addPropertyValue("systemPropertiesModeName", "SYSTEM_PROPERTIES_MODE_" + systemPropertiesModeName);}// value-spearator 默认 ":"if (element.hasAttribute("value-separator")) {builder.addPropertyValue("valueSeparator", element.getAttribute("value-separator"));}if (element.hasAttribute("trim-values")) {builder.addPropertyValue("trimValues", element.getAttribute("trim-values"));}if (element.hasAttribute("null-value")) {builder.addPropertyValue("nullValue", element.getAttribute("null-value"));}
}
// AbstractPropertyLoadingBeanDefinitionParser
@Override
protected void doParse(Element element, ParserContext parserContext, BeanDefinitionBuilder builder) {String location = element.getAttribute("location");if (StringUtils.hasLength(location)) {// resolvePlaceholders 遇见无法解析的占位符跳过location = parserContext.getReaderContext().getEnvironment().resolvePlaceholders(location);String[] locations = StringUtils.commaDelimitedListToStringArray(location);builder.addPropertyValue("locations", locations);}String propertiesRef = element.getAttribute("properties-ref");if (StringUtils.hasLength(propertiesRef)) {builder.addPropertyReference("properties", propertiesRef);}String fileEncoding = element.getAttribute("file-encoding");if (StringUtils.hasLength(fileEncoding)) {builder.addPropertyValue("fileEncoding", fileEncoding);}String order = element.getAttribute("order");if (StringUtils.hasLength(order)) {builder.addPropertyValue("order", Integer.valueOf(order));}builder.addPropertyValue("ignoreResourceNotFound",Boolean.valueOf(element.getAttribute("ignore-resource-not-found")));builder.addPropertyValue("localOverride",Boolean.valueOf(element.getAttribute("local-override")));builder.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
}

可以看到,又调用到了父类 AbstractPropertyLoadingBeanDefinitionParser 中,此处会进行 location 的解析。此处 location 也支持 "${...}" 占位符配置,但由于此时的 Environment 中注册的 PropertySources 只有系统变量和环境变量,所以此处可替换的占位符很有限。

也可以看到此处对 location 中占位符的解析比较宽松,选用的是 resolvePlaceholders,即没有默认值的无法解析的占位符将被忽略,并原封不动地传递。

location 支持以 "," 分隔的形式配置多个资源文件,最后添加 beanDefinition 的 PropertyValues,key 为 locations。

解析得到 BeanDefinition 后,进入 AbstractBeanDefinitionParser#resolveId。

protected String resolveId(Element element, AbstractBeanDefinition definition, ParserContext parserContext)throws BeanDefinitionStoreException {if (shouldGenerateId()) {return parserContext.getReaderContext().generateBeanName(definition);}else {String id = element.getAttribute(ID_ATTRIBUTE);if (!StringUtils.hasText(id) && shouldGenerateIdAsFallback()) {id = parserContext.getReaderContext().generateBeanName(definition);}return id;}
}

针对 PropertySourcesPlaceholderConfigure,默认会生成 id。此处采用默认的 Bean 名称生成策略生成 beanName。

// DefaultBeanNameGenerator
@Override
public String generateBeanName(BeanDefinition definition, BeanDefinitionRegistry registry) {return BeanDefinitionReaderUtils.generateBeanName(definition, registry);
}
public static String generateBeanName(BeanDefinition definition, BeanDefinitionRegistry registry, boolean isInnerBean)throws BeanDefinitionStoreException {String generatedBeanName = definition.getBeanClassName();if (generatedBeanName == null) {if (definition.getParentName() != null) {generatedBeanName = definition.getParentName() + "$child";}else if (definition.getFactoryBeanName() != null) {generatedBeanName = definition.getFactoryBeanName() + "$created";}}if (!StringUtils.hasText(generatedBeanName)) {...}if (isInnerBean) {return generatedBeanName + GENERATED_BEAN_NAME_SEPARATOR + ObjectUtils.getIdentityHexString(definition);}return uniqueBeanName(generatedBeanName, registry);
}
public static String uniqueBeanName(String beanName, BeanDefinitionRegistry registry) {String id = beanName;int counter = -1;// 自增 counter 直到生成的 id 在 BeanFactory 中 唯一String prefix = beanName + GENERATED_BEAN_NAME_SEPARATOR;while (counter == -1 || registry.containsBeanDefinition(id)) {counter++;id = prefix + counter;}return id;
}

逻辑很简单,最后生成的 id 如:org.springframework.context.support.PropertySourcesPlaceholderConfigurer#0,之后在 AbstractBeanDefinitionParser#parse 方法中将此 id 作为 beanName,对 beanDefinition 进行注册。

至此,完成了对 context 标签下 property-placeholder 的解析。

实例化

从上面的介绍可以知道,完成解析后,注册了一个名称为 org.springframework.context.support.PropertySourcesPlaceholderConfigurer#0 的 BeanDefinition,那么这个 bean 是什么时候实例化的呢?

从一开始介绍类结构时,我们说过这是一个 BeanFactoryPostProcessor 子类,所以它会在 AbstractApplicationContext#refersh 中执行 invokeBeanFactoryPostProcessors 时进行实例化。

public static void invokeBeanFactoryPostProcessors(ConfigurableListableBeanFactory beanFactory, List<BeanFactoryPostProcessor> beanFactoryPostProcessors) {// 处理 BeanDefinitionRegistryPostProcessorsSet<String> processedBeans = new HashSet<>();...// 处理 BeanFactoryPostProcessorString[] postProcessorNames =beanFactory.getBeanNamesForType(BeanFactoryPostProcessor.class, true, false);// 将获得的 BeanFactoryPostProcessor 按 PriorityOrdered、Ordered 和 剩下的分类List<BeanFactoryPostProcessor> priorityOrderedPostProcessors = new ArrayList<>();List<String> orderedPostProcessorNames = new ArrayList<>();List<String> nonOrderedPostProcessorNames = new ArrayList<>();for (String ppName : postProcessorNames) {if (processedBeans.contains(ppName)) {// 跳过已经处理过的}else if (beanFactory.isTypeMatch(ppName, PriorityOrdered.class)) {priorityOrderedPostProcessors.add(beanFactory.getBean(ppName, BeanFactoryPostProcessor.class));}else if (beanFactory.isTypeMatch(ppName, Ordered.class)) {orderedPostProcessorNames.add(ppName);}else {nonOrderedPostProcessorNames.add(ppName);}}// First, invoke the BeanFactoryPostProcessors that implement PriorityOrdered.// 首先,调用实现了 PriorityOrdered 的 BeanFactoryPostProcessorsortPostProcessors(priorityOrderedPostProcessors, beanFactory);invokeBeanFactoryPostProcessors(priorityOrderedPostProcessors, beanFactory);// Next, invoke the BeanFactoryPostProcessors that implement Ordered.// 其次,调用实现了 Ordered 的 BeanFactoryPostProcessorList<BeanFactoryPostProcessor> orderedPostProcessors = new ArrayList<>(orderedPostProcessorNames.size());for (String postProcessorName : orderedPostProcessorNames) {orderedPostProcessors.add(beanFactory.getBean(postProcessorName, BeanFactoryPostProcessor.class));}sortPostProcessors(orderedPostProcessors, beanFactory);invokeBeanFactoryPostProcessors(orderedPostProcessors, beanFactory);// 最后,调用剩下的 BeanFactoryPostProcessorList<BeanFactoryPostProcessor> nonOrderedPostProcessors = new ArrayList<>(nonOrderedPostProcessorNames.size());for (String postProcessorName : nonOrderedPostProcessorNames) {nonOrderedPostProcessors.add(beanFactory.getBean(postProcessorName, BeanFactoryPostProcessor.class));}invokeBeanFactoryPostProcessors(nonOrderedPostProcessors, beanFactory);// Clear cached merged bean definitions since the post-processors might have// modified the original metadata, e.g. replacing placeholders in values...beanFactory.clearMetadataCache();
}

PropertySourcesPlaceholderConfigurer 实现了 PriorityOrdered,所以,会首先完成 PropertySourcesPlaceholderConfigurer 的实例化。调用 beanFactory.getBean 调用获取实例对象。

我们知道,在实例化之后,会对创建的半成品对象进行属性填充,下面来看看针对 PropertySourcesPlaceholderConfigurer 的属性填充。

applyPropertyValues

从前面的 property-placeholder 解析可知,将配置的 location 最后解析成了属性名为 locations 的 String 数组,所以在此处,当调用 valueResolver.resolveValueIfNecessary 时,会调用 BeanDefinitionValueResolver#evaluate --> BeanDefinitionValueResolver#doEvaluate               --> AbstractBeanFactory#evaluateBeanDefinitionString

@Nullable
protected Object evaluateBeanDefinitionString(@Nullable String value, @Nullable BeanDefinition beanDefinition) {if (this.beanExpressionResolver == null) {return value;}Scope scope = null;if (beanDefinition != null) {String scopeName = beanDefinition.getScope();if (scopeName != null) {scope = getRegisteredScope(scopeName);}}return this.beanExpressionResolver.evaluate(value, new BeanExpressionContext(this, scope));
}

可以看到最终通过为 beanFactory 设置的 beanExpressionResolver 来进行处理,即 StandardBeanExpressionResolver,这是 Spring EL 表达式的标准实现,默认解析 #{...},此时若为 ${...},并不会对其解析。

接着,判断 locations 是一个可写的属性,执行 convertForProperty,最终调用 TypeConverterDelegate#convertIfNecessary 进行处理。根据需要的属性类型,找到对应的编辑器,ResourceArrayPropertyEditor,执行 setValue。

// ResourceArrayPropertyEditor
@Override
public void setValue(Object value) throws IllegalArgumentException {if (value instanceof Collection || (value instanceof Object[] && !(value instanceof Resource[]))) {Collection<?> input = (value instanceof Collection ? (Collection<?>) value : Arrays.asList((Object[]) value));Set<Resource> merged = new LinkedHashSet<>();for (Object element : input) {if (element instanceof String) {String pattern = resolvePath((String) element).trim();try {Resource[] resources = this.resourcePatternResolver.getResources(pattern);Collections.addAll(merged, resources);}catch (IOException ex) {...}}else if (element instanceof Resource) {merged.add((Resource) element);}else {...}}super.setValue(merged.toArray(new Resource[0]));}else {super.setValue(value);}
}

遍历 locations 字符串数组,先执行 resolvePath,参考 spring 中对象创建之 BeanWrapperImpl 的初始化 可知,创建 ResourceArrayPropertyEditor 时,将 AbstractApplicationContext 作为参数 resourcePatternResolver,将 AbstractApplicationContext 中属性 environment 作为 propertyResolver,默认 ignoreUnresolvablePlaceholders 为 true。

protected String resolvePath(String path) {if (this.propertyResolver == null) {this.propertyResolver = new StandardEnvironment();}return (this.ignoreUnresolvablePlaceholders ? this.propertyResolver.resolvePlaceholders(path) :this.propertyResolver.resolveRequiredPlaceholders(path));
}

所以此处 propertyResolver 不为 null,调用 resolvePlaceholders 来解析 ${...},即宽松的解析策略。

接着调用 resourcePatternResolver.getResources,参考 spring 中的资源文件加载,此时会调用到 AbstractApplicationContext#getResources,继而调用 PathMatchingResourcePatternResolver#getResources。

对于 config.properties,执行 

new Resource[] {getResourceLoader().getResource(locationPattern)};

最终调用 DefaultResourceLoader#getResourceByPath,创建一个 ClassPathContextResource 实例对象。

此时对于非系统变量和环境变量对应的占位符,例如:${cc}.properties,此处无法解析,执行 resolvePath 会将原值返回,因为此处依然采用的是 AbstractApplicationContext 创建时的 environment,注册的 PropertySources 只有系统变量和环境变量,所以可替换的占位符很有限。接着又会调入 PathMatchingResourcePatternResolver#getResources,此时 AntPathMatcher#isPattern 将 {} 判断为路径匹配中的特殊字符,返回true,将其看作了需进行路径匹配的资源,调用 PathMatchingResourcePatternResolver#findPathMatchingResources。一番处理后 rootDirPath = "",subDirPath = ${cc}.properties,此时调用 getResources,会创建一个 path 为 "" 的 ClassPathContextResource,作为 rootDirResources 返回,接着对 rootDirResources 进行遍历,获取 rootDirUrl 为 当前 class 所在根目录,协议为 file,调用 PathMatchingResourcePatternResolver#doFindPathMatchingFileResources                             --> doFindMatchingFileSystemResources  --> retrieveMatchingFiles,一番检索匹配后,返回一个 Resource[0] 数组。

之后调用 super.setValue,对父类 java.beans.PropertyEditorSupport 中 value 进行赋值。接着又在 TypeConverterDelegate#doConvertValue 中获取到这个转换后的 value 进行返回。

initializeBean

AbstractAutowireCapableBeanFactory#applyBeanPostProcessorsBeforeInitialization 中,此时由于已经注册 ApplicationContextAwareProcessor,会调用 ApplicationContextAwareProcessor#invokeAwareInterfaces,为 PropertySourcesPlaceholderConfigurer 设置 environment,为 AbstractApplicationContext 中属性 environment。

invokeBeanFactoryPostProcessors

实例对象创建完成之后,发起 PostProcessorRegistrationDelegate#invokeBeanFactoryPostProcessors 调用。

private static void invokeBeanFactoryPostProcessors(Collection<? extends BeanFactoryPostProcessor> postProcessors, ConfigurableListableBeanFactory beanFactory) {for (BeanFactoryPostProcessor postProcessor : postProcessors) {StartupStep postProcessBeanFactory = beanFactory.getApplicationStartup().start("spring.context.bean-factory.post-process").tag("postProcessor", postProcessor::toString);postProcessor.postProcessBeanFactory(beanFactory);postProcessBeanFactory.end();}
}

此时,就会调用 PropertySourcesPlaceholderConfigurer#postProcessBeanFactory。

@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {if (this.propertySources == null) {this.propertySources = new MutablePropertySources(); // 创建赋值if (this.environment != null) {PropertyResolver propertyResolver = this.environment;// 如果 ignoreUnresolvablePlaceholders 标志被设置为 true,我们必须创建一个本地 PropertyResolver 来强制执行该设置,// 因为 environment 很可能没有将 ignoreUnresolvablePlaceholders 设置为 true// See https://github.com/spring-projects/spring-framework/issues/27947// StandardEnvironment 是 ConfigurableEnvironment 实现类if (this.ignoreUnresolvablePlaceholders && (this.environment instanceof ConfigurableEnvironment)) {ConfigurableEnvironment configurableEnvironment = (ConfigurableEnvironment) this.environment;PropertySourcesPropertyResolver resolver =new PropertySourcesPropertyResolver(configurableEnvironment.getPropertySources());resolver.setIgnoreUnresolvableNestedPlaceholders(true);propertyResolver = resolver;}PropertyResolver propertyResolverToUse = propertyResolver;this.propertySources.addLast(new PropertySource<Environment>(ENVIRONMENT_PROPERTIES_PROPERTY_SOURCE_NAME, this.environment) {@Override@Nullablepublic String getProperty(String key) {return propertyResolverToUse.getProperty(key);}});}try {PropertySource<?> localPropertySource =new PropertiesPropertySource(LOCAL_PROPERTIES_PROPERTY_SOURCE_NAME, mergeProperties());// 默认不覆盖,addLastif (this.localOverride) {this.propertySources.addFirst(localPropertySource);}else {this.propertySources.addLast(localPropertySource);}}catch (IOException ex) {...}}processProperties(beanFactory, new PropertySourcesPropertyResolver(this.propertySources));this.appliedPropertySources = this.propertySources;
}

如果 PropertySourcesPlaceholderConfigurer 中 ignoreUnresolvablePlaceholders 被设置为 true,此时会创建一个 PropertySourcesPropertyResolver 实例对象,来代替 initializeBean 时设置的 environment 作为系统变量和环境变量下的属性解析器。否则,仍采用 environment。

接着执行 mergeProperties,加载配置的属性文件。

protected Properties mergeProperties() throws IOException {// java.util.PropertiesProperties result = new Properties();if (this.localOverride) {// Load properties from file upfront, to let local properties override.loadProperties(result);}if (this.localProperties != null) {for (Properties localProp : this.localProperties) {CollectionUtils.mergePropertiesIntoMap(localProp, result);}}if (!this.localOverride) {// 加载 propertiesloadProperties(result);}return result;
}
// PropertiesLoaderSupport
protected void loadProperties(Properties props) throws IOException {if (this.locations != null) {for (Resource location : this.locations) {try {PropertiesLoaderUtils.fillProperties(props, new EncodedResource(location, this.fileEncoding), this.propertiesPersister);}catch (FileNotFoundException | UnknownHostException | SocketException ex) {...}}}
}// DefaultPropertiesPersister
@Override
public void load(Properties props, InputStream is) throws IOException {props.load(is);
}

遍历 PropertySourcesPlaceholderConfigurer 对象创建时填充的属性 locations,加载 properties 文件,得到 Properties,之后将其包装为 PropertiesPropertySource 对象,加入 propertySources。

接着新建一个 PropertySourcesPropertyResolver,传入 propertySources,之后调用 PropertySourcesPlaceholderConfigurer#processProperties。

protected void processProperties(ConfigurableListableBeanFactory beanFactoryToProcess,final ConfigurablePropertyResolver propertyResolver) throws BeansException {// 利用新的 propertyResolver 来解析占位符propertyResolver.setPlaceholderPrefix(this.placeholderPrefix);propertyResolver.setPlaceholderSuffix(this.placeholderSuffix);propertyResolver.setValueSeparator(this.valueSeparator);// 根据 ignoreUnresolvablePlaceholders 来决定是否执行严格的占位符解析// 默认 false,即 resolveRequiredPlaceholders 执行严格的占位符解析StringValueResolver valueResolver = strVal -> {String resolved = (this.ignoreUnresolvablePlaceholders ?propertyResolver.resolvePlaceholders(strVal) :propertyResolver.resolveRequiredPlaceholders(strVal));if (this.trimValues) {resolved = resolved.trim();}return (resolved.equals(this.nullValue) ? null : resolved);};doProcessProperties(beanFactoryToProcess, valueResolver);
}// PlaceholderConfigurerSupport
protected void doProcessProperties(ConfigurableListableBeanFactory beanFactoryToProcess,StringValueResolver valueResolver) {BeanDefinitionVisitor visitor = new BeanDefinitionVisitor(valueResolver);String[] beanNames = beanFactoryToProcess.getBeanDefinitionNames();for (String curName : beanNames) {// 不是当前 beanName,同一个 beanFactoryif (!(curName.equals(this.beanName) && beanFactoryToProcess.equals(this.beanFactory))) {BeanDefinition bd = beanFactoryToProcess.getBeanDefinition(curName);try {visitor.visitBeanDefinition(bd);}catch (Exception ex) {...}}}// 自 spring 2.5 开始,解析别名中的占位符beanFactoryToProcess.resolveAliases(valueResolver);// 自 spring 3.0 开始,为 beanFactory 添加 embeddedValueResolvers,解析注解属性中的占位符beanFactoryToProcess.addEmbeddedValueResolver(valueResolver);
}// 访问替换 beanDefinition 中的占位符
public void visitBeanDefinition(BeanDefinition beanDefinition) {// parentNamevisitParentName(beanDefinition);// beanClassNamevisitBeanClassName(beanDefinition);visitFactoryBeanName(beanDefinition);visitFactoryMethodName(beanDefinition);visitScope(beanDefinition);// PropertyValuesif (beanDefinition.hasPropertyValues()) {visitPropertyValues(beanDefinition.getPropertyValues());}// ConstructorArgumentValuesif (beanDefinition.hasConstructorArgumentValues()) {ConstructorArgumentValues cas = beanDefinition.getConstructorArgumentValues();visitIndexedArgumentValues(cas.getIndexedArgumentValues());visitGenericArgumentValues(cas.getGenericArgumentValues());}
}

利用 PropertySourcesPlaceholderConfigurer 相关属性来填充新建的 propertyResolver,接着创建一个 valueResolver,并将其封装为 BeanDefinitionVisitor,之后遍历 beanFactory 中注册的 BeanDefinition,利用 BeanDefinitionVisitor 逐个发起访问。

在 visitBeanDefinition 方法中,可以看到,分别对 parentName、beanClassName、factoryBeanName、factoryMethodName、scope、propertyValues、conStructorArgumentValues 进行访问,处理其中的占位符。

@Nullable
protected String resolveStringValue(String strVal) {if (this.valueResolver == null) {throw ...}String resolvedValue = this.valueResolver.resolveStringValue(strVal);// 未修改返回原值return (strVal.equals(resolvedValue) ? strVal : resolvedValue);
}

让前面创建的 valueResolver 进行处理,根据 PropertySourcesPlaceholderConfigurer 中 ignoreUnresolvablePlaceholders 的配置情况来决定选用哪种策略执行占位符解析,默认 false,即选用严格的解析策略,遇见不能解析的占位符直接抛出异常。

接下来就利用前面 PropertySourcesPropertyResolver 中介绍的解析办法,创建 strictHelper 进行解析,此时当再次调用 PropertySourcesPropertyResolver#getPropertyAsRawString 时,就会利用已加载的 PropertySource,获取占位符对应的属性。

还有一点需要注意,经此处解析后的 BeanDefinition 中相关字段和属性,在之后创建 bean 实例时就会使用解析后的值,而对于已经创建实例对象的 BeanDefinition,除非重新创建实例对象,否则已经创建的实例对象并不会采用解析后的值。

举个例子:

<context:property-placeholder location="config.properties"/>
<context:property-placeholder location="${cc}.properties"/>

config.properties 中配置 cc=a

通过前面的介绍可知,当调用 invokeBeanFactoryProcessors 方法时,已经完成了对两个 PropertySourcesPlaceholderConfigurer 实例对象的创建,并且针对第二个 PropertySourcesPlaceholderConfigurer 实例对象,在执行 applyPropertyValues 时,由于 ${cc}.properties 无法解析,将其处理成了一个长度为 0 的 Resource 数组,这样,就会出现 mergeProperties 时 config.properties 会正常加载,接着执行 visitBeanDefinition 时会将 第二个 PropertySourcesPlaceholderConfigurer 对应的 BeanDefinition 中 ${cc}.properties 替换为 a.properties,但由于第二个 PropertySourcesPlaceholderConfigurer 已经完成实例对象的创建,当第二个 PropertySourcesPlaceholderConfigurer 执行 mergeProperties 时,由于 locations 为 Resource[0],并不会进行实质的加载,当后续获取 a.properties 中配置的属性时,就会报错。

在 PlaceholderConfigurerSupport#doProcessProperties 方法中,当完成对 BeanDefinition 的访问之后,会对 SimpleAliasRegistry 中注册的 aliasMap 进行解析,aliasMap 中 key 和 value 都支持占位符解析。

接着将 valueResolver 添加到 AbstractBeanFactory 的 embeddedValueResolvers 集合中,用来在填充属性时,AutowiredAnnotationBeanPostProcessor#postProcessProperties 对 @Value 注解中占位符的解析。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/pingmian/49608.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

基于Neo4j将知识图谱用于检索增强生成:Knowledge Graphs for RAG

Knowledge Graphs for RAG 本文是学习https://www.deeplearning.ai/short-courses/knowledge-graphs-rag/这门课的学习笔记。 What you’ll learn in this course Knowledge graphs are used in development to structure complex data relationships, drive intelligent sea…

GDAL访问HDFS集群中的数据

1.集群搭建 参考文章&#xff1a;hadoop2.10.0完全分布式集群搭建 HA(QJM)高可用集群搭建_hadoop 2.10 ha-CSDN博客 创建文件夹 hdfs dfs -mkdir -p hdfs://192.168.80.132:9000/test 开放权限 hdfs dfs -chmod -R 777 /test 上传文件 hadoop fs -put /home/wh/data/res…

卡夫卡(Kafka)框架详解:从背景到应用实践

卡夫卡&#xff08;Kafka&#xff09;框架详解&#xff1a;从背景到应用实践 引言 在大数据和分布式系统日益普及的今天&#xff0c;数据处理和消息传递成为了支撑复杂业务系统的关键基础设施。Apache Kafka&#xff0c;作为一个高性能的分布式消息队列系统&#xff0c;因其高…

CSS技巧专栏:一日一例 11 -纯CSS实现多彩渐变按钮系列特效

CSS技巧专栏&#xff1a;一日一例 11 -纯CSS实现多彩渐变按钮系列特效 本篇&#xff0c;推荐给你几个按钮&#xff0c;先看一下图片 本例图片 案例分析 这是一个系列的按钮&#xff0c;它们具有共同的特点&#xff1a; 底层按钮层&#xff0c;具有一个彩色的渐变边框&#…

第十四章 数据库

第十四章 数据库 14.1 引言 数据存储在传统上是使用单独的没有关联的文件&#xff0c;称为平面文件 14.1.1 定义 定义&#xff1a;数据库是一个组织内被应用程序使用的逻辑相一致的相关数据的集合 14.1.2 数据库的优点 数据库的优点&#xff1a; 冗余少避免数据的不一致…

docker产生日志过大优化

1、Docker容器启动后日志存放位置 #cat /var/lib/docker/containers/容器ID/容器ID-json.log #echo >/var/lib/docker/containers/容器ID/容器ID-json.log临时清除日志 注&#xff1a;echo一个空进去&#xff0c;不需要重启容器&#xff0c;但如果你直接删除这个日志&…

【JavaEE】Bean的作用域和生命周期

一.Bean的作用域. 1.1 Bean的相关概念. 通过Spring IoC和DI的学习(不清楚的可以看的前面写过的总结,可以快速入门, http://t.csdnimg.cn/K8Xr0),我们知道了Spring是如何帮助我们管理对象的 通过 Controller , Service , Repository , Component , Configuration , Bean 来声明…

无人机之航拍高级操作与技巧

一、直线飞行与矩形飞行练习&#xff1a;通过直线和矩形路径的练习&#xff0c;提升飞行路径控制能力。 二、航点命令和事件编程&#xff1a;学习如何设置航点命令和事件&#xff0c;使无人机能够自动执行复杂任务。 三、故障诊断与处理&#xff1a;掌握基本的故障诊断方法和…

如何使用 API list 极狐GitLab 群组中的镜像仓库?

GitLab 是一个全球知名的一体化 DevOps 平台&#xff0c;很多人都通过私有化部署 GitLab 来进行源代码托管。极狐GitLab &#xff1a;https://gitlab.cn/install?channelcontent&utm_sourcecsdn 是 GitLab 在中国的发行版&#xff0c;专门为中国程序员服务。可以一键式部署…

深入指南:VitePress 如何自定义样式

&#x1f49d;&#x1f49d;&#x1f49d;欢迎莅临我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐:「stormsha的主页」…

PHP安全编程宝典:30000字精细解析

文章目录 基础语法单双引号的区别前后端分离数据类型PHP常量函数var_dump函数count函数print_r函数**readfile&#xff08;&#xff09;函数****file_get_contents()函数****file_put_contents()函数**header函数fopen函数fread 函数rename函数copy&#xff08;&#xff09;函数…

【时时三省】(C语言基础)结构体

山不在高&#xff0c;有仙则名。水不在深&#xff0c;有龙则灵。 ——csdn时时三省 结构体 结构体使得C语言有能力描述复杂类型。 结构体可以让c语言创造新的类型出来 示例: struct Stu &#xff5b; char name&#xff3b;20&#xff3d; &#xff08;名字的话是一个字…

数学建模--简单优化模型之存储模型

目录 1.问题概述 1.1问题介绍 1.2优化目标 2.问题的分析与思考 2.1已知条件的说明 2.2正确的理解准备费 2.3三个举例 3.模型的假设 4.模型的建立与求解 4.1模型的建立 4.2模型的求解 5.灵敏度分析 6.模型推广 1.问题概述 1.1问题介绍 上面这个已经介绍了&#xff…

web小项目-曼波生日录(Servlet+JSP+MySQL)

效果演示&#xff1a; 当记录条数过多时会自动出现滚轮&#xff0c;数据不会超出紫框 数据库实时记录&#xff1a; 项目源代码以及所用到的资源&#xff1a; 链接: https://pan.baidu.com/s/1w0czmH9xBfetk7CZ7RNbtQ?pwd6666 提取码: 6666 复制这段内容后打开百度网盘手机App…

PaddleOCR-PP-OCRv4推理详解及部署实现(下)

目录 前言1. 检测模型1.1 预处理1.2 后处理1.3 推理 2. 方向分类器模型2.1 预处理2.2 后处理2.3 推理 3. 识别模型3.1 预处理3.2 后处理3.3 推理 4. PP-OCRv4部署4.1 源码下载4.2 环境配置4.2.1 配置CMakeLists.txt4.2.2 配置Makefile 4.3 ONNX导出4.4 engine生成4.4.1 检测模型…

linux进程——解析命令行参数——环境变量详解

前言&#xff1a;本节内容还是linux进程&#xff0c; 主要讲解里面的环境变量——我们首先要知道的就是环境变量其实就是操作系统维护的一组kv值&#xff0c; 环境变量是系统提供的一组 变量名变量值 形式的变量。不同的环境变量之间具有不同的用途&#xff0c; 并且具有全局属…

(雷达数据处理中的)跟踪算法(4) --- 基于数据集的目标跟踪实践

说明 本文作为跟踪系列的第4篇博文&#xff0c;在前面几篇博文[1][2][3]的基础上对所采集的实际数据(来自国防科技大学电子科学学院所主导发布的数据集[4])进行跟踪实践。读者在阅读本文前&#xff0c;建议先阅读本系列的前3篇博文。 Blog 20240724 博文第一次撰写 目录 说明…

【Linux】管道通信和 system V 通信

文章目录 一、进程通信原理&#xff08;让不同进程看到同一份资源&#xff09;二、管道通信2.1 管道原理及其特点2.1 匿名管道和命名管道 三、共享内存通信3.1 共享内存原理3.2 创建和关联共享内存3.3 去关联、ipc 指令和删除共享内存 四、消息队列和信号量&#xff08;了解&am…

【Unity2D 2022:UI】无法拖拽TextMeshPro组件

在预制体Card上挂载了四个Text Mesh Pro组件&#xff0c;分别命名为Name、HP、ATK、Description。 将预制体Card挂载脚本CardDisplay用来在预制体上显示属性&#xff0c;并创建TextMeshPro对象来接收TextMeshPro组件。 using TMPro; using UnityEngine; using UnityEngine.UI;…

HCIP之PPP协议(PAP认证,CHAP认证)、GRE、MGRE综合实验

实验过程 一、IP配置 [r1]interface Serial 4/0/0 [r1-Serial4/0/0]ip ad 15.1.1.1 24 [r1]interface GigabitEthernet 0/0/0 [r1-GigabitEthernet0/0/0]ip ad 192.168.1.1 24 r2]interface Serial 4/0/0 [r2-Serial4/0/0]ip ad 25.1.1.2 24 [r2]interface GigabitEthernet 0/…