Spring Boot @ConfigurationProperties(原理四)

用法:https://blog.csdn.net/qq_32868023/article/details/123390627
原理一:https://blog.csdn.net/qq_32868023/article/details/123515652
原理二:https://blog.csdn.net/qq_32868023/article/details/123603241
原理三:https://blog.csdn.net/qq_32868023/article/details/123604439
原理四:https://blog.csdn.net/qq_32868023/article/details/123606236
原理五:https://blog.csdn.net/qq_32868023/article/details/123616110

Binder负责对一个Bindable进行绑定,一个Bindable的属性可能是一个java对象、数组、集合、Map等各种情况,需要递归的去绑定java对象的属性,数组、集合、Map的元素,其流程简单来说就是:从配置属性源中获取到对应的值,将这个值转化为所需要的类型。本节主要分析下获取到值之后如何转换,如何对java对象、数组、集合、Map进行绑定

1、BindConverter

BindConverter负责将配置的Property转换成所需要的类型,具体的转换工作交给它所持有的delegates,看一下delegates的构造函数,delegates包括了三部分

  • TypeConverterConversionService,会处理PropertyEditor逻辑
  • 参数中的conversionServices,顺着调用链路去看,可以发现conversionServices是从beanFactory里获取的,如果没有配置这个bean的话就没有
  • ApplicationConversionService,大部分转换逻辑都是它处理的
final class BindConverter {
    
	private final List<ConversionService> delegates;

	private BindConverter(List<ConversionService> conversionServices,
			Consumer<PropertyEditorRegistry> propertyEditorInitializer) {
		List<ConversionService> delegates = new ArrayList<>();
		delegates.add(new TypeConverterConversionService(propertyEditorInitializer));
		boolean hasApplication = false;
		if (!CollectionUtils.isEmpty(conversionServices)) {
			for (ConversionService conversionService : conversionServices) {
				delegates.add(conversionService);
				hasApplication = hasApplication || conversionService instanceof ApplicationConversionService;
			}
		}
		if (!hasApplication) {
			delegates.add(ApplicationConversionService.getSharedInstance());
		}
		this.delegates = Collections.unmodifiableList(delegates);
	}
}

BindConverter的转换逻辑实际上就是遍历了下delegates,将转换工作委托给delegates,具体这些Converter是如何工作的这里就不继续介绍了

	private Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) {
		ConversionException failure = null;
		for (ConversionService delegate : this.delegates) {
			try {
				if (delegate.canConvert(sourceType, targetType)) {
					return delegate.convert(source, sourceType, targetType);
				}
			}
			catch (ConversionException ex) {
				if (failure == null && ex instanceof ConversionFailedException) {
					failure = ex;
				}
			}
		}
		throw (failure != null) ? failure : new ConverterNotFoundException(sourceType, targetType);
	}

2、AggregateBinder

AggregateBinder是用来绑定数组、集合、Map属性的工具,包括MapBinder、CollectionBinder、ArrayBinder三个

a、AggregateBinder实现

AggregateBinder,bind其实就是个模板方法,调用了bindAggregate抽象方法。

abstract class AggregateBinder<T> {

	@SuppressWarnings("unchecked")
	final Object bind(ConfigurationPropertyName name, Bindable<?> target, AggregateElementBinder elementBinder) {
		Object result = bindAggregate(name, target, elementBinder);
		Supplier<?> value = target.getValue();
		if (result == null || value == null) {
			return result;
		}
		return merge((Supplier<T>) value, (T) result);
	}
}

bind方法里的name和target参数好理解,elementBinder是什么呢?

@FunctionalInterface
interface AggregateElementBinder {

	default Object bind(ConfigurationPropertyName name, Bindable<?> target) {
		return bind(name, target, null);
	}
    
	Object bind(ConfigurationPropertyName name, Bindable<?> target, ConfigurationPropertySource source);
}

AggregateElementBinder就是一个接口,用来绑定数组、集合、Map里面的元素的,这个接口没有实现类,AggregateBinder在调用时,通过lambda的写法传进来一个AggregateElementBinder实例,实例的实现中会调用Binder的bind方法,因为数组、集合、Map里面的元素就是一个普通的Bindable

