【小家Spring】聊聊Spring中的数据转换:Converter、ConversionService、TypeConverter、PropertyEditor(中)

简介: 【小家Spring】聊聊Spring中的数据转换:Converter、ConversionService、TypeConverter、PropertyEditor(中)

GenericConverter


用于在两个或多个类型之间转换的通用转换器接口。这是最灵活的转换器SPI接口,也是最复杂的

灵活是因为它一个转换器就能转换多个s/t,所以它是N->N的。实现类们一般情况下也会实现接口:ConditionalConverter


1个GenericConverter支持转化的所有类型都写在了属性Set内


public interface GenericConverter {
  @Nullable
  Set<ConvertiblePair> getConvertibleTypes();
  @Nullable
  Object convert(@Nullable Object source, TypeDescriptor sourceType, TypeDescriptor targetType);
  /**
   * Holder for a source-to-target class pair.
   */
   // 包含有一对  s和t
  final class ConvertiblePair {
    private final Class<?> sourceType;
    private final Class<?> targetType;
    public ConvertiblePair(Class<?> sourceType, Class<?> targetType) {
      Assert.notNull(sourceType, "Source type must not be null");
      Assert.notNull(targetType, "Target type must not be null");
      this.sourceType = sourceType;
      this.targetType = targetType;
    }
    ... // 去掉get/set方法  以及toString equals等基础方法
  }
}


它的实现类都是子接口ConditionalGenericConverter的实现类(就是GenericConverter和ConditionalConverter的结合).


注意:Spring的所有内部实现,依旧全部未公开,因此本文只举例说明一下即可。


final class ArrayToObjectConverter implements ConditionalGenericConverter {
  // 借助了ConversionService 
  private final ConversionService conversionService;
  public ArrayToObjectConverter(ConversionService conversionService) {
    this.conversionService = conversionService;
  }
  // 残暴:都是object
  @Override
  public Set<ConvertiblePair> getConvertibleTypes() {
    return Collections.singleton(new ConvertiblePair(Object[].class, Object.class));
  }
  // 实现ConditionalConverter的方法,最终是委托给了ConversionService#canConvert方法
  @Override
  public boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType) {
    return ConversionUtils.canConvertElements(sourceType.getElementTypeDescriptor(), targetType, this.conversionService);
  }
  ...
}
// 这里的转换器,都和数组、集合有关,比如:
// StringToCollectionConverter、CollectionToArrayConverter、CollectionToStringConverter
// StringToArrayConverter、StreamConverter、CollectionToArrayConverter等等


特别说一句:这里有一个非常有意思的转换器:IdToEntityConverter,SpringMVC默认给我们这已经注册进去了,在Spring MVC自定义常用的、通用的Controller的时候,我们会借助它实现通用方案,让controller异常的方便,好使~~~暂时可先参考:路由id转化为控制器Entity参数


ConverterRegistry


使用ConverterRegistry可以使我们对类型转换器做一个统一的注册。正如前言所说的,要实现自己的类型转换逻辑我们可以实现Converter接口、ConverterFactory接口和GenericConverter接口,ConverterRegistry接口就分别为这三种类型提供了对应的注册方法,至于里面的逻辑就可以发挥自己的设计能力进行设计实现了。


通过ConverterAdapter或者ConverterFactoryAdapter最后都会转化成GenericConverter,我想应该是因为这种converter是最通用的原因吧


一般而言:我们在实现ConversionService接口的时候也会实现ConverterRegistry接口

// @since 3.0  Converter 注册处,用于存储 Converter 实例
public interface ConverterRegistry {
  void addConverter(Converter<?, ?> converter);
  // 添加一个 Converter 实例,并指定其源和目标类型
  <S, T> void addConverter(Class<S> sourceType, Class<T> targetType, Converter<? super S, ? extends T> converter);
  void addConverter(GenericConverter converter);
  void addConverterFactory(ConverterFactory<?, ?> factory);
  // 移除方法只有一个:它是面向s和t来做移除的~~~~ 删除所有匹配指定源和目标类型的 Converter
  // Remove any converters from {@code sourceType} to {@code targetType}
  void removeConvertible(Class<?> sourceType, Class<?> targetType);
}


