【小家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注册,当属性编辑器以某种方式进行配置时,开发者仍可以注册自定义的变体用于覆盖默认的变量

相关文章
|
3月前
|
存储 运维 Java
使用 @Audited 增强Spring Boot 应用程序的数据审计能力
【7月更文挑战第19天】在Spring Boot应用中使用`@Audited`增强数据审计能力涉及在相关实体或方法上添加该注解以标记需审计的操作。例如,在`User`类的`updateUser`方法上使用`@Audited`可记录更新操作的详情。此外,还需配置审计日志存储方式(如数据库)及事件过滤规则等。这有助于满足合规性需求、故障排查及数据分析,对数据安全和完整至关重要。
|
1月前
|
存储 Java API
如何使用 Java 记录简化 Spring Data 中的数据实体
如何使用 Java 记录简化 Spring Data 中的数据实体
36 9
|
1月前
|
JSON 前端开发 Java
【Spring】“请求“ 之传递 JSON 数据
【Spring】“请求“ 之传递 JSON 数据
87 2
|
3月前
|
JSON Java API
哇塞!Spring Boot 中的 @DateTimeFormat 和 @JsonFormat,竟能引发数据时间大变革!
【8月更文挑战第29天】在Spring Boot开发中,正确处理日期时间至关重要。
69 1
|
3月前
|
XML JSON Java
使用IDEA+Maven搭建整合一个Struts2+Spring4+Hibernate4项目,混合使用传统Xml与@注解,返回JSP视图或JSON数据,快来给你的SSH老项目翻新一下吧
本文介绍了如何使用IntelliJ IDEA和Maven搭建一个整合了Struts2、Spring4、Hibernate4的J2EE项目,并配置了项目目录结构、web.xml、welcome.jsp以及多个JSP页面,用于刷新和学习传统的SSH框架。
90 0
使用IDEA+Maven搭建整合一个Struts2+Spring4+Hibernate4项目,混合使用传统Xml与@注解,返回JSP视图或JSON数据,快来给你的SSH老项目翻新一下吧
|
3月前
|
JSON 前端开发 Java
Spring MVC返回JSON数据
综上所述,Spring MVC提供了灵活、强大的方式来支持返回JSON数据,从直接使用 `@ResponseBody`及 `@RestController`注解,到通过配置消息转换器和异常处理器,开发人员可以根据具体需求选择合适的实现方式。
166 4
|
3月前
|
Java Spring 开发者
掌握Spring事务管理,打造无缝数据交互——实用技巧大公开!
【8月更文挑战第31天】在企业应用开发中,确保数据一致性和完整性至关重要。Spring框架提供了强大的事务管理机制,包括`@Transactional`注解和编程式事务管理,简化了事务处理。本文深入探讨Spring事务管理的基础知识与高级技巧,涵盖隔离级别、传播行为、超时时间等设置,并介绍如何使用`TransactionTemplate`和`PlatformTransactionManager`进行编程式事务管理。通过合理设计事务范围和选择合适的隔离级别,可以显著提高应用的稳定性和性能。掌握这些技巧,有助于开发者更好地应对复杂业务需求,提升应用质量和可靠性。
46 0
|
4月前
|
XML JSON Java
spring,springBoot配置类型转化器Converter以及FastJsonHttpMessageConverter,StringHttpMessageConverter 使用
spring,springBoot配置类型转化器Converter以及FastJsonHttpMessageConverter,StringHttpMessageConverter 使用
638 1
|
3月前
|
存储 Java 数据库
使用 @Audited 增强Spring Boot 应用程序的数据审计能力
【8月更文挑战第3天】在Spring Boot应用中,`@Audited`注解能显著提升数据审计能力。它可用于标记需审计的方法或类,记录操作用户、时间和类型等信息。此注解支持与Logback或Log4j等日志框架集成,亦可将审计信息存入数据库,便于后续分析。此外,还支持自定义审计处理器以满足特定需求。
151 0
|
4月前
|
存储 Java 数据库
如何在Spring Boot中实现多租户数据隔离
如何在Spring Boot中实现多租户数据隔离