[spring core] type conversion mechanism

Type conversion is essentially a process:
SourceType --> TargetType

Spring provides a set of SPI(Server Provide Interface) mechanism based on Converter interface.

By implementing the Converter interface, we can formulate specific type conversion rules according to our business needs.

1 Converter interface

org.springframework.core.convert.converter.Converter:

public interface Converter<S, T> {

   @Nullable
   T convert(S source);

   default <U> Converter<S, U> andThen(Converter<? super T, ? extends U> after) {
      Assert.notNull(after, "After Converter must not be null");
      return (S s) -> {
         T initialResult = convert(s);
         return (initialResult != null ? after.convert(initialResult) : null);
      };
   }
}

Generic interpretation:

  • S: Original type.
  • T: Target type.

Method interpretation:

  • convert(): converts data from S type to T type.
  • andThen(): chained type conversion, S → T → U.

For example, if we want to implement an Object → String type conversion, we can follow the following steps:

  1. Implement the Converter interface, and specify that generic S is Object and generic T is String;
  2. Implement the convert() method and write conversion rules.
public class ObjectToStringConverter implements Converter<Object, String> {
    @Override
    public String convert(Object source) {
        return source.toString();
    }
}

In Spring, you can call it in the following ways:

  1. Create ObjectToStringConverter, which is managed by the container:
@Configuration
public class AppConfig {
    @Bean
    public Converter converter() {
        return new ObjectToStringConverter();
    }
}
  1. Inject the Converter instance into the business:
@Service
public class AppService {
    @Resource
    private Converter converter;

    public void service() {
        Date source = new Date();
        Object target = converter.convert(source);
        System.out.println(target.getClass());
        System.out.println(target);
    }
}
  1. After the service() method is called by the outside world, the following contents will be output, indicating that Date is successfully converted to String:
class java.lang.String
Thu Jan 13 21:33:49 CST 2022

Spring's type conversion mechanism is essentially such a routine. By implementing the Converter interface, you can implement different type conversion rules, which are applicable to different business scenarios.

Of course, Spring also implements many commonly used xxconverters. When you need type conversion, you can first see whether the inheritance level of the Converter can be used directly.

2 ConverterFactory interface

In order to manage a series of converters with hierarchical relationships, Spring also provides the ConverterFactory interface.

org.springframework.core.convert.converter.ConverterFactory:

public interface ConverterFactory<S, R> {
   <T extends R> Converter<S, T> getConverter(Class<T> targetType);
}

Generic interpretation:

  • S: Original type.
  • R: The top-level parent / interface of the target type.
  • T: The specific target type is a subclass / implementation class of R.

Method interpretation:

  • getConverter(): get the Converter implementation class with target type T.

For example, if there are the following business requirements:

  1. Charater → Integer
  2. Charater → Float
  3. Charater → Long
  4. ......

The target types are subclasses of Number. We can implement the following ConverterFactory:

public class CharacterToNumberFactory implements ConverterFactory<Character, Number> {

   @Override
   public <T extends Number> Converter<Character, T> getConverter(Class<T> targetType) {
      return new CharacterToNumber<>(targetType);
   }

   private static final class CharacterToNumber<T extends Number> implements Converter<Character, T> {

      private final Class<T> targetType;

      public CharacterToNumber(Class<T> targetType) {
         this.targetType = targetType;
      }

      @Override
      public T convert(Character source) {
         return NumberUtils.convertNumberToTargetClass((short) source.charValue(), this.targetType);
      }
   }

}

In Spring, you can call it in the following ways:

  1. Create a CharacterToNumberFactory, which is managed by the container:
@Configuration
public class AppConfig {
    @Bean
    public ConverterFactory<Character, Number> converter() {
        return new CharacterToNumberFactory();
    }
}
  1. Inject the ConverterFactory instance into the business:
@Service
public class AppService {
    @Resource
    private ConverterFactory converterFactory;

    public void service() {
        Character character = new Character('a');
        Converter integerConverter = converterFactory.getConverter(Integer.class);
        Object target = integerConverter.convert(character);
        System.out.println(target.getClass());
        System.out.println(target);
        Converter doubleConverter = converterFactory.getConverter(Double.class);
        target = doubleConverter.convert(character);
        System.out.println(target.getClass());
        System.out.println(target);
    }
}
  1. After the service() method is called by the outside world, the following contents will be output, indicating that Date is successfully converted to String:
class java.lang.Integer
97
class java.lang.Double
97.0

Therefore, in essence, ConverterFactory is equivalent to managing a series of Converter factories with special hierarchical relationships, and is equivalent to the upgraded version of Converter.

When you need to convert the same S type into multiple T with hierarchical relationship, you can give priority to implementing ConverterFactory.

3 GenericConverter interface

org. springframework. core. convert. converter. The genericconverter interface specifies sourceType and targetType in the form of ConvertiblePair:

public interface GenericConverter {

   Set<ConvertiblePair> getConvertibleTypes();

   Object convert(@Nullable Object source, TypeDescriptor sourceType, TypeDescriptor targetType);

   final class ConvertiblePair {
      private final Class<?> sourceType;
      private final Class<?> targetType;
   }
}

GenericConverter does not fix the number and types of sourceType and targetType in the implementation class. Compared with the Converter interface, GenericConverter has greater flexibility, but it is also more complex.

In the convert() method, you need to judge according to the types of sourceType and targetType and execute the corresponding conversion logic.

TypeDescriptor is used to describe the type information of sourceType and targetType:

public class TypeDescriptor implements Serializable {
    private final Class<?> type;
    private final ResolvableType resolvableType;
    private final TypeDescriptor.AnnotatedElementAdapter annotatedElement;
}

For example, if we need to implement the type conversion of String → DataSize, we can follow the following example:

final class StringToDataSizeConverter implements GenericConverter {
    StringToDataSizeConverter() {
    }

    public Set<ConvertiblePair> getConvertibleTypes() {
        return Collections.singleton(new ConvertiblePair(String.class, DataSize.class));
    }

    public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) {
        return ObjectUtils.isEmpty(source) ? null : this.convert(source.toString(), this.getDataUnit(targetType));
    }

    private DataUnit getDataUnit(TypeDescriptor targetType) {
        DataSizeUnit annotation = (DataSizeUnit)targetType.getAnnotation(DataSizeUnit.class);
        return annotation != null ? annotation.value() : null;
    }

    private DataSize convert(String source, DataUnit unit) {
        return DataSize.parse(source, unit);
    }
}

4 ConditionalConverter interface

org. springframework. core. convert. converter. The conditionalconverter interface provides a matches() method to judge whether the current Converter/GenericConverter/ConverterFactory can complete the conversion from sourceType to targetType.

public interface ConditionalConverter {
   boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType);
}

Since the ConditionalConverter interface only provides a pre verification method, it is more like an auxiliary function interface.

It is more used with other actual business interfaces, such as the above Converter/ConverterFactory/GenericConverter.

5 ConditionalGenericConverter interface

org. springframework. core. convert. converter. The conditionalgenericconverter interface integrates the functions of ConditionConverter and GenericConverter.

We can implement the functions of the above two interfaces by implementing the ConditionalGenericConverter interface.

public interface ConditionalGenericConverter 
    extends GenericConverter, ConditionalConverter {
}

Added by joeywoodbury on Fri, 14 Jan 2022 18:25:11 +0200