Spring版本
5.2.5.RELEASE
参考
源码解读
PropertyOverrideConfigurer
允许我们对 Spring 容器中配置的任何我们想处理的 bean 定义的 property
信息进行覆盖替换。它与PropertySourcesPlaceholderConfigurer
的区别在于,PropertyOverrideConfigurer
在于替换已经具体化的property信息,而PropertySourcesPlaceholderConfigurer
做的事情是,property
还只是一个占位符,是将占位符解析成具体的property
信息。
Demo
1.1 Student
public class Student {
private String id;
private String name;
private String desc;
// 省略 getter、setter
}
1.2 spring.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="propertyConfig" class="org.springframework.beans.factory.config.PropertyOverrideConfigurer">
<property name="locations">
<list>
<value>classpath:application-dev.properties</value>
</list>
</property>
</bean>
<bean id="student" class="com.kungyu.custom.element.Student">
<property name="name" value="name"/>
</bean>
</beans>
1.3 application-dev.property
student.name=student-dev
1.4 测试
public class Test {
public static void main(String[] args) throws ClassNotFoundException {
ApplicationContext context = new ClassPathXmlApplicationContext("spring.xml");
Student student = (Student) context.getBean("student");
System.out.println(student.getName());
}
输出结果:
student-dev
可以看到,在spring.xml
中配置的student
的name
属性原本的值name
已经被替换成student-dev
了。
那么,如果同时配置PropertySourcesPlaceholderConfigurer
和PropertyOverrideConfigurer
,哪个会生效呢?在测试之前,不妨猜想一下:
- 假如
PropertySourcesPlaceholderConfigurer
先生效,那么占位符首先会被解析成对应的配置值,之后再被PropertyOverrideConfigurer
的值覆盖 - 如果
PropertyOverrideConfigurer
先生效,那么替换后,执行PropertySourcesPlaceholderConfigurer
的时候可能查找不到对应的值,那么最后显示的也是PropertyOverrideConfigurer
配置的值
俩者的结果是一样的,那么,动手实现一下。
添加以下俩个配置文件
application1.properties:
student.name=student-PropertySourcesPlaceholderConfigurer
application2.properties:
student.name=student-PropertyOverrideConfigurer
修改spring.xml如下:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="propertySourcesPlaceholderConfigurer" class="org.springframework.context.support.PropertySourcesPlaceholderConfigurer">
<property name="locations">
<list>
<value>classpath:application1.properties</value>
</list>
</property>
</bean>
<bean id="propertyOverrideConfigurer" class="org.springframework.beans.factory.config.PropertyOverrideConfigurer">
<property name="locations">
<list>
<value>classpath:application2.properties</value>
</list>
</property>
</bean>
<bean id="student" class="com.kungyu.custom.element.Student">
<property name="name" value="${student.name}"/>
</bean>
</beans>
其余保持不变,执行结果如下:
student-PropertyOverrideConfigurer
可以看到和我们原来的猜想是一致的。
2. 源码解读
2.1 PropertyOverrideConfigurer#processProperties
@Override
protected void processProperties(ConfigurableListableBeanFactory beanFactory, Properties props)
throws BeansException {
for (Enumeration<?> names = props.propertyNames(); names.hasMoreElements();) {
String key = (String) names.nextElement();
try {
processKey(beanFactory, key, props.getProperty(key));
}
catch (BeansException ex) {
String msg = "Could not process key '" + key + "' in PropertyOverrideConfigurer";
if (!this.ignoreInvalidKeys) {
throw new BeanInitializationException(msg, ex);
}
if (logger.isDebugEnabled()) {
logger.debug(msg, ex);
}
}
}
}
遍历属性,依次调用processKey
执行属性覆盖逻辑
2.2 PropertyOverrideConfigurer#processKey
protected void processKey(ConfigurableListableBeanFactory factory, String key, String value)
throws BeansException {
// 根据.进行切割,分别获取beanName和beanProperty,比如:配置项student.name切割成student和name
int separatorIndex = key.indexOf(this.beanNameSeparator);
if (separatorIndex == -1) {
throw new BeanInitializationException("Invalid key '" + key +
"': expected 'beanName" + this.beanNameSeparator + "property'");
}
String beanName = key.substring(0, separatorIndex);
String beanProperty = key.substring(separatorIndex + 1);
this.beanNames.add(beanName);
applyPropertyValue(factory, beanName, beanProperty, value);
if (logger.isDebugEnabled()) {
logger.debug("Property '" + key + "' set to value [" + value + "]");
}
}
将配置项根据.这个分割符进行切割,获得beanName
和beanProperty
,调用applyPropertyValue
执行覆盖处理逻辑
2.3 PropertyOverrideConfigurer#applyPropertyValue
protected void applyPropertyValue(
ConfigurableListableBeanFactory factory, String beanName, String property, String value) {
BeanDefinition bd = factory.getBeanDefinition(beanName);
BeanDefinition bdToUse = bd;
while (bd != null) {
bdToUse = bd;
bd = bd.getOriginatingBeanDefinition();
}
PropertyValue pv = new PropertyValue(property, value);
pv.setOptional(this.ignoreInvalidKeys);
// 进行覆盖
bdToUse.getPropertyValues().addPropertyValue(pv);
}
构造一个PropertyValue
对象pv
,调用addPropertyValue
将pv
加入到propertyValues
当中
2.4 MutablePropertyValues#addPropertyValue
public MutablePropertyValues addPropertyValue(PropertyValue pv) {
for (int i = 0; i < this.propertyValueList.size(); i++) {
PropertyValue currentPv = this.propertyValueList.get(i);
if (currentPv.getName().equals(pv.getName())) {
// 如果有需要,进行合并
pv = mergeIfRequired(pv, currentPv);
// 覆盖
setPropertyValueAt(pv, i);
return this;
}
}
// 查找不到,直接新增
this.propertyValueList.add(pv);
return this;
}
如果查找得到对应的属性,进行合并(如果有需要)和覆盖,否则,直接新增该属性
3. 总结
PropertyOverrideConfigurer
很好理解,源码也很简单,功能就是覆盖bean已经定义好的属性值。