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 中对应的值。