Spring Converter 体系(二)

简介: 最近封装 RPC 相关的模块,领导说数据转换可以考虑使用 Spring 原有的 Converter 体系。

DefaultConversionService


DefaultConversionService 提供方法为 ConverterRegistry 增加一些常用的 Converter

public class DefaultConversionService extends GenericConversionService {
   @Nullable
   private static volatile DefaultConversionService sharedInstance;
   public DefaultConversionService() {
      addDefaultConverters(this);
   }
   public static ConversionService getSharedInstance() {
      DefaultConversionService cs = sharedInstance;
      if (cs == null) {
         synchronized (DefaultConversionService.class) {
            cs = sharedInstance;
            if (cs == null) {
               cs = new DefaultConversionService();
               sharedInstance = cs;
            }
         }
      }
      return cs;
   }
   public static void addDefaultConverters(ConverterRegistry converterRegistry) {
      addScalarConverters(converterRegistry);
      addCollectionConverters(converterRegistry);
      converterRegistry.addConverter(new ByteBufferConverter((ConversionService) converterRegistry));
      converterRegistry.addConverter(new StringToTimeZoneConverter());
      converterRegistry.addConverter(new ZoneIdToTimeZoneConverter());
      converterRegistry.addConverter(new ZonedDateTimeToCalendarConverter());
      converterRegistry.addConverter(new ObjectToObjectConverter());
      converterRegistry.addConverter(new IdToEntityConverter((ConversionService) converterRegistry));
      converterRegistry.addConverter(new FallbackObjectToStringConverter());
      converterRegistry.addConverter(new ObjectToOptionalConverter((ConversionService) converterRegistry));
   }
   public static void addCollectionConverters(ConverterRegistry converterRegistry) {
      ConversionService conversionService = (ConversionService) converterRegistry;
      converterRegistry.addConverter(new ArrayToCollectionConverter(conversionService));
      converterRegistry.addConverter(new CollectionToArrayConverter(conversionService));
      converterRegistry.addConverter(new ArrayToArrayConverter(conversionService));
      converterRegistry.addConverter(new CollectionToCollectionConverter(conversionService));
      converterRegistry.addConverter(new MapToMapConverter(conversionService));
      converterRegistry.addConverter(new ArrayToStringConverter(conversionService));
      converterRegistry.addConverter(new StringToArrayConverter(conversionService));
      converterRegistry.addConverter(new ArrayToObjectConverter(conversionService));
      converterRegistry.addConverter(new ObjectToArrayConverter(conversionService));
      converterRegistry.addConverter(new CollectionToStringConverter(conversionService));
      converterRegistry.addConverter(new StringToCollectionConverter(conversionService));
      converterRegistry.addConverter(new CollectionToObjectConverter(conversionService));
      converterRegistry.addConverter(new ObjectToCollectionConverter(conversionService));
      converterRegistry.addConverter(new StreamConverter(conversionService));
   }
   private static void addScalarConverters(ConverterRegistry converterRegistry) {
      converterRegistry.addConverterFactory(new NumberToNumberConverterFactory());
      converterRegistry.addConverterFactory(new StringToNumberConverterFactory());
      converterRegistry.addConverter(Number.class, String.class, new ObjectToStringConverter());
      converterRegistry.addConverter(new StringToCharacterConverter());
      converterRegistry.addConverter(Character.class, String.class, new ObjectToStringConverter());
      converterRegistry.addConverter(new NumberToCharacterConverter());
      converterRegistry.addConverterFactory(new CharacterToNumberFactory());
      converterRegistry.addConverter(new StringToBooleanConverter());
      converterRegistry.addConverter(Boolean.class, String.class, new ObjectToStringConverter());
      converterRegistry.addConverterFactory(new StringToEnumConverterFactory());
      converterRegistry.addConverter(new EnumToStringConverter((ConversionService) converterRegistry));
      converterRegistry.addConverterFactory(new IntegerToEnumConverterFactory());
      converterRegistry.addConverter(new EnumToIntegerConverter((ConversionService) converterRegistry));
      converterRegistry.addConverter(new StringToLocaleConverter());
      converterRegistry.addConverter(Locale.class, String.class, new ObjectToStringConverter());
      converterRegistry.addConverter(new StringToCharsetConverter());
      converterRegistry.addConverter(Charset.class, String.class, new ObjectToStringConverter());
      converterRegistry.addConverter(new StringToCurrencyConverter());
      converterRegistry.addConverter(Currency.class, String.class, new ObjectToStringConverter());
      converterRegistry.addConverter(new StringToPropertiesConverter());
      converterRegistry.addConverter(new PropertiesToStringConverter());
      converterRegistry.addConverter(new StringToUUIDConverter());
      converterRegistry.addConverter(UUID.class, String.class, new ObjectToStringConverter());
   }
}
复制代码

