IoC 之深入分析 PropertyPlaceholderConfigurer
BeanFactoryPostProcessor 作用:在容器启动阶段,可以对解析好的 BeanDefinition 进行定制化处理,而其中 PropertyPlaceholderConfigurer 是其一个非常重要的应用,也是其子类 。
PropertyPlaceholderConfigurer
允许我们用 Properties 文件中的属性,来定义应用上下文(配置文件或者注解),即:我们可以在 XML 配置文件(或者其他方式,如注解方式)中使用占位符 的方式来定义一些资源,并将这些占位符所代表的资源配置到 Properties 中,这样只需要对 Properties 文件进行修改即可。
该类间接实现了 Aware 和 BeanFactoryPostProcessor 两大扩展接口,之前介绍BeanFactoryPostProcessor,知道了BeanFactoryPostProcessor 提供了 postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory)
接口方法,在这个体系中该方法的是在 PropertyResourceConfigurer 中实现,该类为属性资源的配置类,它实现了 BeanFactoryPostProcessor 接口:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 @Override public void postProcessBeanFactory (ConfigurableListableBeanFactory beanFactory) throws BeansException { try { Properties mergedProps = mergeProperties(); convertProperties(mergedProps); processProperties(beanFactory, mergedProps); } catch (IOException ex) { throw new BeanInitializationException ("Could not load properties" , ex); } }
<1>
处,调用 mergeProperties()
方法,返回合并的 Properties 实例。Properties 实例维护着一组 key-value
,其实就是 Properties 配置文件中的内容。
<2>
处,调用 convertProperties(Properties props)
方法,转换合并的值,其实就是将原始值替换为真正的值。
<3>
处,调用 processProperties(ConfigurableListableBeanFactory beanFactory, Properties props)
方法,前面两个步骤已经将配置文件中的值进行了处理,那么该方法就是真正的替换过程,该方法由子类实现 。
1 2 3 protected abstract void processProperties (ConfigurableListableBeanFactory beanFactory, Properties props) throws BeansException;
注: PropertyPlaceholderConfigurer
继承了PlaceholderConfigurerSupport
,PlaceholderConfigurerSupport
继承了PropertyResourceConfigurer
。
在 PropertyPlaceholderConfigurer 中,重写 processProperties(ConfigurableListableBeanFactory beanFactory, Properties props)
方法:
1 2 3 4 5 6 7 8 @Override protected void processProperties (ConfigurableListableBeanFactory beanFactoryToProcess, Properties props) throws BeansException { StringValueResolver valueResolver = new PlaceholderResolvingStringValueResolver (props); doProcessProperties(beanFactoryToProcess, valueResolver); }
PlaceholderResolvingStringValueResolver
类首先,构造一个 PlaceholderResolvingStringValueResolver 类型的 StringValueResolver 实例,该类是个内部类。StringValueResolver 为一个解析 String 类型值的策略接口,该接口提供了 resolveStringValue(String strVal)
方法,用于解析 String 值。PlaceholderResolvingStringValueResolver 为其一个解析策略:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 private class PlaceholderResolvingStringValueResolver implements StringValueResolver { private final PropertyPlaceholderHelper helper; private final PlaceholderResolver resolver; public PlaceholderResolvingStringValueResolver (Properties props) { this .helper = new PropertyPlaceholderHelper ( placeholderPrefix, placeholderSuffix, valueSeparator, ignoreUnresolvablePlaceholders); this .resolver = new PropertyPlaceholderConfigurerResolver (props); } }
在构造 String 值解析器 StringValueResolver 时,将已经解析的 Properties 实例对象封装在 PlaceholderResolver 实例 resolver
中。PlaceholderResolver 是一个用于解析字符串中包含占位符的替换值的策略接口,该接口有一个 resolvePlaceholder(String strVa)
方法,用于返回占位符的替换值。
还有一个 PropertyPlaceholderHelper 工具 helper
,从名字上面看应该是进行替换的工具类。
doProcessProperties()
方法得到 String 解析器的实例 valueResolver
后,则会调用 doProcessProperties(ConfigurableListableBeanFactory beanFactoryToProcess, StringValueResolver valueResolver)
方法,来进行真值的替换操作。该方法在父类 PlaceholderConfigurerSupport 中实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 protected void doProcessProperties (ConfigurableListableBeanFactory beanFactoryToProcess, StringValueResolver valueResolver) { BeanDefinitionVisitor visitor = new BeanDefinitionVisitor (valueResolver); String[] beanNames = beanFactoryToProcess.getBeanDefinitionNames(); for (String curName : beanNames) { if (!(curName.equals(this .beanName) && beanFactoryToProcess.equals(this .beanFactory))) { BeanDefinition bd = beanFactoryToProcess.getBeanDefinition(curName); try { visitor.visitBeanDefinition(bd); } catch (Exception ex) { throw new BeanDefinitionStoreException (bd.getResourceDescription(), curName, ex.getMessage(), ex); } } } beanFactoryToProcess.resolveAliases(valueResolver); beanFactoryToProcess.addEmbeddedValueResolver(valueResolver); }
<1> 处,根据 String 值解析策略 valueResolver
得到 BeanDefinitionVisitor 实例。BeanDefinitionVisitor 是 BeanDefinition 的访问者,我们通过它可以实现对 BeanDefinition 内容的进行访问,内容很多,例如 Scope、PropertyValues、FactoryMethodName 等等。
<2> 处,得到该容器的所有 BeanName,然后对其进行访问( visitBeanDefinition(BeanDefinition beanDefinition)
方法)。
<3> 处,解析别名的占位符。
<4> 处,解析嵌入值的占位符,例如注释属性。
visitBeanDefinition()
方法doProcessProperties()
的核心 在于 visitBeanDefinition(BeanDefinition beanDefinition)
方法的调用:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 public void visitBeanDefinition (BeanDefinition beanDefinition) { visitParentName(beanDefinition); visitBeanClassName(beanDefinition); visitFactoryBeanName(beanDefinition); visitFactoryMethodName(beanDefinition); visitScope(beanDefinition); if (beanDefinition.hasPropertyValues()) { visitPropertyValues(beanDefinition.getPropertyValues()); } if (beanDefinition.hasConstructorArgumentValues()) { ConstructorArgumentValues cas = beanDefinition.getConstructorArgumentValues(); visitIndexedArgumentValues(cas.getIndexedArgumentValues()); visitGenericArgumentValues(cas.getGenericArgumentValues()); } }
该方法基本访问了 BeanDefinition 中所有比较有用的东西了,包括 parent 、class 、factory-bean 、factory-method 、scope 、property 、constructor-arg 。
visitPropertyValues()
方法因为该篇主要内容是property
,所以重点只看visitPropertyValues()
方法。
过程就是对属性数组进行遍历,调用 resolveValue(Object value)
方法,对属性进行解析获取最新值,如果新值和旧值不等,则用新值替换旧值。:
1 2 3 4 5 6 7 8 9 10 11 12 13 protected void visitPropertyValues (MutablePropertyValues pvs) { PropertyValue[] pvArray = pvs.getPropertyValues(); for (PropertyValue pv : pvArray) { Object newVal = resolveValue(pv.getValue()); if (!ObjectUtils.nullSafeEquals(newVal, pv.getValue())) { pvs.add(pv.getName(), newVal); } } }
resolveValue()
方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 @Nullable protected Object resolveValue (@Nullable Object value) { if (value instanceof BeanDefinition) { visitBeanDefinition((BeanDefinition) value); } else if (value instanceof BeanDefinitionHolder) { visitBeanDefinition(((BeanDefinitionHolder) value).getBeanDefinition()); } else if (value instanceof RuntimeBeanReference) { RuntimeBeanReference ref = (RuntimeBeanReference) value; String newBeanName = resolveStringValue(ref.getBeanName()); if (newBeanName == null ) { return null ; } if (!newBeanName.equals(ref.getBeanName())) { return new RuntimeBeanReference (newBeanName); } } else if (value instanceof RuntimeBeanNameReference) { RuntimeBeanNameReference ref = (RuntimeBeanNameReference) value; String newBeanName = resolveStringValue(ref.getBeanName()); if (newBeanName == null ) { return null ; } if (!newBeanName.equals(ref.getBeanName())) { return new RuntimeBeanNameReference (newBeanName); } } else if (value instanceof Object[]) { visitArray((Object[]) value); } else if (value instanceof List) { visitList((List) value); } else if (value instanceof Set) { visitSet((Set) value); } else if (value instanceof Map) { visitMap((Map) value); } else if (value instanceof TypedStringValue) { TypedStringValue typedStringValue = (TypedStringValue) value; String stringValue = typedStringValue.getValue(); if (stringValue != null ) { String visitedString = resolveStringValue(stringValue); typedStringValue.setValue(visitedString); } } else if (value instanceof String) { return resolveStringValue((String) value); } return value; }
resolveStringValue()
方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 @Nullable protected String resolveStringValue (String strVal) { if (this .valueResolver == null ) { throw new IllegalStateException ("No StringValueResolver specified - pass a resolver object into the constructor or override the 'resolveStringValue' method" ); } String resolvedValue = this .valueResolver.resolveStringValue(strVal); return (strVal.equals(resolvedValue) ? strVal : resolvedValue); }
valueResolver
是我们在构造 BeanDefinitionVisitor 实例时传入的 String 类型解析器 PlaceholderResolvingStringValueResolver,调用其 resolveStringValue(String strVal)
方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 public PlaceholderResolvingStringValueResolver (Properties props) { this .helper = new PropertyPlaceholderHelper (placeholderPrefix, placeholderSuffix, valueSeparator, ignoreUnresolvablePlaceholders); this .resolver = new PropertyPlaceholderConfigurerResolver (props); }@Override @Nullable public String resolveStringValue (String strVal) throws BeansException { String resolved = this .helper.replacePlaceholders(strVal, this .resolver); if (trimValues) { resolved = resolved.trim(); } return (resolved.equals(nullValue) ? null : resolved); }
helper
为 PropertyPlaceholderHelper 实例对象,而 PropertyPlaceholderHelper 则是处理应用程序中包含占位符的字符串工具类。在构造 helper
实例对象时需要传入了几个参数:placeholderPrefix
、placeholderSuffix
、valueSeparator
,这些值在 PlaceholderConfigurerSupport 中定义如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 public static final String DEFAULT_PLACEHOLDER_PREFIX = "${" ;public static final String DEFAULT_PLACEHOLDER_SUFFIX = "}" ;public static final String DEFAULT_VALUE_SEPARATOR = ":" ;protected String placeholderPrefix = DEFAULT_PLACEHOLDER_PREFIX;protected String placeholderSuffix = DEFAULT_PLACEHOLDER_SUFFIX;@Nullable protected String valueSeparator = DEFAULT_VALUE_SEPARATOR;
replacePlaceholders()
方法
调用 PropertyPlaceholderHelper 的 replacePlaceholders(String value, PlaceholderResolver placeholderResolver)
方法,进行占位符替换:
1 2 3 4 5 public String replacePlaceholders (String value, PlaceholderResolver placeholderResolver) { Assert.notNull(value, "'value' must not be null" ); return parseStringValue(value, placeholderResolver, null ); }
调用 parseStringValue(String value, PlaceholderResolver placeholderResolver, Set visitedPlaceholders)
方法,该方法是本篇最核心的地方 ,${}
占位符的替换:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 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" ); } placeholder = parseStringValue(placeholder, placeholderResolver, visitedPlaceholders); 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 ) { 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) { 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(); }
获取占位符前缀 "${"
的索引位置 startIndex
。
如果前缀 "${"
存在,则从 “{”
后面开始获取占位符后缀 “}” 的索引位置 endIndex
。
如果前缀 “${”
和后缀 "}"
都存在,则截取中间部分 placeholder
。
从 Properties 中获取 placeHolder
对应的值 propVal
。
如果 propVal
为空,则判断占位符中是否存在 ":"
,如果存在则对占位符进行分割处理,前面部分为 actualPlaceholder
,后面部分 defaultValue
,尝试从 Properties 中获取 actualPlaceholder
对应的值 propVal
,如果不存在,则将 defaultValue
的值赋值给 propVal
返回 propVal
,也就是 Properties 中对应的值。