【Java】Nacos自动配置不生效问题源码跟踪

文章讲述了在使用Nacos配置刷新时遇到的SpringBoot与Druid连接器错误,涉及SpringCloud自动刷新配置的原理和解决方法,涉及到配置文件管理和避免特定类的自动刷新。
摘要由CSDN通过智能技术生成

写在前面,有感而发

上个月,XS 开源库被曝运营者恶意植入后门,虽然与 Java 关系不是很大,但是其所象征的 “开源=免费+安全” 已经变成 “开源=白嫖” 了

事件的来龙去脉可以在网上查看,总的来说就是 XS 库的作者无力维护,来了个人积极提 PR 同时“逼宫”作者,最终将这个人加入了社区运营者,最终让他有了机会
说到底,开源就像是用爱发电,没有收入和赞助,大部分开源作者都是难以为继,如 Python 的 request 库和 LayUI

就这样吧


一、现象

项目开启了自动刷新 Nacos 配置,但是无法刷新配置,并且每隔一段时间报错:

org.springframework.boot.context.properties.bind.BindException: Failed to bind properties under 'spring.datasource.druid.xxxxxxx' to javax.sql.DataSource
  at org.springframework.boot.context.properties.bind.Binder.handleBindError(Binder.java:363)
  at org.springframework.boot.context.properties.bind.Binder.bind(Binder.java:323)
  at org.springframework.boot.context.properties.bind.Binder.bind(Binder.java:308)
  at org.springframework.boot.context.properties.bind.Binder.bind(Binder.java:238)
  at org.springframework.boot.context.properties.bind.Binder.bind(Binder.java:225)
  at org.springframework.boot.context.properties.ConfigurationPropertiesBinder.bind(ConfigurationPropertiesBinder.java:90)
  at org.springframework.boot.context.properties.ConfigurationPropertiesBindingPostProcessor.bind(ConfigurationPropertiesBindingPostProcessor.java:89)
  at org.springframework.boot.context.properties.ConfigurationPropertiesBindingPostProcessor.postProcessBeforeInitialization(ConfigurationPropertiesBindingPostProcessor.java:78)
  at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.applyBeanPostProcessorsBeforeInitialization(AbstractAutowireCapableBeanFactory.java:415)
  at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1786)
  at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:406)
  at org.springframework.cloud.context.properties.ConfigurationPropertiesRebinder.rebind(ConfigurationPropertiesRebinder.java:108)
  at org.springframework.cloud.context.properties.ConfigurationPropertiesRebinder.rebind(ConfigurationPropertiesRebinder.java:84)
  at org.springframework.cloud.context.properties.ConfigurationPropertiesRebinder.onApplicationEvent(ConfigurationPropertiesRebinder.java:142)
  at org.springframework.cloud.context.properties.ConfigurationPropertiesRebinder.onApplicationEvent(ConfigurationPropertiesRebinder.java:51)
  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:404)
  at org.springframework.context.support.AbstractApplicationContext.publishEvent(AbstractApplicationContext.java:361)
  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:404)
  at org.springframework.context.support.AbstractApplicationContext.publishEvent(AbstractApplicationContext.java:361)
  at com.alibaba.cloud.nacos.refresh.NacosContextRefresher$1.innerReceive(NacosContextRefresher.java:133)
  at com.alibaba.nacos.api.config.listener.AbstractSharedListener.receiveConfigInfo(AbstractSharedListener.java:40)
  at com.alibaba.nacos.client.config.impl.CacheData$1.run(CacheData.java:210)
  at com.alibaba.nacos.client.config.impl.CacheData.safeNotifyListener(CacheData.java:241)
  at com.alibaba.nacos.client.config.impl.CacheData.checkListenerMd5(CacheData.java:181)
  at com.alibaba.nacos.client.config.impl.ClientWorker$LongPollingRunnable.run(ClientWorker.java:629)
  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 username
  at org.springframework.boot.context.properties.bind.JavaBeanBinder$BeanProperty.setValue(JavaBeanBinder.java:360)
  at org.springframework.boot.context.properties.bind.JavaBeanBinder.bind(JavaBeanBinder.java:101)
  at org.springframework.boot.context.properties.bind.JavaBeanBinder.bind(JavaBeanBinder.java:83)
  at org.springframework.boot.context.properties.bind.JavaBeanBinder.bind(JavaBeanBinder.java:59)
  at org.springframework.boot.context.properties.bind.Binder.lambda$bindDataObject$5(Binder.java:451)
  at org.springframework.boot.context.properties.bind.Binder$Context.withIncreasedDepth(Binder.java:571)
  at org.springframework.boot.context.properties.bind.Binder$Context.withDataObject(Binder.java:557)
  at org.springframework.boot.context.properties.bind.Binder$Context.access$300(Binder.java:512)
  at org.springframework.boot.context.properties.bind.Binder.bindDataObject(Binder.java:449)
  at org.springframework.boot.context.properties.bind.Binder.bindObject(Binder.java:390)
  at org.springframework.boot.context.properties.bind.Binder.bind(Binder.java:319)
  ... 40 common frames omitted