FormattingConversionService

FormattingConversionService 则实现了 FormatterRetistry 接口

网络异常,图片无法展示
|

网络异常,图片无法展示
|

/**
 * Prints objects of type T for display.
 *
 * @author Keith Donald
 * @since 3.0
 * @param <T> the type of object this Printer prints
 */
@FunctionalInterface
public interface Printer<T> {
   /**
    * Print the object of type T for display.
    * @param object the instance to print
    * @param locale the current user locale
    * @return the printed text string
    */
   String print(T object, Locale locale);
}
复制代码


Printer 主要是为了打印展示某个类型对象的、根据 locale

/**
 * Parses text strings to produce instances of T.
 *
 * @author Keith Donald
 * @since 3.0
 * @param <T> the type of object this Parser produces
 */
@FunctionalInterface
public interface Parser<T> {
   /**
    * Parse a text String to produce a T.
    * @param text the text string
    * @param locale the current user locale
    * @return an instance of T
    * @throws ParseException when a parse exception occurs in a java.text parsing library
    * @throws IllegalArgumentException when a parse exception occurs
    */
   T parse(String text, Locale locale) throws ParseException;
}
复制代码


Parser 则是从 String 根据 locale 解释为 T、这个跟 Converter 优点类似、但是是根据 Locale 的不用来解释转换的

/**
 * Formats objects of type T.
 * A Formatter is both a Printer <i>and</i> a Parser for an object type.
 *
 * @author Keith Donald
 * @since 3.0
 * @param <T> the type of object this Formatter formats
 */
public interface Formatter<T> extends Printer<T>, Parser<T> {
}
复制代码


Formatter 则直接整合这两个接口

网络异常,图片无法展示
|

网络异常,图片无法展示
|

无论是 Printer 或者是 Parser 最终都会转换为 GenericConverter、在 convert 方法中在调用对应的方法进行转换解释。


ApplicationConversionService


Spring Boot 默认使用的 ConversionService 。

但是在 Spring 中管理的却是 WebConversionService。它最终也会调用 addBeans 方法

网络异常,图片无法展示
|

public class ApplicationConversionService extends FormattingConversionService {
   private static volatile ApplicationConversionService sharedInstance;
   public ApplicationConversionService() {
      this(null);
   }
   public ApplicationConversionService(StringValueResolver embeddedValueResolver) {
      if (embeddedValueResolver != null) {
         setEmbeddedValueResolver(embeddedValueResolver);
      }
      configure(this);
   }
   public static ConversionService getSharedInstance() {
      ApplicationConversionService sharedInstance = ApplicationConversionService.sharedInstance;
      if (sharedInstance == null) {
         synchronized (ApplicationConversionService.class) {
            sharedInstance = ApplicationConversionService.sharedInstance;
            if (sharedInstance == null) {
               sharedInstance = new ApplicationConversionService();
               ApplicationConversionService.sharedInstance = sharedInstance;
            }
         }
      }
      return sharedInstance;
   }
   public static void configure(FormatterRegistry registry) {
      DefaultConversionService.addDefaultConverters(registry);
      DefaultFormattingConversionService.addDefaultFormatters(registry);
      addApplicationFormatters(registry);
      addApplicationConverters(registry);
   }
   public static void addApplicationConverters(ConverterRegistry registry) {
      addDelimitedStringConverters(registry);
      registry.addConverter(new StringToDurationConverter());
      registry.addConverter(new DurationToStringConverter());
      registry.addConverter(new NumberToDurationConverter());
      registry.addConverter(new DurationToNumberConverter());
      registry.addConverter(new StringToPeriodConverter());
      registry.addConverter(new PeriodToStringConverter());
      registry.addConverter(new NumberToPeriodConverter());
      registry.addConverter(new StringToDataSizeConverter());
      registry.addConverter(new NumberToDataSizeConverter());
      registry.addConverter(new StringToFileConverter());
      registry.addConverter(new InputStreamSourceToByteArrayConverter());
      registry.addConverterFactory(new LenientStringToEnumConverterFactory());
      registry.addConverterFactory(new LenientBooleanToEnumConverterFactory());
   }
   public static void addDelimitedStringConverters(ConverterRegistry registry) {
      ConversionService service = (ConversionService) registry;
      registry.addConverter(new ArrayToDelimitedStringConverter(service));
      registry.addConverter(new CollectionToDelimitedStringConverter(service));
      registry.addConverter(new DelimitedStringToArrayConverter(service));
      registry.addConverter(new DelimitedStringToCollectionConverter(service));
   }
   public static void addApplicationFormatters(FormatterRegistry registry) {
      registry.addFormatter(new CharArrayFormatter());
      registry.addFormatter(new InetAddressFormatter());
      registry.addFormatter(new IsoOffsetFormatter());
   }
   public static void addBeans(FormatterRegistry registry, ListableBeanFactory beanFactory) {
      Set<Object> beans = new LinkedHashSet<>();
      beans.addAll(beanFactory.getBeansOfType(GenericConverter.class).values());
      beans.addAll(beanFactory.getBeansOfType(Converter.class).values());
      beans.addAll(beanFactory.getBeansOfType(Printer.class).values());
      beans.addAll(beanFactory.getBeansOfType(Parser.class).values());
      for (Object bean : beans) {
         if (bean instanceof GenericConverter) {
            registry.addConverter((GenericConverter) bean);
         }
         else if (bean instanceof Converter) {
            registry.addConverter((Converter<?, ?>) bean);
         }
         else if (bean instanceof Formatter) {
            registry.addFormatter((Formatter<?>) bean);
         }
         else if (bean instanceof Printer) {
            registry.addPrinter((Printer<?>) bean);
         }
         else if (bean instanceof Parser) {
            registry.addParser((Parser<?>) bean);
         }
      }
   }
}
复制代码