b、ArrayBinder和CollectionBinder

ArrayBinder和CollectionBinder都继承IndexedElementsBinder。ArrayBinder调了bindIndexed方法然后把List转换成数组

class ArrayBinder extends IndexedElementsBinder<Object> {

	@Override
	protected Object bindAggregate(ConfigurationPropertyName name, Bindable<?> target,
			AggregateElementBinder elementBinder) {
		IndexedCollectionSupplier result = new IndexedCollectionSupplier(ArrayList::new);
		ResolvableType aggregateType = target.getType();
		ResolvableType elementType = target.getType().getComponentType();
		bindIndexed(name, target, elementBinder, aggregateType, elementType, result);
		if (result.wasSupplied()) {
			List<Object> list = (List<Object>) result.get();
			Object array = Array.newInstance(elementType.resolve(), list.size());
			for (int i = 0; i < list.size(); i++) {
				Array.set(array, i, list.get(i));
			}
			return array;
		}
		return null;
    }
}

CollectionBinder也是调了bindIndexed方法,调用之前用CollectionFactory创建了一下Collection

class CollectionBinder extends IndexedElementsBinder<Collection<Object>> {

	@Override
	protected Object bindAggregate(ConfigurationPropertyName name, Bindable<?> target,
			AggregateElementBinder elementBinder) {
		Class<?> collectionType = (target.getValue() != null) ? List.class : target.getType().resolve(Object.class);
		ResolvableType aggregateType = ResolvableType.forClassWithGenerics(List.class,
				target.getType().asCollection().getGenerics());
		ResolvableType elementType = target.getType().asCollection().getGeneric();
		IndexedCollectionSupplier result = new IndexedCollectionSupplier(
				() -> CollectionFactory.createCollection(collectionType, elementType.resolve(), 0));
		bindIndexed(name, target, elementBinder, aggregateType, elementType, result);
		if (result.wasSupplied()) {
			return result.get();
		}
		return null;
	}
}

c、IndexedElementsBinder

IndexedElementsBinder是ArrayBinder和CollectionBinder的父类。

	protected final void bindIndexed(ConfigurationPropertyName name, Bindable<?> target,
			AggregateElementBinder elementBinder, ResolvableType aggregateType, ResolvableType elementType,
			IndexedCollectionSupplier result) {
		for (ConfigurationPropertySource source : getContext().getSources()) {
			bindIndexed(source, name, target, elementBinder, result, aggregateType, elementType);
			if (result.wasSupplied() && result.get() != null) {
				return;
			}
		}
	}

这个bindIndexed遍历了ConfigurationPropertySource,然后调用bindIndexed重载方法绑定

	private void bindIndexed(ConfigurationPropertySource source, ConfigurationPropertyName root, Bindable<?> target,
			AggregateElementBinder elementBinder, IndexedCollectionSupplier collection, ResolvableType aggregateType,
			ResolvableType elementType) {
		ConfigurationProperty property = source.getConfigurationProperty(root);
		if (property != null) {
			getContext().setConfigurationProperty(property);
			bindValue(target, collection.get(), aggregateType, elementType, property.getValue());
		}
		else {
			bindIndexed(source, root, elementBinder, collection, elementType);
		}
	}

bindIndexed的重载先拿到ConfigurationProperty,如果有直接bingValue,如果没有继续调用bindIndexed重载。这里其实是支持了两种写法:
写法一,逗号分隔

demo.list=1,2,3,4,5

写法二

demo.list[0]=1
demo.list[1]=2
demo.list[2]=3
demo.list[3]=4
demo.list[4]=5

这两种写法的效果是一样的,分别看下bindValue和bindIndexed的实现

	private void bindValue(Bindable<?> target, Collection<Object> collection, ResolvableType aggregateType,
			ResolvableType elementType, Object value) {
		if (value == null || value instanceof CharSequence && ((CharSequence) value).length() == 0) {
			return;
		}
		Object aggregate = convert(value, aggregateType, target.getAnnotations());
		ResolvableType collectionType = ResolvableType.forClassWithGenerics(collection.getClass(), elementType);
		Collection<Object> elements = convert(aggregate, collectionType);
		collection.addAll(elements);
	}

