事情是这样的,近期公司业务原因,要在一个项目中加入多数据源配置,加就加吧,也不麻烦,可是加完多数据源之后业务没出问题,却发现刷新nacos时,原本可以正常refresh值的,现在出现异常报错,只能通过重启保证重新加载更新过的nacos的值了。那怎么办,自己作的死要自己解决。
如果没耐性的,可以直接拉到底部看问题解决吧。
异常报错
2021-04-13 17:02:52.962 [tid: ][skId: ] [com.alibaba.nacos.client.Worker.longPolling.fixed-ip_10348-ip_10348-ip_10348] ERROR c.a.n.c.config.impl.CacheData:220 - [fixed-ip_10348-ip_10348-ip_10348] [notify-error] dataId=arthur-payment-test.yml, group=DEFAULT_GROUP, md5=01bf9cdbf9d77212704149639b020844, listener=com.alibaba.cloud.nacos.refresh.NacosContextRefresher$1@1ac9633c tx={}
org.springframework.boot.context.properties.bind.BindException: Failed to bind properties under 'spring.datasource' to javax.sql.DataSource
at org.springframework.boot.context.properties.bind.Binder.handleBindError(Binder.java:242)
at org.springframework.boot.context.properties.bind.Binder.bind(Binder.java:218)
at org.springframework.boot.context.properties.bind.Binder.bind(Binder.java:202)
at org.springframework.boot.context.properties.bind.Binder.bind(Binder.java:185)
at org.springframework.boot.context.properties.ConfigurationPropertiesBinder.bind(ConfigurationPropertiesBinder.java:78)
at org.springframework.boot.context.properties.ConfigurationPropertiesBindingPostProcessor.bind(ConfigurationPropertiesBindingPostProcessor.java:101)
at org.springframework.boot.context.properties.ConfigurationPropertiesBindingPostProcessor.postProcessBeforeInitialization(ConfigurationPropertiesBindingPostProcessor.java:89)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.applyBeanPostProcessorsBeforeInitialization(AbstractAutowireCapableBeanFactory.java:414)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1763)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:405)
at org.springframework.cloud.context.properties.ConfigurationPropertiesRebinder.rebind(ConfigurationPropertiesRebinder.java:102)
at org.springframework.cloud.context.properties.ConfigurationPropertiesRebinder.rebind(ConfigurationPropertiesRebinder.java:83)
at org.springframework.cloud.context.properties.ConfigurationPropertiesRebinder.onApplicationEvent(ConfigurationPropertiesRebinder.java:128)
at org.springframework.cloud.context.properties.ConfigurationPropertiesRebinder.onApplicationEvent(ConfigurationPropertiesRebinder.java:50)
at org.springframework.context.event.SimpleApplicationEventMulticaster.doInvokeListener(SimpleApplicationEventMulticaster.java:172)
at org.springframework.context.event.SimpleApplicationEventMulticaster.invokeListener(SimpleApplicationEventMulticaster.java:165)
at org.springframework.context.event.SimpleApplicationEventMulticaster.multicastEvent(SimpleApplicationEventMulticaster.java:139)
at org.springframework.context.support.AbstractApplicationContext.publishEvent(AbstractApplicationContext.java:402)
at org.springframework.context.support.AbstractApplicationContext.publishEvent(AbstractApplicationContext.java:359)
at org.springframework.cloud.context.refresh.ContextRefresher.refreshEnvironment(ContextRefresher.java:96)
at org.springframework.cloud.context.refresh.ContextRefresher.refresh(ContextRefresher.java:85)
at org.springframework.cloud.endpoint.event.RefreshEventListener.handle(RefreshEventListener.java:72)
at org.springframework.cloud.endpoint.event.RefreshEventListener.onApplicationEvent(RefreshEventListener.java:61)
at org.springframework.context.event.SimpleApplicationEventMulticaster.doInvokeListener(SimpleApplicationEventMulticaster.java:172)
at org.springframework.context.event.SimpleApplicationEventMulticaster.invokeListener(SimpleApplicationEventMulticaster.java:165)
at org.springframework.context.event.SimpleApplicationEventMulticaster.multicastEvent(SimpleApplicationEventMulticaster.java:139)
at org.springframework.context.support.AbstractApplicationContext.publishEvent(AbstractApplicationContext.java:402)
at org.springframework.context.support.AbstractApplicationContext.publishEvent(AbstractApplicationContext.java:359)
at com.alibaba.cloud.nacos.refresh.NacosContextRefresher$1.innerReceive(NacosContextRefresher.java:133)
at com.alibaba.nacos.api.config.listener.AbstractSharedListener.receiveConfigInfo(AbstractSharedListener.java:38)
at com.alibaba.nacos.client.config.impl.CacheData$1.run(CacheData.java:203)
at com.alibaba.nacos.client.config.impl.CacheData.safeNotifyListener(CacheData.java:233)
at com.alibaba.nacos.client.config.impl.CacheData.checkListenerMd5(CacheData.java:174)
at com.alibaba.nacos.client.config.impl.ClientWorker$LongPollingRunnable.run(ClientWorker.java:552)
at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511)
at java.util.concurrent.FutureTask.run(FutureTask.java:266)
at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.access$201(ScheduledThreadPoolExecutor.java:180)
at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:293)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
at java.lang.Thread.run(Thread.java:748)
Caused by: java.lang.IllegalStateException: Unable to set value for property driver-class-name
at org.springframework.boot.context.properties.bind.JavaBeanBinder$BeanProperty.setValue(JavaBeanBinder.java:339)
at org.springframework.boot.context.properties.bind.JavaBeanBinder.bind(JavaBeanBinder.java:86)
at org.springframework.boot.context.properties.bind.JavaBeanBinder.bind(JavaBeanBinder.java:70)
at org.springframework.boot.context.properties.bind.JavaBeanBinder.bind(JavaBeanBinder.java:54)
at org.springframework.boot.context.properties.bind.Binder.lambda$null$4(Binder.java:329)
at java.util.stream.ReferencePipeline$3$1.accept(ReferencePipeline.java:193)
at java.util.ArrayList$ArrayListSpliterator.tryAdvance(ArrayList.java:1359)
at java.util.stream.ReferencePipeline.forEachWithCancel(ReferencePipeline.java:126)
at java.util.stream.AbstractPipeline.copyIntoWithCancel(AbstractPipeline.java:499)
at java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:486)
at java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:472)
at java.util.stream.FindOps$FindOp.evaluateSequential(FindOps.java:152)
at java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234)
at java.util.stream.ReferencePipeline.findFirst(ReferencePipeline.java:464)
at org.springframework.boot.context.properties.bind.Binder.lambda$bindBean$5(Binder.java:330)
at org.springframework.boot.context.properties.bind.Binder$Context.withIncreasedDepth(Binder.java:429)
at org.springframework.boot.context.properties.bind.Binder$Context.withBean(Binder.java:415)
at org.springframework.boot.context.properties.bind.Binder$Context.access$400(Binder.java:372)
at org.springframework.boot.context.properties.bind.Binder.bindBean(Binder.java:328)
at org.springframework.boot.context.properties.bind.Binder.bindObject(Binder.java:269)
at org.springframework.boot.context.properties.bind.Binder.bind(Binder.java:214)
... 39 common frames omitted
Caused by: java.lang.reflect.InvocationTargetException: null
at sun.reflect.GeneratedMethodAccessor650.invoke(Unknown Source)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at org.springframework.boot.context.properties.bind.JavaBeanBinder$BeanProperty.setValue(JavaBeanBinder.java:336)
... 59 common frames omitted
Caused by: java.lang.IllegalStateException: The configuration of the pool is sealed once started. Use HikariConfigMXBean for runtime changes.
at com.zaxxer.hikari.HikariConfig.checkIfSealed(HikariConfig.java:1014)
at com.zaxxer.hikari.HikariConfig.setDriverClassName(HikariConfig.java:472)
... 63 common frames omitted
数据源DataSource配置
@Configuration
@MapperScan(value = DBConfiguration.PRIMARY_MAPPER_BASE_PACKAGE, sqlSessionTemplateRef = "sessionTemplate")
public class DBConfiguration {
public static final String PRIMARY_MAPPER_BASE_PACKAGE = "cn.arthur.payment.biz.dal.mapper";
public static final String TRANSACTION_MANAGER = "transactionManager";
@Bean(name="dataSource")
@ConfigurationProperties(prefix="spring.datasource")
public DataSource dataSource() {
return new HikariDataSource();
}
@Bean(name = "sqlSessionFactory")
public SqlSessionFactory sqlSessionFactory(@Qualifier("dataSource") DataSource dataSource)
throws Exception {
SqlSessionFactoryBean sqlSessionFactory = new SqlSessionFactoryBean();
sqlSessionFactory.setDataSource(dataSource);
sqlSessionFactory.setMapperLocations(new PathMatchingResourcePatternResolver().
getResources("classpath:mapper/*.xml"));
return sqlSessionFactory.getObject();
}
@Bean("sessionTemplate")
public SqlSessionTemplate sqlSessionTemplate(
@Qualifier("sqlSessionFactory") SqlSessionFactory sqlSessionFactory) {
return new SqlSessionTemplate(sqlSessionFactory);
}
@Bean(TRANSACTION_MANAGER)
public DataSourceTransactionManager transactionManager(@Qualifier("dataSource") DataSource dataSource){
return new DataSourceTransactionManager(dataSource);
}
@Bean("transactionTemplate")
public TransactionTemplate transactionTemplate(@Qualifier("transactionManager") PlatformTransactionManager transactionManager) {
return new TransactionTemplate(transactionManager);
}
}
问题分析
报错日志可太长了,我们看看最底部的报错日志在说什么.
Caused by: java.lang.IllegalStateException: Unable to set value for property driver-class-name
省略一部分代码
Caused by: java.lang.IllegalStateException: The configuration of the pool is sealed once started. Use HikariConfigMXBean for runtime changes.
at com.zaxxer.hikari.HikariConfig.checkIfSealed(HikariConfig.java:1014)
at com.zaxxer.hikari.HikariConfig.setDriverClassName(HikariConfig.java:472)
... 63 common frames omitted
翻译翻译就是这个连接池的配置,一旦被创建过了就不能被修改了,导致在设置driver-class-name
属性的时候,报错了。那到底是什么情况,我们跟进源码看看。
首先看HikariConfig#checkIfSealed 和 HikariConfig#setDriverClassName方法
private void checkIfSealed()
{
if (sealed) throw new IllegalStateException("The configuration of the pool is sealed once started. Use HikariConfigMXBean for runtime changes.");
}
public void setDriverClassName(String driverClassName)
{
checkIfSealed();
省略一部分代码
}
那么我们可以知道,其实报错就是因为,sealed
这个属性为true,导致了报错,接下去该找找,sealed
这个属性什么时候会被更改。
代码一:设置为false, 这个方法后续会讲到,注意下
public void copyStateTo(HikariConfig other)
{
for (Field field : HikariConfig.class.getDeclaredFields()) {
if (!Modifier.isFinal(field.getModifiers())) {
field.setAccessible(true);
try {
field.set(other, field.get(this));
}
catch (Exception e) {
throw new RuntimeException("Failed to copy HikariConfig state: " + e.getMessage(), e);
}
}
}
other.sealed = false;
}
代码二:设置为true
void seal()
{
this.sealed = true;
}
在HikariConfig类中,sealed
属性在三个地方被使用,两处是赋值,一处是调用判断。当调用了HikariConfig#seal方法时,会导致后续 setDriverClassName
这个方法会报错。那么我们看看到底哪些地方会调用HikariConfig#seal方法。
还是查看源码
代码一: HikariDataSource的无参初始化
public HikariDataSource()
{
super();
fastPathPool = null;
}
代码二: HikariDataSource的有参初始化
public HikariDataSource(HikariConfig configuration)
{
configuration.validate();
configuration.copyStateTo(this);
LOGGER.info("{} - Starting...", configuration.getPoolName());
pool = fastPathPool = new HikariPool(this);
LOGGER.info("{} - Start completed.", configuration.getPoolName());
this.seal();
}
代码三:创建连接池
@Override
public Connection getConnection() throws SQLException
{
省略一部分代码
if (result == null) {
synchronized (this) {
result = pool;
if (result == null) {
validate();
LOGGER.info("{} - Starting...", getPoolName());
try {
pool = result = new HikariPool(this);
this.seal();
}
catch (PoolInitializationException pie) {
if (pie.getCause() instanceof SQLException) {
throw (SQLException) pie.getCause();
}
else {
throw pie;
}
}
LOGGER.info("{} - Start completed.", getPoolName());
}
}
}
return result.getConnection();
}
我们可以看到,HikariConfig的子类HikariDataSource,在初始化和创建连接池时,都会调用HikariConfig#seal方法,HikariDataSource的初始化可以选择无参初始化方式,但是创建连接池是不可避免的,这问题看起来似乎无解,但未接入多数据源时,spring如何解决这个问题,之前为何不会报错。
仔细看代码,HikariDataSource类中调用seal方法,都是this.seal,而非HikariConfig的实例调用seal方法,换成人话说,就是这里只会影响HikariDataSource类实例的sealed
属性啊。我们只需要创建一个HikariConfig的实例,由spring注入,使用HikariDataSource的有参构造方法,那么该HikariConfig 实例的sealed
一直是没被设置过的默认初始值false,那么HikariConfig#setDriverClassName方法调用就不会出错,问题不就解决了。
初始化时HikariConfig类实例内部的属性值,为什么会影响到HikariDataSource类实例的属性值呢。我们可以看下HikariDataSource有参初始化时,调用的configuration.copyStateTo(this);
这行代码,这个其实调用的是父类HikariConfig的方法,具体的代码上面介绍HikariConfig的时候也摘录了。其实是将HikariConfig实例内的所有field的值,都赋值到HikariDataSource实例上。
代码的介绍到此也结束了,总结下怎么解决nacos更新报错的问题吧。
报错原因如下
- 我使用了HikariDataSource的无参构造函数,创建了HikariDataSource的实例bean
- 随着程序的运行,创建并获取连接池后,HikariDataSourced实例的
sealed
属性被设置为true - 更改配置文件后,HikariConfig#setDriverClassName方法被调用,此时其实是HikariDataSourced实例作为调用方,由于步骤2的存在,会使得调用方法报错,nacos更新值报错
解决方法:
- 创建HikariConfig实例,交由spring管理,再使用HikariDataSourced有参构造方法,创建HikariDataSourced的实例
- 初始化完毕后HikariDataSourced的
sealed
属性已被赋值成true,意味着他不允许再调用setDriverClassName
方法。而此时的HikariConfig实例的sealed
属性值仍为初始值false,意味着配置文件更改后,仍能通过nacos影响HikariConfig实例的值的更改 - 随着配置文件更改,HikariConfig实例的值被更改后,由于HikariDataSourced实例已存在,不会再次进行初始化,所以HikariDataSourced实例的属性值不会有影响,但是也不会报错了。
问题解决
改变DBConfiguration 配置方式即可,更新后的代码如下
@Configuration
@MapperScan(value = DBConfiguration.PRIMARY_MAPPER_BASE_PACKAGE, sqlSessionTemplateRef = "sessionTemplate")
public class DBConfiguration {
public static final String PRIMARY_MAPPER_BASE_PACKAGE = "cn.arthur.payment.biz.dal.mapper";
public static final String TRANSACTION_MANAGER = "transactionManager";
@Primary
@Bean(name = "dataSourceConfig")
@ConfigurationProperties(prefix = "spring.datasource")
public HikariConfig hikariConfig() {
return new HikariConfig();
}
@Primary
@Bean(name = "dataSource")
public DataSource dataSource(HikariConfig hikariConfig) {
return new HikariDataSource(hikariConfig);
}
@Primary
@Bean(name = "sqlSessionFactory")
public SqlSessionFactory sqlSessionFactory(@Qualifier("dataSource") DataSource dataSource)
throws Exception {
SqlSessionFactoryBean sqlSessionFactory = new SqlSessionFactoryBean();
sqlSessionFactory.setDataSource(dataSource);
sqlSessionFactory.setMapperLocations(new PathMatchingResourcePatternResolver().
getResources("classpath:mapper/*.xml"));
return sqlSessionFactory.getObject();
}
@Primary
@Bean("sessionTemplate")
public SqlSessionTemplate sqlSessionTemplate(
@Qualifier("sqlSessionFactory") SqlSessionFactory sqlSessionFactory) {
return new SqlSessionTemplate(sqlSessionFactory);
}
@Primary
@Bean(TRANSACTION_MANAGER)
public DataSourceTransactionManager transactionManager(@Qualifier("dataSource") DataSource dataSource) {
return new DataSourceTransactionManager(dataSource);
}
@Primary
@Bean("transactionTemplate")
public TransactionTemplate transactionTemplate(
@Qualifier("transactionManager") PlatformTransactionManager transactionManager) {
return new TransactionTemplate(transactionManager);
}
}