并且会将所有的 GenericConverter、Converter、Printer、Parser bean 注册到 FormatterRegistry 中


Converter VS PropertyEditor


  • PropertyEditor 从 String 转为其他类型
  • Converter 从各种类型转为各种类型

Spring 同时支持两者。

BeanWrapper 实现了 TypeConverter、而 TypeConverter 则 先使用PropertyEditor转换器器转换,如果没找到对应的转换器器,会⽤ConversionService来进⾏行行对象转换。将两者整合起来做转换。


ConditionalConverter


public interface ConditionalConverter {
   boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType);
}
public interface ConditionalGenericConverter extends GenericConverter, ConditionalConverter {
}
复制代码

网络异常,图片无法展示
|

当你实现了 Converter 接口和 ConditionalConverter 接口的时候、在 ConverterAdapter 适配类中会回调你的 matchs 方法


还有一种情况是

当存在多个一样的 sourceType 和 targetType 的转换器时、怎么选择出一个合适的 Converter

private static class ConvertersForPair {
   private final Deque<GenericConverter> converters = new ConcurrentLinkedDeque<>();
   public void add(GenericConverter converter) {
      this.converters.addFirst(converter);
   }
   @Nullable
   public GenericConverter getConverter(TypeDescriptor sourceType, TypeDescriptor targetType) {
      for (GenericConverter converter : this.converters) {
         if (!(converter instanceof ConditionalGenericConverter) ||
               ((ConditionalGenericConverter) converter).matches(sourceType, targetType)) {
            return converter;
         }
      }
      return null;
   }
   @Override
   public String toString() {
      return StringUtils.collectionToCommaDelimitedString(this.converters);
   }
}
复制代码


实际问题


封装 RPC 模块过程中、涉及到各种类型的转换、有 json和 PoJo 的转换、也有 xml 和 PoJo 的转换、更有一些自定义协议报文到 PoJo 的转换、而这些 PoJo 具体是什么类型、在 RPC 的底层模块中是不知道的、只有在运行时通过泛型相关的信息获取的

比如 Json 和 PoJo 之间的转换、可能需要两个转换器、一个是 String 到 PoJo 的、一个是 PoJo 到 String

PoJo 到 String 是比较简单的、这里讨论下 String 到 PoJo。

String 到 PoJo 、这个关系貌似就是 1:N 的关系、貌似使用 ConverterFactory 就可以解决

网络异常,图片无法展示
|

第一个是泛型问题、而且作为 ConverterFactory 需要手动注册到 ConverterRegistry 中、

最终选定 GenericConverter、

@Component
public class JsonStringToObjectConverter implements
        GenericConverter , ConditionalConverter {
    @Autowired
    private ObjectMapper objectMapper;
    @Override
    public Set<ConvertiblePair> getConvertibleTypes() {
         return null;
    }
    @Override
    public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) {
        try {
            if (source instanceof JsonStringMessage) {
                return objectMapper.readValue(((JsonStringMessage) source).getSource(), targetType.getType());
            }else {
                // impossible
                throw new IllegalArgumentException("illegal argument");
            }
        } catch (JsonProcessingException e) {
            throw new IllegalStateException(e);
        }
    }
    @Override
    public boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType) {
        return sourceType.getType().equals(JsonStringMessage.class);
    }