image.png


两大分支。FormatterRegistry用于注册格式化器,下面再说

ConfigurableConversionService:它就是把ConversionService和ConverterRegistry绑定在一起,自己并不提供新接口


// @since 3.1
public interface ConfigurableConversionService extends ConversionService, ConverterRegistry {
}



所以它的具体内容,放到ConversionService里描述吧。


ConversionService


用于类型转换的服务接口。这是转换系统的**入口点**。请保证它convert方法的线程安全,这个接口非常的重要。


举个例子,使用Environment的<T> T getProperty(String key, Class<T> targetType)这里的类型转换,就是要通过ConversionService来完成的。


// @since 3.0
public interface ConversionService {
  // 特别说明:若是Map、集合、数组转换时。即使下面方法convert转换抛出了异常,这里也得返回true  因为Spring希望调用者处理这个异常:ConversionException
  boolean canConvert(@Nullable Class<?> sourceType, Class<?> targetType);
  boolean canConvert(@Nullable TypeDescriptor sourceType, TypeDescriptor targetType);
  // 注意此处:转换的source都是对象,target只需要类型即可~~~
  @Nullable
  <T> T convert(@Nullable Object source, Class<T> targetType);
  @Nullable
  Object convert(@Nullable Object source, @Nullable TypeDescriptor sourceType, TypeDescriptor targetType);
}


image.png


GenericConversionService


它也并不是一个抽象类,它是一个通用的处理。但是一般不会直接使用它,而是使用它的更具体的子类