bindValue就是调用converter将property转换成需要的类型

	private void bindIndexed(ConfigurationPropertySource source, ConfigurationPropertyName root,
			AggregateElementBinder elementBinder, IndexedCollectionSupplier collection, ResolvableType elementType) {
		MultiValueMap<String, ConfigurationPropertyName> knownIndexedChildren = getKnownIndexedChildren(source, root);
		for (int i = 0; i < Integer.MAX_VALUE; i++) {
			ConfigurationPropertyName name = root.append((i != 0) ? "[" + i + "]" : INDEX_ZERO);
			Object value = elementBinder.bind(name, Bindable.of(elementType), source);
			if (value == null) {
				break;
			}
			knownIndexedChildren.remove(name.getLastElement(Form.UNIFORM));
			collection.get().add(value);
		}
		assertNoUnboundChildren(source, knownIndexedChildren);
	}

bindIndexed方法从index=0开始,追加到root后面,调用elementBinder继续绑定。而elementBinder实际上又是调用的Binder#bind方法,所以集合的元素又可以是一个java对象

d、MapBinder

	protected Object bindAggregate(ConfigurationPropertyName name, Bindable<?> target,
			AggregateElementBinder elementBinder) {
		Map<Object, Object> map = CollectionFactory
				.createMap((target.getValue() != null) ? Map.class : target.getType().resolve(Object.class), 0);
		Bindable<?> resolvedTarget = resolveTarget(target);
		boolean hasDescendants = hasDescendants(name);
		for (ConfigurationPropertySource source : getContext().getSources()) {
			if (!ConfigurationPropertyName.EMPTY.equals(name)) {
				ConfigurationProperty property = source.getConfigurationProperty(name);
				if (property != null && !hasDescendants) {
					return getContext().getConverter().convert(property.getValue(), target);
				}
				source = source.filter(name::isAncestorOf);
			}
			new EntryBinder(name, resolvedTarget, elementBinder).bindEntries(source, map);
		}
		return map.isEmpty() ? null : map;
	}

bindAggregate遍历了所有source,对每个source首先还是尝试查找property,如果查找到了就用converter进行转换,由于ApplicationConversionService里没有String到Map的转换,如果没有配置的话走到这里会转换不了抛异常;如果没有找到property,将实例化一个FilteredConfigurationPropertiesSource,这样对source遍历就都是需要的kv了,然后用EntryBinder对Map的Entry进行绑定

		void bindEntries(ConfigurationPropertySource source, Map<Object, Object> map) {
			if (source instanceof IterableConfigurationPropertySource) {
				for (ConfigurationPropertyName name : (IterableConfigurationPropertySource) source) {
					Bindable<?> valueBindable = getValueBindable(name);
					ConfigurationPropertyName entryName = getEntryName(source, name);
					Object key = getContext().getConverter().convert(getKeyName(entryName), this.keyType);
					map.computeIfAbsent(key, (k) -> this.elementBinder.bind(entryName, valueBindable));
				}
			}
		}

bindEntries方法遍历了所有的kv(由于source是FilteredConfigurationPropertiesSource,这里遍历出来的kv都是需要绑定的)。for循环里面对key和value进行了绑定,对key是去除keyName,直接convert,那么key必须是BindConverter能直接转换的类型,而对于value,使用elementBinder继续绑定,这一点跟前面数组和集合是一样的

3、DataObjectBinder

DataObjectBinder是一个接口,解决java对象绑定的问题。接口定义如下,有两个方法:bind和create

interface DataObjectBinder {

	<T> T bind(ConfigurationPropertyName name, Bindable<T> target, Context context,
			DataObjectPropertyBinder propertyBinder);

	<T> T create(Bindable<T> target, Context context);
}

java对象是怎么绑定的?有两种情况:

  • 对于bindMethod=JAVA_BEAN,遍历java对象内的所有属性,先将属性的值绑定出来,然后调用set方法将值设置进去
  • 对于bindMethod=VALUE_OBJECT,需要先准备好所有的构造函数参数,然后调用构造函数实例化出来

