问题描述
在使用spring创建bean时,bean的值由properties文件中取出,但是取出的值却与文件中的值不一致,spring配置文件中的相关配置如下
<context:property-placeholder location="classpath:db.properties"/>
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="url" value="${url}"/>
<property name="username" value="${username}"/>
<property name="password" value="${password}"/>
<property name="driverClassName" value="${driver}"/>
</bean>
db.properties
driver=com.mysql.cj.jdbc.Driver
url=jdbc:mysql://localhost:3306/mybatis?serverTimezone=GMT%2B8&useSSL=false
username=root
password=密码
maxActive=100
maxIdle=50
但是创建出来的dataSource的属性却有点问题
初步分析
我写的明明是username=root
但是这里确实username=ko
,仔细想了一下,这个有点ko熟悉,猛然发现这是我电脑上的用户名
spring bean的创建
spring中bean由beanFactory来创建,beanFactory中保存了bean创建所需要的信息(BeanDefinition),上面的问题大概率就出现在这里。之前看过一点spring的源码,所以直接定位了到相关的函数。 AbstractApplicationContext中有个refresh()方法,源码如下
// Allows post-processing of the bean factory in context subclasses.
postProcessBeanFactory(beanFactory);
// Invoke factory processors registered as beans in the context.
//调用注册在上下文中的工厂后置处理器
invokeBeanFactoryPostProcessors(beanFactory);
// Register bean processors that intercept bean creation.
registerBeanPostProcessors(beanFactory);
// Initialize message source for this context.
initMessageSource();
// Initialize event multicaster for this context.
initApplicationEventMulticaster();
// Initialize other special beans in specific context subclasses.
onRefresh();
// Check for listener beans and register them.
registerListeners();
// Instantiate all remaining (non-lazy-init) singletons.
finishBeanFactoryInitialization(beanFactory);
// Last step: publish corresponding event.
finishRefresh();
关于bean工厂后置处理器
spring中很多特性都由后置处理器来实现,猜测填充配置文件中beanDefinition的属性应该是在invokeBeanFactoryPostProcessors(beanFactory)
中实现,但是并不清楚是哪个后置处理器,只能一步一步跟进去看了。跟进去之后定位到一个关键函数DefaultListableBeanFactory中的doGetBeanNamesForType()
方法
for (String beanName : this.beanDefinitionNames) {
// Only consider bean as eligible if the bean name
// is not defined as alias for some other bean.
if (!isAlias(beanName)) {
try {
//在这个地方下个条件断点(beanName.equal("dataSource"))
RootBeanDefinition mbd = getMergedLocalBeanDefinition(beanName);
// Only check bean definition if it is complete.
if (!mbd.isAbstract() && (allowEagerInit ||
(mbd.hasBeanClass() || !mbd.isLazyInit() || isAllowEagerClassLoading()) &&
断点停下五次到六次时this.mergedBeanDefinitions中的关于dataSource的beanDefinition的定义就发生了变化,
由原来的占位符变为了实际的值 由堆栈信息得到两次调用这个函数的位置都在PostProcessorRegistrationDelegate中的invokeBeanFactoryPostProcessors()方法String[] postProcessorNames =
beanFactory.getBeanNamesForType(BeanFactoryPostProcessor.class, true, false);//第五次调用
/*省略部分代码*/
// First, invoke the BeanFactoryPostProcessors that implement PriorityOrdered.
sortPostProcessors(priorityOrderedPostProcessors, beanFactory);
//这里就是关键代码
invokeBeanFactoryPostProcessors(priorityOrderedPostProcessors, beanFactory);
/*省略部分代码*/
invokeBeanFactoryPostProcessors(nonOrderedPostProcessors, beanFactory);//第六次调用
接着就是一步一步看到底是哪变化了,经过分析,就是invokeBeanFactoryPostProcessors(priorityOrderedPostProcessors, beanFactory);
这个方法,这个后置处理器的名字为PropertySourcesPlaceholderConfigurer,看来确实就是这个了
PropertySourcesPlaceholderConfigurer
在PropertyPlaceholderHelper这个类的parseStringValue()方法中
// Recursive invocation, parsing placeholders contained in the placeholder key.
//解析占位符
placeholder = parseStringValue(placeholder, placeholderResolver, visitedPlaceholders);
// Now obtain the value for the fully resolved key...
//解析占位符的值
String propVal = placeholderResolver.resolvePlaceholder(placeholder);
就是从propertySources中解析出相应的值
propertySourceList包含两个propertySourceList,一个是environmentProperties,另一个是localProperties,username这个属性在两个里都有,我希望的值存储在后者中,但是spring会按照顺序先从environmentProperties中取,如果取到了,直接结束,否则再从localProperties中取。environmentProperties->systemProperties->systemEnviroment
这里面确实有个USERNAME的属性,貌似spring取这个值的时候不分大小写结语
命名冲突引发的“血案”,总算理解为什么看到许多的配置文件中XX.username的写法了