PropertyPlaceholderConfigurer
用于加载指定的配置文件,替换 bean 配置中的占位符为指定的属性值。
用法 首先看下它的使用方法,以便有个直观的感受,顺便思考下它是怎么做到的,这样有目的性去看源码会有更好的效果。
在配置 PropertyPlaceholderConfigurer
时,一般通过 locations
属性指定多个 property 源文件,比如下面的示例
1 2 3 4 5 6 7 <bean class ="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer" > <property name ="locations" > <list > <value > classpath:jdbc.properties</value > </list > </property > </bean >
它指定了一个配置文件:jdbc.properties
,位于 classpath 下。该属性文件的内容如下:
1 2 3 4 jdbc.driverClassName =com.mysql.jdbc.Driverjdbc.url =jdbc:mysql://127.0 .0.1 /demos?useUnicode=true &jdbc.username =userNamejdbc.password =passWord
配置好这些之后,就可以通过占位符引用配置文件中的属性。例如在配置 bean 时,${jdbc.driverClassName}
会被替换成 com.mysql.jdbc.Driver
1 2 3 4 5 6 <bean id ="dataSource" class ="org.apache.commons.dbcp2.BasicDataSource" > <property name ="driverClassName" value ="${jdbc.driverClassName}" /> <property name ="url" value ="${jdbc.url}" /> <property name ="username" value ="${jdbc.username}" /> <property name ="password" value ="${jdbc.password}" /> </bean >
这样做的好处是将配置从代码中分离了出来,单独管理,提高了灵活性,换种方式说,就是将配置和代码解耦了。
源码阅读 接下来看下源码,看看 PropertyPlaceholderConfigurer
是如何实现上面提到的功能的:比如是如何读的配置文件、结果放在何处、在何时替换的 bean 配置等等。
首先还是看下类图,这样做的目的是对类的结构有一个整体的认识,以便在看到相关类时,不至于迷失在茫茫代码中。
需要说明的是,为了避免干扰,我从图中删去了 Ordered
相关接口和 Aware
相关接口,但这并不影响源码的阅读和理解。
从上往下看,首先看到 BeanFactoryPostProcessor
接口,想必你已经很熟悉了,它提供了一个 postProcessBeanFactory
回调方法,可以对 ConfigurableListableBeanFactory
进行一些操作。
然后就是 PropertiesLoaderSupport
类
PropertiesLoaderSupport PropertiesLoaderSupport
是一个抽象的基类,它用于从一个或多个 Resource 中加载 properties,另外还支持通过 api 设置本地 properties(local properties)。
loadProperties
是它的一个核心方法,用于加载 properties 到指定的实例中,它的内部是通过 PropertiesLoaderUtils
类的 fillProperties
方法实现单个 Resource 的加载的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 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 ex) { if (this .ignoreResourceNotFound) { if (logger.isDebugEnabled()) { logger.debug("Properties resource not found: " + ex.getMessage()); } } else { throw ex; } } } } }
PropertiesLoaderUtils.fillProperties
则是通过指定的 PropertiesPersister
策略加载资源
1 2 3 4 5 6 7 8 9 10 11 12 13 String filename = resource.getResource().getFilename(); if (filename != null && filename.endsWith(XML_FILE_EXTENSION)) { stream = resource.getInputStream(); persister.loadFromXml(props, stream); } else if (resource.requiresReader()) { reader = resource.getReader(); persister.load(props, reader); } else { stream = resource.getInputStream(); persister.load(props, stream); }
在 loadProperties
的基础上,mergeProperties
方法封装了生成最终 Properties
的策略,它主要是根据配置的覆盖策略对 localProperties
以及从 Resource 中加载出来的 Properties
进行合并处理
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 protected Properties mergeProperties () throws IOException { Properties result = new Properties(); if (this .localOverride) { loadProperties(result); } if (this .localProperties != null ) { for (Properties localProp : this .localProperties) { CollectionUtils.mergePropertiesIntoMap(localProp, result); } } if (!this .localOverride) { loadProperties(result); } return result; }
再往下看到的是PropertyResourceConfigurer
类,它也是一个抽象类,主要封装了配置 bean 属性值 的流程。为了能够对 beanFactory 中的所有 BeanDefinition
进行处理,PropertyResourceConfigurer
实现了 BeanFactoryPostProcessor
的 postProcessBeanFactory
方法,这样在拿到 properties 之后就可以对 beanFactory 进行操作了。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 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); } }
它的流程如下:
获取合并后的 Properties 必要情况下对 Properties 进行转换,比如对 value 进行一些特殊的处理 将属性值应用到 beanFactory 中,这个交给子类实现 这也是一个抽象类,从名字上就可以看出,它用于对占位符进行处理。这里定义了几个常量,分别是占位符前缀、占位符后缀、属性值分隔符
1 2 3 4 5 6 7 8 public static final String DEFAULT_PLACEHOLDER_PREFIX = "${" ;public static final String DEFAULT_PLACEHOLDER_SUFFIX = "}" ;public static final String DEFAULT_VALUE_SEPARATOR = ":" ;
PlaceholderConfigurerSupport
提供了一个模板方法:doProcessProperties
,注意它不是 PropertyResourceConfigurer
中留给子类实现的 processProperties
方法。
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 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); }
它的主要思路是:遍历 beanFactory
中的 BeanDefinition
,使用 BeanDefinitionVisitor
去访问每一个 BeanDefinition
进行处理。很明显 visitor.visitBeanDefinition(bd)
需要对 BeanDefinition
的每一个属性做处理。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 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()); } }
这里可以看到都有哪些配置支持占位符替换。以 parentName 的处理为例,看下是如何实现占位符替换的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 protected void visitParentName (BeanDefinition beanDefinition) { String parentName = beanDefinition.getParentName(); if (parentName != null ) { String resolvedName = resolveStringValue(parentName); if (!parentName.equals(resolvedName)) { beanDefinition.setParentName(resolvedName); } } } 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); }
这里的关键在于 resolveStringValue
方法,它将原始字符串解析为替换占位符后的值,它是通过 valueResolver
的 resolveStringValue
方法实现的。
PropertyPlaceholderConfigurer
是最终的实现类,它实现了 PropertyResourceConfigurer
的 processProperties
方法
1 2 3 4 5 6 7 8 9 protected void processProperties (ConfigurableListableBeanFactory beanFactoryToProcess, Properties props) throws BeansException { StringValueResolver valueResolver = new PlaceholderResolvingStringValueResolver(props); doProcessProperties(beanFactoryToProcess, valueResolver); }
它主要职责是构建解析器,然后将解析工作交给父类 PlaceholderConfigurerSupport
的 doProcessProperties
方法来处理,因此我们只需关心解析器的构建。
PlaceholderResolvingStringValueResolver 它就是解析器,用于解析占位符为配置的属性值。该类有 PropertyPlaceholderHelper
和 PlaceholderResolver
两个属性
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 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); } }
它用于将解析出来的占位符转换为配置的属性值
1 2 3 4 public String resolvePlaceholder (String placeholderName) { return PropertyPlaceholderConfigurer.this .resolvePlaceholder(placeholderName, this .props, systemPropertiesMode); }
转换操作就是从配置的 key-value 中查询属性值,根据 systemPropertiesMode 配置的不同,取数据的 properties 来源也有所不同
1 2 3 4 5 6 7 8 9 10 11 12 13 protected String resolvePlaceholder (String placeholder, Properties props, int systemPropertiesMode) { String propVal = null ; if (systemPropertiesMode == SYSTEM_PROPERTIES_MODE_OVERRIDE) { propVal = resolveSystemProperty(placeholder); } if (propVal == null ) { propVal = resolvePlaceholder(placeholder, props); } if (propVal == null && systemPropertiesMode == SYSTEM_PROPERTIES_MODE_FALLBACK) { propVal = resolveSystemProperty(placeholder); } return propVal; }
PropertyPlaceholderHelper 它用来解析原始字符串,找到占位符并替换成对应属性值
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 72 73 74 75 76 77 78 79 80 81 82 83 84 85 protected String parseStringValue ( String value, PlaceholderResolver placeholderResolver, Set<String> visitedPlaceholders) { StringBuilder result = new StringBuilder(value); int startIndex = value.indexOf(this .placeholderPrefix); 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.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(); }