前言:我们知道在Spring中经常使用配置文件的形式对进行属性的赋值,那配置文件的值是怎么赋值到属性上的呢,本文将对其进行分析。
首先了解一个类:PropertySourcesPlaceholderConfigurer,该类对程序中使用占位符的方式对属性进行赋值的形式进行解析,如在xml配置文件中进行了key-value的配置,在程序中使用该配置的值的形式。
分析:
从PropertySourcesPlaceholderConfigurer的继承结构图上可知,PropertySourcesPlaceholderConfigurer间接实现了Aware和BeanFactoryPostProcessor两大接口,这里只关注BeanFactoryPostProcessor(Aware接口前面已经分析了),BeanFactoryPostProcessor接口中就只有一个postProcessBeanFactory方法,其实现如下:
1 // PropertySourcesPlaceholderConfigurer 2 public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException { 3 // 如果propertySources为null,则初始化该属性 4 if (this.propertySources == null) { 5 this.propertySources = new MutablePropertySources(); 6 if (this.environment != null) { 7 this.propertySources.addLast( 8 new PropertySource<Environment>(ENVIRONMENT_PROPERTIES_PROPERTY_SOURCE_NAME, this.environment) { 9 @Override 10 @Nullable 11 public String getProperty(String key) { 12 return this.source.getProperty(key); 13 } 14 } 15 ); 16 } 17 try { 18 // 构建localPropertySource对象,Properties通过mergeProperties方法获取。 19 PropertySource<?> localPropertySource = 20 new PropertiesPropertySource(LOCAL_PROPERTIES_PROPERTY_SOURCE_NAME, mergeProperties()); 21 if (this.localOverride) { 22 this.propertySources.addFirst(localPropertySource); 23 } 24 else { 25 this.propertySources.addLast(localPropertySource); 26 } 27 } 28 catch (IOException ex) { 29 throw new BeanInitializationException("Could not load properties", ex); 30 } 31 } 32 // 进行占位符替换 33 processProperties(beanFactory, new PropertySourcesPropertyResolver(this.propertySources)); 34 this.appliedPropertySources = this.propertySources; 35 }
分析:
- 首先需要构建MutablePropertySources对象,用于存储Properties,注意Properties是通过mergeProperties获取。
- 占位符的替换过程在processProperties函数中实现。
PropertiesLoaderSupport#mergeProperties
1 protected Properties mergeProperties() throws IOException { 2 // 创建Properties对象 3 Properties result = new Properties(); 4 5 // 是否允许覆盖配置 之前 6 if (this.localOverride) { 7 // Load properties from file upfront, to let local properties override. 8 loadProperties(result); 9 } 10 11 // 进行配置合并 12 if (this.localProperties != null) { 13 for (Properties localProp : this.localProperties) { 14 CollectionUtils.mergePropertiesIntoMap(localProp, result); 15 } 16 } 17 // 再次进行覆盖 之后 18 if (!this.localOverride) { 19 // Load properties from file afterwards, to let those properties override. 20 loadProperties(result); 21 } 22 23 return result; 24 }
分析:
这里其实逻辑简单,主要通过loadProperties进行配置文件的导入。
PropertiesLoaderSupport#loadProperties
1 protected void loadProperties(Properties props) throws IOException { 2 // 遍历文件路径 3 if (this.locations != null) { 4 for (Resource location : this.locations) { 5 if (logger.isTraceEnabled()) { 6 logger.trace("Loading properties file from " + location); 7 } 8 try { 9 PropertiesLoaderUtils.fillProperties( 10 props, new EncodedResource(location, this.fileEncoding), this.propertiesPersister); 11 } catch (FileNotFoundException | UnknownHostException ex) { 12 if (this.ignoreResourceNotFound) { 13 if (logger.isDebugEnabled()) { 14 logger.debug("Properties resource not found: " + ex.getMessage()); 15 } 16 } else { 17 throw ex; 18 } 19 } 20 } 21 } 22 }
分析:
这里代码逻辑简单,变量locations对象,然后通过PropertiesLoaderUtils#fillProperties方法进行属性填充,这里不对该方法进行分析,有兴趣的可以自己详细看下。
PropertiesLoaderSupport#processProperties
1 protected void processProperties(ConfigurableListableBeanFactory beanFactoryToProcess, 2 final ConfigurablePropertyResolver propertyResolver) throws BeansException { 3 // 设置"${"、"}"、":" 4 propertyResolver.setPlaceholderPrefix(this.placeholderPrefix); 5 propertyResolver.setPlaceholderSuffix(this.placeholderSuffix); 6 propertyResolver.setValueSeparator(this.valueSeparator); 7 8 // 根据是否需要忽略不能解析的符号进行分支处理 9 StringValueResolver valueResolver = strVal -> { 10 String resolved = (this.ignoreUnresolvablePlaceholders ? 11 propertyResolver.resolvePlaceholders(strVal) : 12 propertyResolver.resolveRequiredPlaceholders(strVal)); 13 if (this.trimValues) { 14 resolved = resolved.trim(); 15 } 16 return (resolved.equals(this.nullValue) ? null : resolved); 17 }; 18 19 // 进行具体解析 20 doProcessProperties(beanFactoryToProcess, valueResolver); 21 }
分析:
从函数的具体逻辑可以看出Spring中对占位符的解析只包含"${ }"和":"。
- 创建StringValueResolver对象,根据不同的策略(是否要忽略不能解析的符号),这里会在doProcessProperties中进行回调。其实resolvePlaceholders和resolveRequiredPlaceholders函数内部都调用的同一个函数,只是其初始化方式不同(false和true的区别),具体代码AbstractPropertyResolver中。
- 最终的具体解析过程落在doProcessProperties函数中。
AbstractPropertyResolver#resolveRequiredPlaceholders
1 public String resolveRequiredPlaceholders(String text) throws IllegalArgumentException { 2 // 如果strictHelper为null,则通过createPlaceholderHelper进行创建 3 // 注意这里createPlaceholderHelper入参为false,这就是和resolvePlaceholders不一样的地方 4 if (this.strictHelper == null) { 5 this.strictHelper = createPlaceholderHelper(false); 6 } 7 // 通过PropertyPlaceholderHelper进行解析 8 return doResolvePlaceholders(text, this.strictHelper); 9 } 10 11 private PropertyPlaceholderHelper createPlaceholderHelper(boolean ignoreUnresolvablePlaceholders) { 12 return new PropertyPlaceholderHelper(this.placeholderPrefix, this.placeholderSuffix, 13 this.valueSeparator, ignoreUnresolvablePlaceholders); 14 } 15 16 private String doResolvePlaceholders(String text, PropertyPlaceholderHelper helper) { 17 return helper.replacePlaceholders(text, this::getPropertyAsRawString); 18 } 19 // PropertyPlaceholderHelper 20 public String replacePlaceholders(String value, PlaceholderResolver placeholderResolver) { 21 Assert.notNull(value, "'value' must not be null"); 22 return parseStringValue(value, placeholderResolver, new HashSet<>()); 23 }
分析:
以上代码逻辑还是比较简单,最终的解析落入PropertyPlaceholderHelper#replacePlaceHolders函数中,该函数在【spring源码分析】IOC容器初始化(一)中已分析。
PlaceholderConfigurerSupport#doProcessProperties
1 protected void doProcessProperties(ConfigurableListableBeanFactory beanFactoryToProcess, 2 StringValueResolver valueResolver) { 3 4 // 创建BeanDefinitionVisitor对象 5 BeanDefinitionVisitor visitor = new BeanDefinitionVisitor(valueResolver); 6 7 // 对BeanDefinition进行遍历 8 String[] beanNames = beanFactoryToProcess.getBeanDefinitionNames(); 9 for (String curName : beanNames) { 10 // Check that we're not parsing our own bean definition, 11 // to avoid failing on unresolvable placeholders in properties file locations. 12 // 校验 13 // 当前PlaceholderConfigurerSupport不在解析范围内 同一个Spring容器 14 if (!(curName.equals(this.beanName) && beanFactoryToProcess.equals(this.beanFactory))) { 15 BeanDefinition bd = beanFactoryToProcess.getBeanDefinition(curName); 16 try { 17 // 访问bean,其实是对属性进行解析 18 visitor.visitBeanDefinition(bd); 19 } 20 catch (Exception ex) { 21 throw new BeanDefinitionStoreException(bd.getResourceDescription(), curName, ex.getMessage(), ex); 22 } 23 } 24 } 25 26 // New in Spring 2.5: resolve placeholders in alias target names and aliases as well. 27 // 别名占位符 28 beanFactoryToProcess.resolveAliases(valueResolver); 29 30 // New in Spring 3.0: resolve placeholders in embedded values such as annotation attributes. 31 // 解析嵌入值的占位符,例如注释属性 32 beanFactoryToProcess.addEmbeddedValueResolver(valueResolver); 33 }
分析:
- 首先创建BeanDefinitionVisitor对象,通过字符串解析器,用于后期对配置文件进行解析。
- 对BeanDefinition进行遍历,通过visitBeanDefinition方法进行配置文件解析。
- 解析别名占位符。
- 解析嵌入值的占位符,例如注释属性。
其实整个方法的核心在于visitBeanDefinition方法。
BeanDefinitionVisitor#visitBeanDefinition
1 public void visitBeanDefinition(BeanDefinition beanDefinition) { 2 visitParentName(beanDefinition); 3 visitBeanClassName(beanDefinition); 4 visitFactoryBeanName(beanDefinition); 5 visitFactoryMethodName(beanDefinition); 6 visitScope(beanDefinition); 7 if (beanDefinition.hasPropertyValues()) { 8 visitPropertyValues(beanDefinition.getPropertyValues()); 9 } 10 if (beanDefinition.hasConstructorArgumentValues()) { 11 ConstructorArgumentValues cas = beanDefinition.getConstructorArgumentValues(); 12 visitIndexedArgumentValues(cas.getIndexedArgumentValues()); 13 visitGenericArgumentValues(cas.getGenericArgumentValues()); 14 } 15 }
分析:
该方法基本访问了BeanDefinition中所有值得访问的东西了,包括parent 、class 、factory-bean 、factory-method 、scope 、property 、constructor-arg。
这里关注visitPropertyValues方法。
BeanDefinitionVisitor#visitPropertyValues
1 protected void visitPropertyValues(MutablePropertyValues pvs) { 2 // 遍历PropertyValue数组 3 PropertyValue[] pvArray = pvs.getPropertyValues(); 4 for (PropertyValue pv : pvArray) { 5 // 解析真值 6 Object newVal = resolveValue(pv.getValue()); 7 if (!ObjectUtils.nullSafeEquals(newVal, pv.getValue())) { 8 // 设置到PropertyValue中 9 pvs.add(pv.getName(), newVal); 10 } 11 } 12 }
分析:
该函数主要就是对属性数组进行遍历,通过resolveValue方法对属性进行解析获取最新值,如果新值与就只不相等,则用新值替换旧值。
BeanDefinitionVisitor#resolveValue
1 protected Object resolveValue(@Nullable Object value) { 2 if (value instanceof BeanDefinition) { 3 visitBeanDefinition((BeanDefinition) value); 4 } 5 else if (value instanceof BeanDefinitionHolder) { 6 visitBeanDefinition(((BeanDefinitionHolder) value).getBeanDefinition()); 7 } 8 else if (value instanceof RuntimeBeanReference) { 9 RuntimeBeanReference ref = (RuntimeBeanReference) value; 10 String newBeanName = resolveStringValue(ref.getBeanName()); 11 if (newBeanName == null) { 12 return null; 13 } 14 if (!newBeanName.equals(ref.getBeanName())) { 15 return new RuntimeBeanReference(newBeanName); 16 } 17 } 18 else if (value instanceof RuntimeBeanNameReference) { 19 RuntimeBeanNameReference ref = (RuntimeBeanNameReference) value; 20 String newBeanName = resolveStringValue(ref.getBeanName()); 21 if (newBeanName == null) { 22 return null; 23 } 24 if (!newBeanName.equals(ref.getBeanName())) { 25 return new RuntimeBeanNameReference(newBeanName); 26 } 27 } 28 else if (value instanceof Object[]) { 29 visitArray((Object[]) value); 30 } 31 else if (value instanceof List) { 32 visitList((List) value); 33 } 34 else if (value instanceof Set) { 35 visitSet((Set) value); 36 } 37 else if (value instanceof Map) { 38 visitMap((Map) value); 39 } 40 else if (value instanceof TypedStringValue) { 41 TypedStringValue typedStringValue = (TypedStringValue) value; 42 String stringValue = typedStringValue.getValue(); 43 if (stringValue != null) { 44 String visitedString = resolveStringValue(stringValue); 45 typedStringValue.setValue(visitedString); 46 } 47 } 48 // 由于Properties中的是String,所以重点在此 49 else if (value instanceof String) { 50 return resolveStringValue((String) value); 51 } 52 return value; 53 }
分析:
该函数对各种类型的属性进行解析,由于配置为String类型的,因此这里关注resolveStringValue函数。
BeanDefinitionVisitor#resolveStringValue
1 protected String resolveStringValue(String strVal) { 2 if (this.valueResolver == null) { 3 throw new IllegalStateException("No StringValueResolver specified - pass a resolver " + 4 "object into the constructor or override the 'resolveStringValue' method"); 5 } 6 // 解析真值 7 String resolvedValue = this.valueResolver.resolveStringValue(strVal); 8 // Return original String if not modified. 9 return (strVal.equals(resolvedValue) ? strVal : resolvedValue); 10 }
分析:
这里具体的解析就会回调valueResolver(根据不同策略创建的StringValueResolver对象),然后进入具体的解析,其解析过程已经分析,这里不在赘述。
总结
至此关于占位符的解析过程就大致分析完了,其实里面还有很多值得我们细究的地方,具体过程可debug调试一遍,可能会有更深的理解。
by Shawn Chen,2019.05.08,晚。