最近在弄springboot整合mybatis,遇到一个非常诡异的问题:
1.新增一个BeanConfig用来配置dataSource等相关信息:
@Configuration
public class BeanConfig {
/**
*
* @return
*/
@Bean
@ConfigurationProperties("jdbc.love")
public DataSource dataSource() {
DataSource bean = DataSourceBuilder.create().build();
return bean;
}
2.在application.properties中添加dataSource相关配置:
db.url=127.0.0.1:3306
db.username=love
db.password=love
jdbc.love.driverClassName=com.mysql.jdbc.Driver
jdbc.love.url=jdbc:mysql://${db.url}/love?autoReconnect=true&useUnicode=true&characterEncoding=utf-8&useSSL=false
jdbc.love.username=${db.username}
jdbc.love.password=${db.password}
jdbc.love.initialSize=0
jdbc.love.maxActive=20
jdbc.love.maxIdle=10
jdbc.love.minIdle=1
jdbc.love.maxWait=15000
jdbc.love.validationInterval = 15000
jdbc.love.testOnBorrow=true
jdbc.love.validationQuery=SELECT 1
3.启动项目执行SQL语句时,报错了:

这就很诡异了,在dataSource bean上添加了@ConfigurationProperties("jdbc.love")注解,正常来讲,springboot会在dataSource bean初始化时读取application.properties配置文件讲属性值一一注入dataSource属性,所以driverClassName不可能为空,url也不可能为空。
在网上搜了一下,有的是说application.properties文件存在重复,有的是说被注入的类必须有getter、setter属性名要一致。显然这两种情况和我目前弄的都不一致:application.properties文件没有重复;dataSource也不存在setter、getter的问题。
那么就很诡异了,都是很正常,很简单的配置,为什么就不行了呢?
经过两天的折腾,终于发现了问题所在:
1.首先要打开maven的debug模式,不然根本看不到出问题的地方在哪

2.仔细看springboot的qi启动日志,发现了一行WARN:
2020-03-01 18:38:50.185 WARN 7628 --- [main] o.s.c.a.ConfigurationClassPostProcessor : Cannot enhance @Configuration bean definition 'beanConfig' since its singleton instance has been created too early. The typical cause is a non-static @Bean method with a BeanDefinitionRegistryPostProcessor return type: Consider declaring such methods as 'static'.
什么意思呢:具体到本例就是不能去增强BeanConfig类,因为BeanConfig类在增强步骤之前就已经被实例化了,并且它是单例,生命周期里只能存在一个,已经实例化后就无法再被增强了。
这里增强又是什么意思呢:增强就是解析@ConfigurationProperties("jdbc.love")注解,做把application.properties配置文件中的属性值注入到dataSource里这些事。
从这里能知道问题出现的原因了,是因为没有做把application.properties配置文件中的属性值注入到dataSource里这一步,导致在执行SQL的时候,连接数据库driverClassName、url都为空。
那么为什么会出现这个问题呢?
3.查看日志里抛出WARN的类ConfigurationClassPostProcessor,这个类,也是专门处理加上了@Configuration注解的类
首先找到抛出WARN的代码:
public void enhanceConfigurationClasses(ConfigurableListableBeanFactory beanFactory) {
Map<String, AbstractBeanDefinition> configBeanDefs = new LinkedHashMap<String, AbstractBeanDefinition>();
for (String beanName : beanFactory.getBeanDefinitionNames()) {
BeanDefinition beanDef = beanFactory.getBeanDefinition(beanName);
if (ConfigurationClassUtils.isFullConfigurationClass(beanDef)) {
if (!(beanDef instanceof AbstractBeanDefinition)) {
throw new BeanDefinitionStoreException("Cannot enhance @Configuration bean definition '" +
beanName + "' since it is not stored in an AbstractBeanDefinition subclass");
}
else if (logger.isWarnEnabled() && beanFactory.containsSingleton(beanName)) {
logger.warn("Cannot enhance @Configuration bean definition '" + beanName +
"' since its singleton instance has been created too early. The typical cause " +
"is a non-static @Bean method with a BeanDefinitionRegistryPostProcessor " +
"return type: Consider declaring such methods as 'static'.");
}
configBeanDefs.put(beanName, (AbstractBeanDefinition) beanDef);
}
}
代码里可以看出,是在做增强步骤的时候,判断需要增强的类是否是单例且已实例化,如果是,就抛出了WARN。
4.找到该方法被调用的源头


会发现调用的源头是ApplicationContext里的refresh()方法。
5.查看invokeBeanFactoryPostProcessors()做了什么
/**
* Instantiate and invoke all registered BeanFactoryPostProcessor beans,
* respecting explicit order if given.
* <p>Must be called before singleton instantiation.
*/
protected void invokeBeanFactoryPostProcessors(ConfigurableListableBeanFactory beanFactory) {
PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(beanFactory, getBeanFactoryPostProcessors());
// Detect a LoadTimeWeaver and prepare for weaving, if found in the meantime
// (e.g. through an @Bean method registered by ConfigurationClassPostProcessor)
if (beanFactory.getTempClassLoader() == null && beanFactory.containsBean(LOAD_TIME_WEAVER_BEAN_NAME)) {
beanFactory.addBeanPostProcessor(new LoadTimeWeaverAwareProcessor(beanFactory));
beanFactory.setTempClassLoader(new ContextTypeMatchClassLoader(beanFactory.getBeanClassLoader()));
}
}
重点关注invokeBeanFactoryPostProcessors()方法:

这几行代码的意思是在实例化常规Bean之前,首先以byType的方式实例化实现了BeanDefinitionRegistryPostProcessor接口的类。
ps:这里将会是找到问题根本原因的关键
继续往下看

invokeBeanFactoryPostProcessors(registryProcessors, beanFactory)方法就是前面提到了最终会调用增强BeanConfig的方法。从这里也可以看出,正常流程,BeanConfig类会在这里被增强,然后再进行实例化。
看到这里应该有点明白问题的所在了:正常流程都是先增强再实例化,只有一种情况例外,就是实现了BeanDefinitionRegistryPostProcessor接口的类会被优先初始化。
可BeanConfig类没有实现任何接口,按理说不会被优先初始化;那就只剩最后一种情况,BeanConfig中配置@Bean注解方法生成的Bean中,有实现了BeanDefinitionRegistryPostProcessor接口的类。
按照这个思路排查,果然发现:
/**
*
* @return
*/
@Bean
public MapperScannerConfigurer mapperScannerConfigurer() {
MapperScannerConfigurer bean = new MapperScannerConfigurer();
bean.setBasePackage("com.springboot.mybatis.persistence");
bean.setSqlSessionFactoryBeanName("sqlSessionFactory");
return bean;
}

配置的mybatis MapperScannerConfigurer类实现了BeanDefinitionRegistryPostProcessor接口,该Bean会被优先实例化,而它的实例化会首先实例化BeanConfig,这就导致了问题所在。
清楚了这个问题的根源之后,解决起来就很简单了,只要保证BeanConfig类不被其他情况导致优先实例化就可以了。
在MapperScannerConfigurer bean方法前加上static,这样,当MapperScannerConfigurer类实例化时,BeanConfig就不会被实例化。解决问题。



在SpringBoot整合Mybatis过程中,遇到dataSource配置无法从application.properties读取的问题。错误源于BeanConfig类在增强之前已被实例化,导致@ConfigurationProperties注解未生效,数据库连接信息为空。通过分析启动日志,发现MapperScannerConfigurer类(实现了BeanDefinitionRegistryPostProcessor接口)的优先实例化引发了问题。解决方案是在MapperScannerConfigurer bean方法前添加static关键字,避免提前实例化BeanConfig。
1289

被折叠的 条评论
为什么被折叠?