这两种情况也就对应了DataObjectBinder的两个实现,bind方法name、target、context都好理解,propetyBinder用来绑定每个属性,DataObjectPropertyBinder是一个接口,就一个bindProperty方法,这个接口没有实现类,也是通过lambda写法传进来实例。对于property实际上也是一个普通的Bindable对象,所以其lambda实现中也是调用了Binder#bind方法

interface DataObjectPropertyBinder {

	Object bindProperty(String propertyName, Bindable<?> target);
}

看下DataObjectBinder的两个实现:JavaBeanBinder和ValueObjectBinder

a、JavaBeanBinder

	public <T> T bind(ConfigurationPropertyName name, Bindable<T> target, Context context,
			DataObjectPropertyBinder propertyBinder) {
		boolean hasKnownBindableProperties = target.getValue() != null && hasKnownBindableProperties(name, context);
		Bean<T> bean = Bean.get(target, hasKnownBindableProperties);
		if (bean == null) {
			return null;
		}
		BeanSupplier<T> beanSupplier = bean.getSupplier(target);
		boolean bound = bind(propertyBinder, bean, beanSupplier, context);
		return (bound ? beanSupplier.get() : null);
	}

bind实现先通过Bean#get实例化一个bean,这个过程已经把target的属性全部解析好了,调用bind重载方法。这Bean#get解析的过程中,已经将fieldName的驼峰命名法转换成了用’-'来分隔不同单词

	private <T> boolean bind(DataObjectPropertyBinder propertyBinder, Bean<T> bean, BeanSupplier<T> beanSupplier,
			Context context) {
		boolean bound = false;
		for (BeanProperty beanProperty : bean.getProperties().values()) {
			bound |= bind(beanSupplier, propertyBinder, beanProperty);
			context.clearConfigurationProperty();
		}
		return bound;
	}

这个重载方法遍历了Bean解析出来的所有field,继续调用bind重载

	private <T> boolean bind(BeanSupplier<T> beanSupplier, DataObjectPropertyBinder propertyBinder,
			BeanProperty property) {
		String propertyName = property.getName();
		ResolvableType type = property.getType();
		Supplier<Object> value = property.getValue(beanSupplier);
		Annotation[] annotations = property.getAnnotations();
		Object bound = propertyBinder.bindProperty(propertyName,
				Bindable.of(type).withSuppliedValue(value).withAnnotations(annotations));
		if (bound == null) {
			return false;
		}
		if (property.isSettable()) {
			property.setValue(beanSupplier, bound);
		}
		else if (value == null || !bound.equals(value.get())) {
			throw new IllegalStateException("No setter found for property: " + property.getName());
		}
		return true;
	}

这个重载好理解了,调用propertyBinder获取绑定到这个field的值,然后调用set方法set进去。

b、ValueObjectBinder

	public <T> T bind(ConfigurationPropertyName name, Bindable<T> target, Binder.Context context,
			DataObjectPropertyBinder propertyBinder) {
		ValueObject<T> valueObject = ValueObject.get(target, this.constructorProvider, context);
		if (valueObject == null) {
			return null;
		}
		context.pushConstructorBoundTypes(target.getType().resolve());
		List<ConstructorParameter> parameters = valueObject.getConstructorParameters();
		List<Object> args = new ArrayList<>(parameters.size());
		boolean bound = false;
		for (ConstructorParameter parameter : parameters) {
			Object arg = parameter.bind(propertyBinder);
			bound = bound || arg != null;
			arg = (arg != null) ? arg : getDefaultValue(context, parameter);
			args.add(arg);
		}
		context.clearConfigurationProperty();
		context.popConstructorBoundTypes();
		return bound ? valueObject.instantiate(args) : null;
	}

这里逻辑也不复杂,解析出构造函数的所有参数ConstructorParameter,for循环通过propertyBinder获取到每个参数绑定到的value,然后将这些value作为参数调用构造函数去实例化对象,完成绑定

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值