// @since 3.0  实现了接口ConversionService和ConverterRegistry  
public class GenericConversionService implements ConfigurableConversionService {
  // 啥都不做,但是呢conversion is not required,相当于占位的意思
  private static final GenericConverter NO_OP_CONVERTER = new NoOpConverter("NO_OP");
  // 当转换器缓存中没有任何匹配时,它上场
  // 请不要把它直接return,用null代替返回
  private static final GenericConverter NO_MATCH = new NoOpConverter("NO_MATCH");
  // 说明:Converter是一个静态内部类 它会Manages all converters registered with the service
  private final Converters converters = new Converters();
  // 缓存转换器。用的ConcurrentReferenceHashMap是Spring自己实现的一个软引用/弱引用的Map
  private final Map<ConverterCacheKey, GenericConverter> converterCache = new ConcurrentReferenceHashMap<>(64);
  // 仅有一个空构造函数,构造函数内啥都没做
  @Override
  public void addConverter(Converter<?, ?> converter) {
    // 这个处理很有意思:getRequiredTypeInfo    拿到两个泛型参数类型(若没有指定泛型  返回的是null)
    ResolvableType[] typeInfo = getRequiredTypeInfo(converter.getClass(), Converter.class);
    // Decorate和Proxy模式的区别。Decorate模式可用于函数防抖   Proxy模式就是我们常用的代理模式
    if (typeInfo == null && converter instanceof DecoratingProxy) {
      typeInfo = getRequiredTypeInfo(((DecoratingProxy) converter).getDecoratedClass(), Converter.class);
    }
    // 由此可见这个转换器的泛型类型是必须的~~~
    if (typeInfo == null) {
      throw new IllegalArgumentException("Unable to determine source type <S> and target type <T> for your " +
          "Converter [" + converter.getClass().getName() + "]; does the class parameterize those types?");
    }
    // ConverterAdapter是个GenericConverter。由此课件最终都是转换成了GenericConverter类型
    addConverter(new ConverterAdapter(converter, typeInfo[0], typeInfo[1]));
  }
  @Override
  public <S, T> void addConverter(Class<S> sourceType, Class<T> targetType, Converter<? super S, ? extends T> converter) {
    addConverter(new ConverterAdapter(converter, ResolvableType.forClass(sourceType), ResolvableType.forClass(targetType)));
  }
  // 最终都是转换成了GenericConverter  进行转换器的保存  全部放在Converters里保存着
  @Override
  public void addConverter(GenericConverter converter) {
    this.converters.add(converter);
    invalidateCache(); // 清空缓存
  }
  // 使用ConverterFactoryAdapter转换成GenericConverter
  @Override
  public void addConverterFactory(ConverterFactory<?, ?> factory) { ... }
  // 注意ConvertiblePair是重写了equals方法和hash方法的
  @Override
  public void removeConvertible(Class<?> sourceType, Class<?> targetType) {
    this.converters.remove(sourceType, targetType);
    invalidateCache();
  }
  // 主要是getConverter() 方法  相当于只有有转换器匹配,就是能够被转换的
  @Override
  public boolean canConvert(@Nullable TypeDescriptor sourceType, TypeDescriptor targetType) {
    Assert.notNull(targetType, "Target type to convert to cannot be null");
    if (sourceType == null) {
      return true;
    }
    GenericConverter converter = getConverter(sourceType, targetType);
    return (converter != null);
  }
  @Nullable
  protected GenericConverter getConverter(TypeDescriptor sourceType, TypeDescriptor targetType) {
    ConverterCacheKey key = new ConverterCacheKey(sourceType, targetType);
    GenericConverter converter = this.converterCache.get(key);
    // 这个处理:如果缓存有值   但是为NO_MATCH 那就返回null,而不是把No_Match直接return
    if (converter != null) {
      return (converter != NO_MATCH ? converter : null);
    }
    converter = this.converters.find(sourceType, targetType);
    if (converter == null) {
      converter = getDefaultConverter(sourceType, targetType);
    }
    // 如果默认的不为null 也可以return的
    // NO_OP_CONVERTER还是可以return的~~~
    if (converter != null) {
      this.converterCache.put(key, converter);
      return converter;
    }
    this.converterCache.put(key, NO_MATCH);
    return null;
  }
  @Nullable
  protected GenericConverter getDefaultConverter(TypeDescriptor sourceType, TypeDescriptor targetType) {
    return (sourceType.isAssignableTo(targetType) ? NO_OP_CONVERTER : null);
  }
  // 拿到泛型类型们
  @Nullable
  private ResolvableType[] getRequiredTypeInfo(Class<?> converterClass, Class<?> genericIfc) {
    ResolvableType resolvableType = ResolvableType.forClass(converterClass).as(genericIfc);
    ResolvableType[] generics = resolvableType.getGenerics();
    if (generics.length < 2) {
      return null;
    }
    Class<?> sourceType = generics[0].resolve();
    Class<?> targetType = generics[1].resolve();
    if (sourceType == null || targetType == null) {
      return null;
    }
    return generics;
  }
  ...
}


image.png


绝大多数情况下,我们不会直接使用GenericConversionService,而是使用它的子类DefaultConversionService


DefaultConversionService


它能适用于绝大多数的场景中。

// @since 3.1
public class DefaultConversionService extends GenericConversionService {
  // @since 4.3.5 改变量出现得还是比较晚的
  @Nullable
  private static volatile DefaultConversionService sharedInstance;
  // 空构造,那就注册到自己this身上~~~因为自己也是个ConverterRegistry
  public DefaultConversionService() {
    addDefaultConverters(this);
  }
  // 就是把sharedInstance返回出去~~~(永远不可能返回null)
  public static ConversionService getSharedInstance() { ... }
  // 默认情况下,这个ConversionService注册的转换器们~~~~  几乎涵盖了所有~~~~
  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));
  }
  ...
}


从源码可以看出,它几乎覆盖注册了所有的通用的类型转换,若涉及到自定义的对象的转换,亦可自己自定义转换器。


备注:DefaultConversionService它在PropertyResolver、org.springframework.jdbc.core.RowMapper、org.springframework.expression.TypeConverter…也就是properties、el表达式里、spring-jdbc数据封装的类型转换里都有应用


