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

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

前言


前面聊了HttpMessageConverter,它的名称叫消息转换器,所以它面向的是消息体,和Http强相关,所以该接口所在的包为:org.springframework.http.converter


数据转换,顾名思义就是数据类型之间的转换,但是对于数据转换,有的是可以进行转化的,例如字符串转整型,但是有些数据类型之间是不能进行转换的,例如从“aaa”字符串到整型的转换。


不同的框架,肯定都有自己的数据转换的实现,比如MyBatis、Hibernate等这些转换器都是必备的。然后作为这么强大的Spring,它肯定也缺席不了。org.springframework.core.convert.converter.Converter它位于核心包中,所以它不仅仅运用于Spring MVC等web环境,比如spring-jdbc等都是有使用到的~


数据转换在框架设计中是非常重要的一环,它能让你的框架更普适,更通用,更自动化,解决的问题更多,所以我个人认为,了解Spring数据转换的设计思想,以及它的常用实现是非常有必要的。


若是源生Servlet开发,你能想象到那种低下的开发效率吗以及漫天遍地的“垃圾代码”吗?


关于Spring中的数据转换,首先需要了解两大主要分支:


  1. Converter<S, T>:是Spring中最为简单的一个接口。位于包:org.springframework.core.convert.converter。 相关的顶层接口(类)有:ConditionalConverter、GenericConverter、ConverterFactory、ConvertingComparator、ConverterRegistry
  2. ConversionService:用于类型转换的服务接口。这是进入转换系统的入口点。位于包:org.springframework.core.convert。相关的顶层接口(类)有:ConversionService、FormattingConversionService、DefaultConversionService、ConversionServiceFactoryBean、FormattingConversionServiceFactoryBean…

注意各子接口,实现类不一定都是core包里,可能在context包、web包等等~。他俩体系都是@since 3.0


Converter<S, T>


Spring的Converter是可以将一种类型转换成另一种类型的一个对象,它的接口定义非常的的简单。

// 实现此接口的 大都会实现ConditionalConverter
// 请保持线程安全~~
@FunctionalInterface
public interface Converter<S, T> {
  // 把S转成T
  @Nullable
  T convert(S source);
}


Spring提供了3种converter接口,分别是Converter、ConverterFactory和GenericConverter.一般用于1:1, 1:N, N:N的source->target类型转化。


Converter接口 :使用最简单,最不灵活;

ConverterFactory接口 :使用较复杂,比较灵活;

GenericConverter接口 :使用最复杂,也最灵活;


Converter


Converter的实现类举例:该接口Spring内部的实现也非常多,大多数都是以内部类的形式实现(因为它是一个@FunctionalInterface嘛)


// ObjectToStringConverter
final class ObjectToStringConverter implements Converter<Object, String> {
  @Override
  public String convert(Object source) {
    return source.toString();
  }
}


// StringToCharsetConverter  @since 4.2
  @Override
  public Charset convert(String source) {
    return Charset.forName(source);
  }
// StringToPropertiesConverter
  @Override
  public Properties convert(String source) {
    try {
      Properties props = new Properties();
      // Must use the ISO-8859-1 encoding because Properties.load(stream) expects it.
      props.load(new ByteArrayInputStream(source.getBytes(StandardCharsets.ISO_8859_1)));
      return props;
    }catch (Exception ex) {
      // Should never happen.
      throw new IllegalArgumentException("Failed to parse [" + source + "] into Properties", ex);
    }
  }
// StringToTimeZoneConverter @since 4.2
  @Override
  public TimeZone convert(String source) {
    return StringUtils.parseTimeZoneString(source);
  }
//ZoneIdToTimeZoneConverter @since 4.0
  @Override
  public TimeZone convert(ZoneId source) {
    return TimeZone.getTimeZone(source);
  }
// StringToBooleanConverter  这个转换器很有意思  哪些代表true,哪些代表fasle算是业界的一个规范了
// 这就是为什么,我们给传值1也会被当作true来封装进Boolean类型的根本原因所在~
  static {
    trueValues.add("true");
    trueValues.add("on");
    trueValues.add("yes");
    trueValues.add("1");
    falseValues.add("false");
    falseValues.add("off");
    falseValues.add("no");
    falseValues.add("0");
  }
