记一次nacos自动更新导致druird属性数据更新异常

现象

在这里插入图片描述

org.springframework.boot.context.properties.bind.BindException: Failed to bind properties under 'spring.druid' 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)

主要报错信息如下:

  • java.lang.IllegalStateException: Unable to set value for property initial-size
  • org.springframework.boot.context.properties.bind.BindException: Failed to bind properties under ‘spring.druid’ to javax.sql.DataSource
    在nacos上修改了druid的属性配置后, 基于nacos的自动更新, 会重新设置druid的属性值

原因

public void setInitialSize(int initialSize) {
        if (this.initialSize == initialSize) {
            return;
        }

        if (inited) {
            throw new UnsupportedOperationException();
        }

        this.initialSize = initialSize;
    }

在重新绑定属性值的时候, 会调用对应的setter方法, 从上面报错的截图可以看到, 由于inited是false, 导致抛出了UnsupportedOperationException异常
inited属性默认是

protected volatile boolean                         inited                                    = false;

第一种就是把inited配置成true, 第二个就是自己重写DruidDataSourceWrapper实现自己的自动更新业务逻辑.

自动更新原理

先上图, 对流程有个整体的认识, 这个过程相对于spring容器启动的时候, 和创建bean的时候相差无几, 只是BeanPostProcessor不同而已.
在这里插入图片描述

在nacos变更数据后, 主要是通过发送event事件, 监听者监听事件变动后, 做相应的业务逻辑处理.

NacosContextRefresher

private void registerNacosListener(final String groupKey, final String dataKey) {
		Listener listener = listenerMap.computeIfAbsent(key,
				lst -> new AbstractSharedListener() {
					@Override
					public void innerReceive(String dataId, String group,
							String configInfo) {
						//发送RefreshEvent事件
						applicationContext.publishEvent(
								new RefreshEvent(this, null, "Refresh Nacos config"));
						if (log.isDebugEnabled()) {
					}
				});
		try {
			configService.addListener(dataKey, groupKey, listener);
		}
		catch (NacosException e) {
		}
	}

RefreshEventListener

public void handle(RefreshEvent event) {
		if (this.ready.get()) {
			//调用refresher的refresh方法
			Set<String> keys = this.refresh.refresh();
		}
	}

ContextRefresher

public synchronized Set<String> refreshEnvironment() {
		Map<String, Object> before = extract(
				this.context.getEnvironment().getPropertySources());
		addConfigFilesToEnvironment();
		Set<String> keys = changes(before,
				extract(this.context.getEnvironment().getPropertySources())).keySet();
		//发送EnvironmentChangeEvent事件
		this.context.publishEvent(new EnvironmentChangeEvent(this.context, keys));
		return keys;
	}

ConfigurationPropertiesRebinder

它是EnvironmentChangeEvent的监听器实现, 从名字上我们也可以看到, 它的主要作用是为了ConfigurationProperties配置方式的属性重新绑定.在nacos更新以后, 就是通过它做属性重新绑定的.

public void rebind() {
		this.errors.clear();
		for (String name : this.beans.getBeanNames()) {
			rebind(name);
		}
	}

调用指定bean的rebind方法

public boolean rebind(String name) {
		if (!this.beans.getBeanNames().contains(name)) {
			return false;
		}
		if (this.applicationContext != null) {
			try {
				Object bean = this.applicationContext.getBean(name);
				if (AopUtils.isAopProxy(bean)) {
					bean = ProxyUtils.getTargetObject(bean);
				}
				if (bean != null) {
					//省略....
					//获取bean实例, 首先销毁它, 然后重新初始化这个bean
					this.applicationContext.getAutowireCapableBeanFactory()
							.initializeBean(bean, name);
					return true;
				}
			}
			//省略...
		}
		return false;
	}

AbstractAutowireCapableBeanFactory

protected Object initializeBean(String beanName, Object bean, @Nullable RootBeanDefinition mbd) {
		//省略...
		Object wrappedBean = bean;
		if (mbd == null || !mbd.isSynthetic()) {
			//调用bean的前置处理器, 初始化数据, 调用processor的postProcessBeforeInitialization方法
			wrappedBean = applyBeanPostProcessorsBeforeInitialization(wrappedBean, beanName);
		}
		//省略...

		return wrappedBean;
	}

这里调用的processor就是ConfigurationPropertiesBindingPostProcessor

ConfigurationPropertiesBindingPostProcessor

private void bind(ConfigurationPropertiesBean bean) {
		//省略...
		try {
			this.binder.bind(bean);
		}
		catch (Exception ex) {
			throw new ConfigurationPropertiesBindException(bean, ex);
		}
	}

Binder

javabean的属性绑定就是通过它实现的.

private Object bindDataObject(ConfigurationPropertyName name, Bindable<?> target, BindHandler handler,
			Context context, boolean allowRecursiveBinding) {
		//省略...
		DataObjectPropertyBinder propertyBinder = (propertyName, propertyTarget) -> bind(name.append(propertyName),
				propertyTarget, handler, context, false, false);
		return context.withDataObject(type, () -> {
			for (DataObjectBinder dataObjectBinder : this.dataObjectBinders) {
				Object instance = dataObjectBinder.bind(name, target, context, propertyBinder);
				if (instance != null) {
					return instance;
				}
			}
			return null;
		});
	}

最终调用DataObjectBinder的bind方法完成属性的装配, DataObjectBinder的实现类如下:
在这里插入图片描述

JavaBean是用来将数据绑定到javabean对象上的工具
ValueObjectBinder是用来将数据绑定到值对象上的工具

这里我们简单看下JavaBean对象的数据绑定, 比如我们controller上的body的请求参数, 自动绑定到我们设置的javabean对象上, 就是通过它来实现的.

JavaBeanBinder

private <T> boolean bind(BeanSupplier<T> beanSupplier, DataObjectPropertyBinder propertyBinder,
			BeanProperty property) {
		//javabean的属性名称
		String propertyName = property.getName();
		//javabean的属性数据类型
		ResolvableType type = property.getType();
		//javabean的属性值
		Supplier<Object> value = property.getValue(beanSupplier);
		//javabean属性的注解
		Annotation[] annotations = property.getAnnotations();
		//获取属性值
		Object bound = propertyBinder.bindProperty(propertyName,
				Bindable.of(type).withSuppliedValue(value).withAnnotations(annotations));
		if (bound == null) {
			return false;
		}
		//设置javabean的属性值
		if (property.isSettable()) {
			property.setValue(beanSupplier, bound);
		}
		//省略.....
	}

属性设置

void setValue(Supplier<?> instance, Object value) {
			try {
				this.setter.setAccessible(true);
				this.setter.invoke(instance.get(), value);
			}
			catch (Exception ex) {
				throw new IllegalStateException("Unable to set value for property " + this.name, ex);
			}
		}

这段代码是不是很熟悉, 写过反射的同学, 应该都知道这段代码的含义, 通过反射, 调用bean的setter方法, 赋值属性值.

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值