在讲解properties文件中${…}替换之前,首先介绍一下BeanFactoryPostProcessor
类。
BeanFactoryPostProcessor讲解
BeanFactoryPostProcessor
和BeanPostProcessor
,这两个接口,都是spring初始化bean时对外暴露的扩展点
。两个接口名称看起来很相似,但作用及使用场景却不同。
BeanFactoryPostProcessor 接口
源码
1
2
3public interface BeanFactoryPostProcessor {
void postProcessBeanFactory(ConfigurableListableBeanFactory var1) throws BeansException;
}此接口只有一个方法,接受
ConfigurableListableBeanFactory
参数。实现该接口,可以在spring的bean创建之前,修改bean的定义属性
。即读取bean的配置元数据,并可以根据需要进行修改(直白点,就是修改bean的BeanDefinition中相应信息)。由Bean加载流程梳理之BeanDefinitions加载 可知,在获取BeanFactory时,即接口中唯一方法的参数,Spring框架会将加载的BeanDefinitions放进BeanFactory,因此该接口中的方法可以获取所有bean的BeanDefinition。然后进行相应修改。
BeanFactoryPostProcessor是在spring容器加载了bean的定义文件之后,在bean实例化之前执行的
。
BeanPostProcessor接口
源码
1
2
3
4
5
6
7public interface BeanPostProcessor {
//spring容器实例化bean之后,在执行bean的初始化之前进行的处理
Object postProcessBeforeInitialization(Object var1, String var2) throws BeansException;
//spring容器实例化bean之后,在执行bean的初始化之后进行的处理
Object postProcessAfterInitialization(Object var1, String var2) throws BeansException;
}该接口中有两个方法,分别在bean初始化前后添加一些自己的处理逻辑。由
initializeBean
源码可知,这里说的初始化方法,指的是下面两种:- bean实现了InitializingBean接口,对应的方法为afterPropertiesSet
在bean定义的时候,通过init-method设置的方法。
initializeBean源码
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
32protected Object initializeBean(final String beanName, final Object bean, RootBeanDefinition mbd) {
if(System.getSecurityManager() != null) {
AccessController.doPrivileged(new PrivilegedAction() {
public Object run() {
AbstractAutowireCapableBeanFactory.this.invokeAwareMethods(beanName, bean);
return null;
}
}, this.getAccessControlContext());
} else {
this.invokeAwareMethods(beanName, bean);
}
Object wrappedBean = bean;
if(mbd == null || !mbd.isSynthetic()) {
////spring容器实例化bean之后,在执行bean的初始化之前进行的处理
wrappedBean = this.applyBeanPostProcessorsBeforeInitialization(bean, beanName);
}
try {
//bean初始化方法执行。该方法具体实现是判断bean是否继承InitializingBean,是,执行afterPropertiesSet方法;是否配置了init-method,设置了,执行相应方法。
this.invokeInitMethods(beanName, wrappedBean, mbd);
} catch (Throwable var6) {
throw new BeanCreationException(mbd != null?mbd.getResourceDescription():null, beanName, "Invocation of init method failed", var6);
}
if(mbd == null || !mbd.isSynthetic()) {
////spring容器实例化bean之后,在执行bean的初始化之后进行的处理
wrappedBean = this.applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName);
}
return wrappedBean;
}
以上内容重点讲解了BeanFactoryPostProcessor
接口,主要作用就是:在bean实例创建之前,可以根据需要修改bean的BeanDefinition,从而达到bean的修改。有了上面的基础,下面继续学习配置文件中占位符替换的知识。
项目配置
applicationContext-database.xml配置
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd">
<!-- 此类是重点,该类也是本次重点讲解对象 -->
<bean id="propertyConfigurer" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<property name="locations">
<list>
<value>classpath:properties/database.properties</value>
</list>
</property>
</bean>
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
<property name="url" value="${mysql.url}" />
<property name="username" value="${mysql.username}" />
<property name="password" value="${mysql.password}" />
</bean>
</beans>database.properties配置文件
1 | mysql.url=jdbc:mysql://db.frogshealth.com:3306/mcenter?characterEncoding=utf8&useUnicode=true |
现在重点讲解如何将properties文件中的属性读入并替换”${}”占位符。
PropertyPlaceholderConfigurer源码
在applicationContext-database.xml文件中我们看到了一个类PropertyPlaceholderConfigurer,顾名思义它就是一个属性占位符配置器,看一下这个类的继承关系图。
类图:
由类图可知,PropertyPlaceholderConfigurer是BeanFactoryPostProcessor接口的实现类。由此可知,Spring上下文必然是在Bean定义全部加载完毕后且Bean实例化之前通过postProcessBeanFactory方法一次性地替换了占位符”${}”。
回顾一下,在加载beanDefinitions后(AbstractApplicationContext类中的refresh()方法),会触发BeanFactoryPostProcessors
的子类执行。
refresh源码
1 | public void refresh() throws BeansException, IllegalStateException { |
invokeBeanFactoryPostProcessors源码
1 | //为AbstractApplicationContext类中相应方法 |
1 | //PostProcessorRegistrationDelegate类中方法 |
功能分析:
- 如果beanFactory为BeanDefinitionRegistry的子类,则先执行通过AbstractApplicationContext.addBeanFactoryPostProcessor()加进去的BeanFactoryPostProcessor接口子类的postProcessBeanFactory()方法
- 如果有自定义的BeanFactoryPostProcessor子类,则执行Spring配置文件中自定义的BeanFactoryPostProcessor接口子类的postProcessBeanFactory()方法。
PropertyResourceConfigurer类中postProcessBeanFactory源码
在applicationContext-database.xml配置文件中,我们定义了名为propertyConfigurer的bean,该bean为BeanFactoryPostProcessor的子类。通过上述源码分析可知,在触发BeanFactoryPostProcessor子类执行时,PropertyPlaceholderConfigurer中的postProcessBeanFactory会执行,进行${}替换操作。1
2
3
4
5
6
7
8
9
10
11
12
13
14public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
try {
//合并了.properties文件(之所以叫做合并是因为多个.properties文件中可能有相同的Key)
Properties ex = this.mergeProperties();
//必要的情况下对合并的P roperties进行转换
this.convertProperties(ex);
//开始替换占位符"${...}"
this.processProperties(beanFactory, ex);
} catch (IOException var3) {
throw new BeanInitializationException("Could not load properties", var3);
}
}
doProcessProperties源码
在processProperties()方法中,通过PlaceholderResolvingStringValueResolver对象,即一个持有.properties文件配置的字符串值解析器,然后去执行PlaceholderConfigurerSupport类中的doProcessProperties()方法。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
28protected void doProcessProperties(ConfigurableListableBeanFactory beanFactoryToProcess, StringValueResolver valueResolver) {
//获取BeanDefinitionVistor对象,即一个Bean定义访问工具,持有字符串值解析器。然后可以通过BeanDefinitionVistor访问Bean定义,在遇到需要解析的字符串的时候使用构造函数传入的StringValueResolver解析字符串。
BeanDefinitionVisitor visitor = new BeanDefinitionVisitor(valueResolver);
//获取所有Bean定义的名称
String[] beanNames = beanFactoryToProcess.getBeanDefinitionNames();
String[] var5 = beanNames;
int var6 = beanNames.length;
//遍历所有Bean定义的名称,注意判断"!(curName.equals(this.beanName)"中,this.beanName指的是PropertyPlaceholderConfigurer,意为PropertyPlaceholderConfigurer本身不会去解析占位符"${...}"。
for(int var7 = 0; var7 < var6; ++var7) {
String curName = var5[var7];
if(!curName.equals(this.beanName) || !beanFactoryToProcess.equals(this.beanFactory)) {
//获取bean的BeanDefinition对象。
BeanDefinition bd = beanFactoryToProcess.getBeanDefinition(curName);
try {
//调用BeanDefinitionVisitor对象的visitBeanDefinition()方法 visitor.visitBeanDefinition(bd);
} catch (Exception var11) {
throw new BeanDefinitionStoreException(bd.getResourceDescription(), curName, var11.getMessage(), var11);
}
}
}
beanFactoryToProcess.resolveAliases(valueResolver);
beanFactoryToProcess.addEmbeddedValueResolver(valueResolver);
}
功能分析:
上述代码的功能主要是获取BeanDefinitionVisitor对象,然后遍历已加载的BeanDefinitions,调用BeanDefinitionVisitor对象的visitBeanDefinition()方法。
visitBeanDefinition源码
1 | public void visitBeanDefinition(BeanDefinition beanDefinition) { |
功能分析:
该方法轮番访问
visitPropertyValues源码
1 | protected void visitPropertyValues(MutablePropertyValues pvs) { |
resolveValue源码
1 | protected Object resolveValue(Object value) { |
功能分析:
主要对value类型做一个判断,然后执行相应方法处理。已resolveStringValue()方法为例分析。
resolveStringValue源码
1 | protected String resolveStringValue(String strVal) { |
resolveStringValue源码
1 | public String resolveStringValue(String strVal) throws BeansException { |
经过一系列处理,会执行到PropertyPlaceholderHelper类中的parseStringValue()方法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
61protected String parseStringValue(String strVal, PropertyPlaceholderHelper.PlaceholderResolver placeholderResolver, Set<String> visitedPlaceholders) {
StringBuilder result = new StringBuilder(strVal);
//获取占位符前缀"${"的位置索引startIndex
int startIndex = strVal.indexOf(this.placeholderPrefix);
while(startIndex != -1) {
//占位符前缀"{"存在,从"{"后面开始获取占位符后缀"}"的位置索引endIndex
int endIndex = this.findPlaceholderEndIndex(result, startIndex);
if(endIndex != -1) {
//如果占位符前缀位置索引startIndex与占位符后缀的位置索引endIndex都存在,截取中间的部分placeHolder
String placeholder = result.substring(startIndex + this.placeholderPrefix.length(), endIndex);
String originalPlaceholder = placeholder;
if(!visitedPlaceholders.add(placeholder)) {
throw new IllegalArgumentException("Circular placeholder reference \'" + placeholder + "\' in property definitions");
}
placeholder = this.parseStringValue(placeholder, placeholderResolver, visitedPlaceholders);
//从Properties中获取placeHolder对应的值propVal
String propVal = placeholderResolver.resolvePlaceholder(placeholder);
//如果propVal不存在,尝试对placeHolder使用":"进行一次分割,如果分割出来有结果,那么前面一部分命名为actualPlaceholder,后面一部分命名为defaultValue,尝试从Properties中获取actualPlaceholder对应的value,如果存在则取此value,如果不存在则取defaultValue,最终赋值给propVal
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 = this.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) {
throw new IllegalArgumentException("Could not resolve placeholder \'" + placeholder + "\' in string value \"" + strVal + "\"");
}
startIndex = result.indexOf(this.placeholderPrefix, endIndex + this.placeholderSuffix.length());
}
visitedPlaceholders.remove(originalPlaceholder);
} else {
startIndex = -1;
}
}
return result.toString();
}
功能分析:
- 获取占位符前缀”${“的位置索引startIndex
- 占位符前缀”{“存在,从”{“存在,从”{“后面开始获取占位符后缀”}”的位置索引endIndex
- 如果占位符前缀位置索引startIndex与占位符后缀的位置索引endIndex都存在,截取中间的部分placeHolder
- 从Properties中获取placeHolder对应的值propVal
- 如果propVal不存在,尝试对placeHolder使用”:”进行一次分割,如果分割出来有结果,那么前面一部分命名为actualPlaceholder,后面一部分命名为defaultValue,尝试从Properties中获取actualPlaceholder对应的value,如果存在则取此value,如果不存在则取defaultValue,最终赋值给propVal
- 返回propVal,就是替换之后的值
通过这样一整个的流程,将占位符”${…}”中的内容替换为了我们需要的值。