复制代码

getConvertibleTypes 返回 null 的话、必须实现接口 ConditionalConverter

此 Converter 作为 globalConverter

public void add(GenericConverter converter) {
   Set<ConvertiblePair> convertibleTypes = converter.getConvertibleTypes();
   if (convertibleTypes == null) {
      Assert.state(converter instanceof ConditionalConverter,
            "Only conditional converters may return null convertible types");
      this.globalConverters.add(converter);
   }
   else {
      for (ConvertiblePair convertiblePair : convertibleTypes) {
         getMatchableConverters(convertiblePair).add(converter);
      }
   }
}


目录
相关文章
|
6月前
|
JSON 前端开发 Java
解决Spring MVC中No converter found for return value of type异常
在Spring MVC开发中遇到`No converter found for return value of type`异常,通常是因缺少消息转换器、返回值类型不支持或转换器优先级配置错误。解决方案包括:1) 添加对应的消息转换器,如`MappingJackson2HttpMessageConverter`;2) 自定义消息转换器并实现`HttpMessageConverter`接口,设置优先级;3) 修改返回值类型为如`ResponseEntity`的合适类型。通过这些方法可确保返回值正确转换为响应内容。
492 1
|
4月前
|
XML JSON Java
spring,springBoot配置类型转化器Converter以及FastJsonHttpMessageConverter,StringHttpMessageConverter 使用
spring,springBoot配置类型转化器Converter以及FastJsonHttpMessageConverter,StringHttpMessageConverter 使用
579 1
|
机器学习/深度学习 XML 前端开发
【小家Spring】聊聊Spring中的数据转换:Converter、ConversionService、TypeConverter、PropertyEditor(下)
【小家Spring】聊聊Spring中的数据转换:Converter、ConversionService、TypeConverter、PropertyEditor(下)
【小家Spring】聊聊Spring中的数据转换:Converter、ConversionService、TypeConverter、PropertyEditor(下)
|
前端开发 Java Spring
Spring MVC-06循序渐进之Converter和Formatter
Spring MVC-06循序渐进之Converter和Formatter
81 0
|
前端开发 安全 Java
【小家Spring】聊聊Spring中的数据转换:Converter、ConversionService、TypeConverter、PropertyEditor(中)
【小家Spring】聊聊Spring中的数据转换:Converter、ConversionService、TypeConverter、PropertyEditor(中)
【小家Spring】聊聊Spring中的数据转换:Converter、ConversionService、TypeConverter、PropertyEditor(中)
|
机器学习/深度学习 前端开发 Java
【小家Spring】聊聊Spring中的数据转换:Converter、ConversionService、TypeConverter、PropertyEditor(上)
【小家Spring】聊聊Spring中的数据转换:Converter、ConversionService、TypeConverter、PropertyEditor(上)
【小家Spring】聊聊Spring中的数据转换:Converter、ConversionService、TypeConverter、PropertyEditor(上)
|
机器学习/深度学习 安全 Java
Spring Converter 体系(一)
最近封装 RPC 相关的模块,领导说数据转换可以考虑使用 Spring 原有的 Converter 体系。
191 0
|
2月前
|
SQL 监控 druid
springboot-druid数据源的配置方式及配置后台监控-自定义和导入stater(推荐-简单方便使用)两种方式配置druid数据源
这篇文章介绍了如何在Spring Boot项目中配置和监控Druid数据源,包括自定义配置和使用Spring Boot Starter两种方法。
|
24天前
|
人工智能 自然语言处理 前端开发
SpringBoot + 通义千问 + 自定义React组件:支持EventStream数据解析的技术实践
【10月更文挑战第7天】在现代Web开发中,集成多种技术栈以实现复杂的功能需求已成为常态。本文将详细介绍如何使用SpringBoot作为后端框架,结合阿里巴巴的通义千问(一个强大的自然语言处理服务),并通过自定义React组件来支持服务器发送事件(SSE, Server-Sent Events)的EventStream数据解析。这一组合不仅能够实现高效的实时通信,还能利用AI技术提升用户体验。
125 2
|
3月前
|
缓存 Java Maven
Java本地高性能缓存实践问题之SpringBoot中引入Caffeine作为缓存库的问题如何解决
Java本地高性能缓存实践问题之SpringBoot中引入Caffeine作为缓存库的问题如何解决