preface
The conversion capability at the Spring framework level is mainly provided by ConversionService. This chapter briefly discusses the Spring conversion service:
- Converter API: Converter ConverterFactory GenericConverter, etc
- Conversion service: ConversionService series
- Integration of Formatter and Converter
- Unified transformation service exit TypeConverter
Converter API
Converter
@FunctionalInterface public interface Converter<S, T> { @Nullable T convert(S source); }
- A very simple functional interface, S is converted to T
- Example omitted
ConverterFactory
public interface ConverterFactory<S, R> { <T extends R> Converter<S, T> getConverter(Class<T> targetType); }
- Allow S to be converted into a set of R subclasses, that is, one to many conversion
- Please refer to the official implementation, such as NumberToNumberConverterFactory
ConditionalConverter
public interface ConditionalConverter { boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType); }
- matches determines whether a match is made based on the type
- GenericConverter mentioned later will make extensive use of this interface
GenericConverter
public interface GenericConverter { @Nullable Set<ConvertiblePair> getConvertibleTypes(); @Nullable Object convert(@Nullable Object source, TypeDescriptor sourceType, TypeDescriptor targetType); final class ConvertiblePair { private final Class<?> sourceType; private final Class<?> targetType; // ... } }
- This is the most flexible and complex converter interface because it supports many to many conversion services
- The specific conversion type is maintained by the internal class ConvertiblePair, and the method getConvertibleTypes returns the collection of conversion types supported by the converter
Conversion service
ConversionService
public interface ConversionService { boolean canConvert(@Nullable Class<?> sourceType, Class<?> targetType); boolean canConvert(@Nullable TypeDescriptor sourceType, TypeDescriptor targetType); @Nullable <T> T convert(@Nullable Object source, Class<T> targetType); @Nullable Object convert(@Nullable Object source, @Nullable TypeDescriptor sourceType, TypeDescriptor targetType); }
- canConvert, whether the conversion of target type is supported
- convert is converted by the conversion service (select the corresponding converter)
ConverterRegistry
public interface ConverterRegistry { void addConverter(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); void removeConvertible(Class<?> sourceType, Class<?> targetType); }
- XXXRegistry, the model of registration (Management) center
- Provides the registration and management of converters
ConfigurableConversionService
public interface ConfigurableConversionService extends ConversionService, ConverterRegistry { }
- Its subclass is a ConversionService, which is responsible for selecting the converter to convert the corresponding type
- Its subclass is a ConverterRegistry, which is responsible for maintaining a set of converters
GenericConversionService
- All the logic of transformation service is basically implemented in this class: converter maintenance, specified type transformation, etc
- Whether its implementation is exquisite depends on two internal classes:
- Converters maintains the ConvertiblePair to ConvertersForPair relationship, that is, it obtains the converter set according to the type of converted data
- ConvertersForPair maintains the corresponding converter set, that is, it obtains the only converter instance for conversion operation. Here, it obtains the first converter that meets the conditions (such as match method)
- If there are too many details, we won't start. If you are interested, you can have a look
DefaultConversionService
- Default implementation of ConversionService
- There is no additional logic. The main reason is that many converters are registered by default, which can basically deal with all common conversion types
Integration of formatting and type conversion
Printer
@FunctionalInterface public interface Printer<T> { String print(T object, Locale locale); }
Parser
@FunctionalInterface public interface Parser<T> { T parse(String text, Locale locale) throws ParseException; }
Formatter
public interface Formatter<T> extends Printer<T>, Parser<T> { }
- Formatting can actually be regarded as a type conversion: the conversion between String and T
- All can be understood as one Formatter corresponding to two converters
FormatterRegistry
public interface FormatterRegistry extends ConverterRegistry { void addPrinter(Printer<?> printer); void addParser(Parser<?> parser); void addFormatter(Formatter<?> formatter); void addFormatterForFieldType(Class<?> fieldType, Formatter<?> formatter); void addFormatterForFieldType(Class<?> fieldType, Printer<?> printer, Parser<?> parser); void addFormatterForFieldAnnotation(AnnotationFormatterFactory<? extends Annotation> annotationFormatterFactory); }
- The register center of Formatter is also a ConverterRegistry, which is actually a combined relationship
- Support the separate registration of Printer and Parser and the registration of Formatter
FormattingConversionService
public class FormattingConversionService extends GenericConversionService implements FormatterRegistry, EmbeddedValueResolverAware { // ... // For printers, adapt to PrinterConverter registration @Override public void addPrinter(Printer<?> printer) { Class<?> fieldType = getFieldType(printer, Printer.class); addConverter(new PrinterConverter(fieldType, printer, this)); } // For Parser, adapt to the registration of ParserConverter @Override public void addParser(Parser<?> parser) { Class<?> fieldType = getFieldType(parser, Parser.class); addConverter(new ParserConverter(fieldType, parser, this)); } // One Formatter for two converters @Override public void addFormatterForFieldType(Class<?> fieldType, Formatter<?> formatter) { addConverter(new PrinterConverter(fieldType, formatter, this)); addConverter(new ParserConverter(fieldType, formatter, this)); } // ... }
- As mentioned earlier, Formatter can be understood as a Converter from T to String, and FormattingConversionService depends on this idea
- Printer and Parser are respectively adapted to the classic scenario of PrinterConverter and ParserConverter registration and adapter design mode
- The registration method addConverter is provided by the parent class GenericConversionService and combines the classic scenarios of design patterns
DefaultFormattingConversionService
- The idea is the same as that of DefaultConversionService, and it is also a subclass of DefaultConversionService
- Register some default formatters on top of the converter registered by DefaultConversionService
TypeConverter
public interface TypeConverter { @Nullable <T> T convertIfNecessary(@Nullable Object value, @Nullable Class<T> requiredType) throws TypeMismatchException; @Nullable <T> T convertIfNecessary(@Nullable Object value, @Nullable Class<T> requiredType, @Nullable MethodParameter methodParam) throws TypeMismatchException; @Nullable <T> T convertIfNecessary(@Nullable Object value, @Nullable Class<T> requiredType, @Nullable Field field) throws TypeMismatchException; @Nullable default <T> T convertIfNecessary(@Nullable Object value, @Nullable Class<T> requiredType, @Nullable TypeDescriptor typeDescriptor) throws TypeMismatchException { throw new UnsupportedOperationException("TypeDescriptor resolution not supported"); } }
- In addition to transformation services, the property editor provided by JDK also provides a similar mechanism. TypeConverter unifies the export of all the above transformations
- The subclass TypeConverterSupport is used as the abstract base class to delegate the implementation of the above methods to TypeConverterDelegate
- TypeConverterDelegate is converted based on PropertyEditor and ConversionService. The details will not be further explored
- The default TypeConverter instance in the Spring container is simpletype converter
summary
Spring's data transformation is reflected in all aspects:
- Simple type conversion, such as type conversion of BeanFactory#getBean method, etc
- Data binding, such as DataBinder binding data, must also involve type conversion
- wait