现象
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方法, 赋值属性值.