关于FormattingConversionService,它和格式化有关,所以放在Formatter章节里了,可参考:

【小家Spring】聊聊Spring中的格式化:Formatter、AnnotationFormatterFactory、DateFormatter以及@DateTimeFormat…

ConversionServiceFactoryBean


它是我们自定义转换器的一个入口。比如之前我们见过这么配置的自定义转换器:


  <bean id="conversionService" class="org.springframework.context.support.ConversionServiceFactoryBean">
    <property name="converters">
      <set>
        <ref bean="userConverter"/>
      </set>
    </property>
  </bean>


这样,我们的自定义的转换器userConverter就被添加进去了。我们在Spring MVC中需要自定义转换器的时候,也是这么来弄的。(使用java配置的方式添加,此处省略)


它的源码比较简单:


public class ConversionServiceFactoryBean implements FactoryBean<ConversionService>, InitializingBean {
  // 保存着我们diy set捡来的转换器们
  @Nullable
  private Set<?> converters;
  // 最终是一个DefaultConversionService,然后向里添加自定义的转换器~
  @Nullable
  private GenericConversionService conversionService;
  // Bean初始化结束后,注册自定义的转换器进去~~
  @Override
  public void afterPropertiesSet() {
    this.conversionService = createConversionService();
    ConversionServiceFactory.registerConverters(this.converters, this.conversionService);
  }
  protected GenericConversionService createConversionService() {
    return new DefaultConversionService();
  }
  @Override
  @Nullable
  public ConversionService getObject() {
    return this.conversionService;
  }
  // 最终是个GenericConversionService,实际是个DefaultConversionService
  @Override
  public Class<? extends ConversionService> getObjectType() {
    return GenericConversionService.class;
  }
  @Override
  public boolean isSingleton() {
    return true;
  }
}


另外,如果你还需要格式化的功能,使用FormattingConversionServiceFactoryBean代替即可


Spring中的PropertyEditor属性编辑器


在文末稍微介绍一下Spring中的PropertyEditor属性编辑器,因为它和类型转换器特别的像。


PropertyEditor是JavaBean规范定义的接口,这是java.beans中一个接口,其设计的意图是图形化编程上,方便对象与String之间的转换工作,而spring将其扩展,方便各种对象与String之间的转换工作。

Spring所有的扩展都是通过继承PropertyEditorSupport,因为它只聚焦于转换上,所以只需复写setAsText()、getAsText()以及构造方法即可实现扩展。


Spring 使用PropertyEditors的接口来实现对象和字符串之间的转换,比如将 2007-14-09转化为日期类型等,可以通过注册自定义编辑器来实现此功能


下面贴出Spring内置的一些属性编辑器们:

image.png


这些PropertyEditors都位于org.springframework.beans.propertyeditors包中,大多是都是由BeanWrapperImpl注册,当属性编辑器以某种方式进行配置时,开发者仍可以注册自定义的变体用于覆盖默认的变量