Caused by: java.lang.reflect.InvocationTargetException: null
  at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
  at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
  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:357)
  ... 50 common frames omitted
Caused by: java.lang.UnsupportedOperationException: null
  at com.alibaba.druid.pool.DruidAbstractDataSource.setUsername(DruidAbstractDataSource.java:1135)
  ... 55 common frames omitted

直觉告诉我,就是这个报错导致的自动刷新配置失效

仔细一看,什么 bind、refresh,应该就是在刷新数据的时候发生报错了
也就是说,Nacos 的自动刷新配置生效了,但是报错导致失败

二、跟源码

从这个方法入手:

at com.alibaba.druid.pool.DruidAbstractDataSource.setUsername(DruidAbstractDataSource.java:1135)

在这里插入图片描述

报错在这里,看起来是在绑定 username 时发生了报错,首先会判断当前的 username 和即将注入的字符串是否相同,发现不相同,往下走又发现已经完成了初始化,最终导致报错
这里的原因在于配置项中数据库的用户名和密码都是加密的,在项目启动注入 Bean 的时候会进行解密操作,因此配置项和服务的用户名自然不一样
由此问题从“为什么报错”变成了“如何不刷新指定的配置项”

接下来往这走:

at com.alibaba.cloud.nacos.refresh.NacosContextRefresher$1.innerReceive(NacosContextRefresher.java:133)

在这里插入图片描述

因为它的类名 NacosContextRefresher,引起了我的注意

看起来像是注册了一个 Spring 监听事件,其中的 RefreshEvent 就是这个刷新事件,并且报错就是从这里出来的
接下来看这里:

at org.springframework.cloud.context.refresh.ContextRefresher.refreshEnvironment(ContextRefresher.java:96)

虽然现在说的简单,但是跟源码就是一个一个方法点进去看,提取有用的信息,因此写代码时方法名的选取真的很重要——除非是不想让人轻易发现方法的作用,防御性编程

在这里插入图片描述

在这个方法里打断点,可以发现 before 里存储的就是所有配置项,keys 里存储的就是发生变化的配置项(只有第一次发生修改的时候 keys 里有值,更新失败之后的轮询中 keys 中不会再有值)

同时,这里又触发了新的事件——EnvironmentChangeEvent

这就很明显了,触发了环境变更事件,需要进行更新了
接下来就直接跳到:

at org.springframework.cloud.context.properties.ConfigurationPropertiesRebinder.rebind(ConfigurationPropertiesRebinder.java:84)

在这里插入图片描述

很明显,对所有 Beans 的配置变量进行重新绑定
进入这个 rebind 方法
(org.springframework.cloud.context.properties.ConfigurationPropertiesRebinder#rebind(java.lang.String))

在这里插入图片描述

注意到这里,会进行是否刷新的判断,再进入 getNeverRefreshable 这个方法
(org.springframework.cloud.context.properties.ConfigurationPropertiesRebinder#getNeverRefreshable):

这就非常非常明显了,SpringCloud 在刷新变量前会先获取不进行更新的类名,当类名匹配到时就会跳过此类的更新

因此,当我不想更新 com.alibaba.druid.pool.DruidDataSource 这个类的时候,就需要在配置文件中加入

spring:
  cloud:
    refresh:
      never-refreshable: com.zaxxer.hikari.HikariDataSource,com.alibaba.druid.pool.DruidDataSource

不同类之间用英文逗号分隔,其中 com.zaxxer.hikari.HikariDataSource 是默认值,因此加上以防万一
至此,解决了 Nacos 自动刷新配置报错的问题

  • 19
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值