// StringToUUIDConverter  @since 3.2
  @Override
  public UUID convert(String source) {
    return (StringUtils.hasLength(source) ? UUID.fromString(source.trim()) : null);
  }
// StringToLocaleConverter
  @Override
  @Nullable
  public Locale convert(String source) {
    return StringUtils.parseLocale(source);
  }
// SerializingConverter:把任意一个对象,转换成byte[]数组,唯独这一个是public的,其它的都是Spring内置的
public class SerializingConverter implements Converter<Object, byte[]> {
  // 序列化器:DefaultSerializer   就是new ObjectOutputStream(outputStream).writeObject(object)
  // 就是简单的把对象写到输出流里~~
  private final Serializer<Object> serializer;
  public SerializingConverter() {
    this.serializer = new DefaultSerializer();
  }
  public SerializingConverter(Serializer<Object> serializer) { // 自己亦可指定实现。
    Assert.notNull(serializer, "Serializer must not be null");
    this.serializer = serializer;
  }
  @Override
  public byte[] convert(Object source) {
    ByteArrayOutputStream byteStream = new ByteArrayOutputStream(1024);
    try  {
      this.serializer.serialize(source, byteStream);
      // 把此输出流转为byte[]数组~~~~~~
      return byteStream.toByteArray();
    } catch (Throwable ex) {
      throw new SerializationFailedException("Failed to serialize object using " +
          this.serializer.getClass().getSimpleName(), ex);
    }
  }
}


Converter接口非常的简单,所以除了SerializingConverter一个是外部类,我们可以拿来使用外,其余的都是Spring内部自己使用的。从此可以看出:此接口一般也用于我们自己去实现,即:自定义数据转换器。


自定义转换器的一个Demo:

// 把形如这样的字符串:  "fsx:18" 转换为Person对象
public class PersonConverter implements Converter<String, Person> {
    @Override
    public Person convert(String source) {
        if (StringUtils.isEmpty(source)) {
            return null;
        }
        String[] strings = StringUtils.delimitedListToStringArray(source, ":");
        Person person = new Person();
        person.setName(strings[0]);
        person.setAge(Integer.valueOf(strings[1]));
        return person;
    }
    public static void main(String[] args) {
        PersonConverter personConverter = new PersonConverter();
        System.out.println(personConverter.convert("fsx:18")); //Person{name='fsx', age=18}
    }
}

Converter接口非常的简单,所以除了SerializingConverter一个是外部类,我们可以拿来使用外,其余的都是Spring内部自己使用的。从此可以看出:此接口一般也用于我们自己去实现,即:自定义数据转换器。


自定义转换器的一个Demo:


// 把形如这样的字符串:  "fsx:18" 转换为Person对象
public class PersonConverter implements Converter<String, Person> {
    @Override
    public Person convert(String source) {
        if (StringUtils.isEmpty(source)) {
            return null;
        }
        String[] strings = StringUtils.delimitedListToStringArray(source, ":");
        Person person = new Person();
        person.setName(strings[0]);
        person.setAge(Integer.valueOf(strings[1]));
        return person;
    }
    public static void main(String[] args) {
        PersonConverter personConverter = new PersonConverter();
        System.out.println(personConverter.convert("fsx:18")); //Person{name='fsx', age=18}
    }
}


备注:在Spring内部消息转换器的注册、使用一般都结合ConversionService这个接口


ConditionalConverter


根据source和target来做条件判断,从而可以判断哪个转换器生效,哪个不生效之类的。


// @since 3.2   出现稍微较晚
public interface ConditionalConverter {
  boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType);
}


org.springframework.core.convert.TypeDescriptor也是一个Spring的基础类(类似ResolvableType)这种,若有需要我们平时也可以使用它。 它能够把基础类型、MethodParameter、Field、org.springframework.core.convert.Property、Class等都描述进来。并且提供如下非常方便方法:


// @since 3.0
public class TypeDescriptor implements Serializable {
  public Class<?> getType() {
    return this.type;
  }
  public ResolvableType getResolvableType() {
    return this.resolvableType;
  }
  public Object getSource() {
    return this.resolvableType.getSource();
  }
  public String getName();
  public boolean isPrimitive();
  public Annotation[] getAnnotations();
  public boolean hasAnnotation(Class<? extends Annotation> annotationType);
  public <T extends Annotation> T getAnnotation(Class<T> annotationType);
  public boolean isAssignableTo(TypeDescriptor typeDescriptor);
  public boolean isCollection();
  public boolean isArray();
  public boolean isMap();
  public TypeDescriptor getMapKeyTypeDescriptor();
  public TypeDescriptor getMapValueTypeDescriptor()
  // 静态方法:可吧基础类型、任意一个class类型转为这个描述类型  依赖于下面的valueOf方法  source为null  返回null
  public static TypeDescriptor forObject(@Nullable Object source);
  public static TypeDescriptor valueOf(@Nullable Class<?> type);
  // 把集合转为描述类型~
  public static TypeDescriptor collection(Class<?> collectionType, @Nullable TypeDescriptor elementTypeDescriptor)
  public static TypeDescriptor map(Class<?> mapType, @Nullable TypeDescriptor keyTypeDescriptor, @Nullable TypeDescriptor valueTypeDescriptor);
  public static TypeDescriptor array(@Nullable TypeDescriptor elementTypeDescriptor);
  public static TypeDescriptor nested(MethodParameter methodParameter, int nestingLevel);
  public static TypeDescriptor nested(Field field, int nestingLevel);
  public static TypeDescriptor nested(Property property, int nestingLevel);
}


ConditionalConverter的继承树:


image.png

ConditionalGenericConverter这个子接口,就是把GenericConverter和ConditionalConverter联合起来了。而GenericConverter我们上面提到了,它一般用于处理N:N的转换,因此它的子类们放在下面讲会更合适~


NumberToNumberConverterFactory:它是个ConverterFactory,所以也放下面

AbstractConditionalEnumConverter:枚举类型的转换


// @since 4.3  也是只能Spring内部自己用的
abstract class AbstractConditionalEnumConverter implements ConditionalConverter {
  // 它借助了ConversionService这个接口  需要外部自定义转换逻辑~~
  private final ConversionService conversionService;
  protected AbstractConditionalEnumConverter(ConversionService conversionService) {
    this.conversionService = conversionService;
  }
  @Override
  public boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType) {
    // 拿到source所有实现的接口  若没有实现任何接口,永远返回true
    for (Class<?> interfaceType : ClassUtils.getAllInterfacesForClassAsSet(sourceType.getType())) {
      // 最终是委托给conversionService去做这件事了~~~~
      if (this.conversionService.canConvert(TypeDescriptor.valueOf(interfaceType), targetType)) {
        return false;
      }
    }
    return true;
  }
}


它的两个子类实现:EnumToIntegerConverter和EnumToStringConverter就是调用了source.ordinal()和source.name()。若你想要实现自己的枚举自定义属性的转换,其实是可以继承AbstractConditionalEnumConverter它的,但是Spring并没有公开它,so~~~你还是自己写吧


ConverterFactory


ConverterFactory:range范围转换器的工厂:可以将对象从S转换为R的子类型(1:N)

public interface ConverterFactory<S, R> {
  //Get the converter to convert from S to target type T, where T is also an instance of R
  <T extends R> Converter<S, T> getConverter(Class<T> targetType);
}


它的实现类不多:


image.png


final class IntegerToEnumConverterFactory implements ConverterFactory<Integer, Enum> {
  // ConversionUtils.getEnumType表示拿出枚举的class类型
  @Override
  public <T extends Enum> Converter<Integer, T> getConverter(Class<T> targetType) {
    return new IntegerToEnum(ConversionUtils.getEnumType(targetType));
  }
  // 内部类的实现  把Integer转为Enum的子类型~~~   相当于根据integer找到一个enum(注意此处根据角标来找的)
  private class IntegerToEnum<T extends Enum> implements Converter<Integer, T> {
    private final Class<T> enumType;
    public IntegerToEnum(Class<T> enumType) {
      this.enumType = enumType;
    }
    @Override
    public T convert(Integer source) {
      return this.enumType.getEnumConstants()[source];
    }
  }
}
// StringToEnumConverterFactory  大体同上  return (T) Enum.valueOf(this.enumType, source.trim())
...


该工厂就是用来创建一个converter,把目标类型转换成子类型,所以它是1->N的。注意:Spring内置的实现也都是外部不可访问的

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