Spring Boot 的类型转换实现
1. 转换的配置
WebFlux转换服务是通过 org.springframework.boot.autoconfigure.web.format.WebConversionService 实现的,他实现在不同类型的属性之间进行转换。
其类图如下图所示
其提供多种默认类型的转换,开发人员也可以根据自己的需求自定义转换规则。
支持Converter,GenericConverter,Formatter等多种形式的自定义转换,只需要编写自己的实现类并注册到Spring容器中就可以实现注册。
所有的自定义转换最终都会转换成GenericConverter存储到Map中。
org.springframework.boot.autoconfigure.web.reactive.WebFluxAutoConfiguration.WebFluxConfig#addFormatters
@Override
public void addFormatters(FormatterRegistry registry) {
for (Converter<?, ?> converter : getBeansOfType(Converter.class)) {
registry.addConverter(converter);
}
for (GenericConverter converter : getBeansOfType(GenericConverter.class)) {
registry.addConverter(converter);
}
for (Formatter<?> formatter : getBeansOfType(Formatter.class)) {
registry.addFormatter(formatter);
}
}
1.1 StringToZonedDateTimeConverter
org.springframework.cloud.gateway.support.StringToZonedDateTimeConverter
public class StringToZonedDateTimeConverter implements Converter<String, ZonedDateTime> {
@Override
public ZonedDateTime convert(String source) {
ZonedDateTime dateTime;
try {
long epoch = Long.parseLong(source);
dateTime = Instant.ofEpochMilli(epoch).atOffset(ZoneOffset.ofTotalSeconds(0))
.toZonedDateTime();
}
catch (NumberFormatException e) {
// try ZonedDateTime instead
dateTime = ZonedDateTime.parse(source);
}
return dateTime;
}
}
1.2 StringToRedisClientInfoConverter
public class StringToRedisClientInfoConverter implements Converter<String[], List<RedisClientInfo>> {
/*
* (non-Javadoc)
* @see org.springframework.core.convert.converter.Converter#convert(Object)
*/
@Override
public List<RedisClientInfo> convert(String[] lines) {
List<RedisClientInfo> clientInfoList = new ArrayList<>(lines.length);
for (String line : lines) {
clientInfoList.add(RedisClientInfoBuilder.fromString(line));
}
return clientInfoList;
}
}
2. 绑定的方式
可以通过创建Binder处理字段绑定逻辑,提供聚合、bean、property维度的类型转换。
以spring-cloud-gateway的route definiton转换成route的逻辑举例:
org.springframework.cloud.gateway.support.ConfigurationUtils#bind(java.lang.Object, java.util.Map<java.lang.String,java.lang.Object>, java.lang.String, java.lang.String, org.springframework.validation.Validator, org.springframework.core.convert.ConversionService)
public static void bind(Object o, Map<String, Object> properties,
String configurationPropertyName, String bindingName, Validator validator,
ConversionService conversionService) {
Object toBind = getTargetObject(o);
new Binder(
Collections.singletonList(new MapConfigurationPropertySource(properties)),
null, conversionService).bind(configurationPropertyName,
Bindable.ofInstance(toBind));
if (validator != null) {
BindingResult errors = new BeanPropertyBindingResult(toBind, bindingName);
validator.validate(toBind, errors);
if (errors.hasErrors()) {
throw new RuntimeException(new BindException(errors));
}
}
}
org.springframework.boot.context.properties.bind.Binder#bindObject
private <T> Object bindObject(ConfigurationPropertyName name, Bindable<T> target, BindHandler handler,
Context context, boolean allowRecursiveBinding) {
ConfigurationProperty property = findProperty(name, context);
if (property == null && containsNoDescendantOf(context.getSources(), name)) {
return null;
}
AggregateBinder<?> aggregateBinder = getAggregateBinder(target, context);
if (aggregateBinder != null) {
return bindAggregate(name, target, handler, context, aggregateBinder);
}
if (property != null) {
try {
return bindProperty(target, context, property);
}
catch (ConverterNotFoundException ex) {
// We might still be able to bind it as a bean
Object bean = bindBean(name, target, handler, context, allowRecursiveBinding);
if (bean != null) {
return bean;
}
throw ex;
}
}
return bindBean(name, target, handler, context, allowRecursiveBinding);
}
3. 转换的执行
最终的转换逻辑是根据源类型和目标类型查找与之匹配的转换器(Converter)并进行转换,如果找不到转换器会抛出异常。
org.springframework.core.convert.support.GenericConversionService#convert(java.lang.Object, org.springframework.core.convert.TypeDescriptor, org.springframework.core.convert.TypeDescriptor)
@Override
@Nullable
public Object convert(@Nullable Object source, @Nullable TypeDescriptor sourceType, TypeDescriptor targetType) {
Assert.notNull(targetType, "Target type to convert to cannot be null");
if (sourceType == null) {
Assert.isTrue(source == null, "Source must be [null] if source type == [null]");
return handleResult(null, targetType, convertNullSource(null, targetType));
}
if (source != null && !sourceType.getObjectType().isInstance(source)) {
throw new IllegalArgumentException("Source to convert from must be an instance of [" +
sourceType + "]; instead it was a [" + source.getClass().getName() + "]");
}
GenericConverter converter = getConverter(sourceType, targetType);
if (converter != null) {
Object result = ConversionUtils.invokeConverter(converter, source, sourceType, targetType);
return handleResult(sourceType, targetType, result);
}
return handleConverterNotFound(source, sourceType, targetType);
}