springboot配置dataSource无法从application.properties文件获取配置项值

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

最近在弄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就不会被实例化。解决问题。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值