相关文章
|
6月前
|
人工智能 运维 Java
Spring AI Alibaba Admin 开源!以数据为中心的 Agent 开发平台
Spring AI Alibaba Admin 正式发布!一站式实现 Prompt 管理、动态热更新、评测集构建、自动化评估与全链路可观测,助力企业高效构建可信赖的 AI Agent 应用。开源共建,现已上线!
6890 89
|
6月前
|
NoSQL Java 数据库连接
《深入理解Spring》Spring Data——数据访问的统一抽象与极致简化
Spring Data通过Repository抽象和方法名派生查询,简化数据访问层开发,告别冗余CRUD代码。支持JPA、MongoDB、Redis等多种存储,统一编程模型,提升开发效率与架构灵活性,是Java开发者必备利器。(238字)
|
JSON Java 数据格式
微服务——SpringBoot使用归纳——Spring Boot返回Json数据及数据封装——封装统一返回的数据结构
本文介绍了在Spring Boot中封装统一返回的数据结构的方法。通过定义一个泛型类`JsonResult&lt;T&gt;`,包含数据、状态码和提示信息三个属性,满足不同场景下的JSON返回需求。例如,无数据返回时可设置默认状态码&quot;0&quot;和消息&quot;操作成功!&quot;,有数据返回时也可自定义状态码和消息。同时,文章展示了如何在Controller中使用该结构,通过具体示例(如用户信息、列表和Map)说明其灵活性与便捷性。最后总结了Spring Boot中JSON数据返回的配置与实际项目中的应用技巧。
924 0
|
JSON Java fastjson
微服务——SpringBoot使用归纳——Spring Boot返回Json数据及数据封装——使用 fastJson 处理 null
本文介绍如何使用 fastJson 处理 null 值。与 Jackson 不同,fastJson 需要通过继承 `WebMvcConfigurationSupport` 类并覆盖 `configureMessageConverters` 方法来配置 null 值的处理方式。例如,可将 String 类型的 null 转为 &quot;&quot;,Number 类型的 null 转为 0,避免循环引用等。代码示例展示了具体实现步骤,包括引入相关依赖、设置序列化特性及解决中文乱码问题。
629 0
|
JSON Java fastjson
微服务——SpringBoot使用归纳——Spring Boot返回Json数据及数据封装——Spring Boot 默认对Json的处理
本文介绍了在Spring Boot中返回Json数据的方法及数据封装技巧。通过使用`@RestController`注解,可以轻松实现接口返回Json格式的数据,默认使用的Json解析框架是Jackson。文章详细讲解了如何处理不同数据类型(如类对象、List、Map)的Json转换,并提供了自定义配置以应对null值问题。此外,还对比了Jackson与阿里巴巴FastJson的特点,以及如何在项目中引入和配置FastJson,解决null值转换和中文乱码等问题。
1674 0
|
9月前
|
JSON Java 数据格式
Spring Boot返回Json数据及数据封装
在Spring Boot中,接口间及前后端的数据传输通常使用JSON格式。通过@RestController注解,可轻松实现Controller返回JSON数据。该注解是Spring Boot新增的组合注解,结合了@Controller和@ResponseBody的功能,默认将返回值转换为JSON格式。Spring Boot底层默认采用Jackson作为JSON解析框架,并通过spring-boot-starter-json依赖集成了相关库,包括jackson-databind、jackson-datatype-jdk8等常用模块,简化了开发者对依赖的手动管理。
794 3
|
人工智能 安全 Dubbo
Spring AI 智能体通过 MCP 集成本地文件数据
MCP 作为一款开放协议,直接规范了应用程序如何向 LLM 提供上下文。MCP 就像是面向 AI 应用程序的 USB-C 端口,正如 USB-C 提供了一种将设备连接到各种外围设备和配件的标准化方式一样,MCP 提供了一个将 AI 模型连接到不同数据源和工具的标准化方法。
7554 106
|
存储 运维 Java
使用 @Audited 增强Spring Boot 应用程序的数据审计能力
【7月更文挑战第19天】在Spring Boot应用中使用`@Audited`增强数据审计能力涉及在相关实体或方法上添加该注解以标记需审计的操作。例如,在`User`类的`updateUser`方法上使用`@Audited`可记录更新操作的详情。此外,还需配置审计日志存储方式(如数据库)及事件过滤规则等。这有助于满足合规性需求、故障排查及数据分析,对数据安全和完整至关重要。
323 1
|
存储 NoSQL Java
使用Java和Spring Data构建数据访问层
本文介绍了如何使用 Java 和 Spring Data 构建数据访问层的完整过程。通过创建实体类、存储库接口、服务类和控制器类,实现了对数据库的基本操作。这种方法不仅简化了数据访问层的开发,还提高了代码的可维护性和可读性。通过合理使用 Spring Data 提供的功能,可以大幅提升开发效率。
342 21
|
存储 Java API
如何使用 Java 记录简化 Spring Data 中的数据实体
如何使用 Java 记录简化 Spring Data 中的数据实体
235 9