[Spring] about Spring's type conversion ConversionService TypeConverter, etc

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

Keywords: Spring

Added by theredking on Sat, 26 Feb 2022 12:28:27 +0200