“This is the 18th day of my participation in the First Challenge 2022. For details: First Challenge 2022.”

Based on the latest Spring 5.x, the type conversion mechanism of Spring is introduced in detail. Including three of the most common data type converters PropertyEditor, Formatter, Converter, HttpMessageConverter, ConversionService and other core classes.

When using Spring and Spring MVC, Spring uses a series of type conversion mechanisms to convert parameters to the type we specify. This conversion is usually insensitive to the user, and we just need to accept it using the specified type!

Let’s take a look at Spring’s conversion mechanism in detail, including the three most common data type converters PropertyEditor, Formatter, Converter, and ConversionService.

Spring MVC learning series

Spring MVC Learning (1) — An introduction to MVC and an introduction to Spring MVC

Spring MVC Learning (2) – The container hierarchy in Spring MVC and the concept of parent-child containers

Spring MVC Learning (3) – The core components in Spring MVC and the execution flow of the request

Spring MVC Learning (4) — A detailed introduction and use case of the ViewSolvsolver view parser

Spring MVC Learning (5) – Annotation-based Controller configuration complete solution

Spring MVC Learning (6) – Spring data type conversion mechanism

Annotation-based declarative Validation of data

Spring MVC Learning (8) – HandlerInterceptor processor interceptor mechanism complete solution

Spring MVC Learning (9) – Project unified exception handling mechanism details and use cases

Spring MVC Learning (10) – File upload configuration, path configuration of DispatcherServlet, request and response content encoding

Spring MVC Learning (11) – Introduction to cross-domain and solving cross-domain problems using CORS

@[toc]

1 Overview of Spring type conversion mechanism

BeanWrapper is an internal Spring system that is used to fill bean dependencies after creating bean instances. As we explained in the previous Spring IOC source section, BeanWrapper is unconscious to most people and is used internally by Spring. Belongs to an underlying object.

Type conversions in Spring occur in two main places:

  1. When Spring creates the bean instanceWhen injecting Bean property dependencies into the underlying BeanWrapper, if the found dependency type (given a constant value or found dependency object) does not match the specific type of the property, it needs to be converted to the corresponding property type.
  2. In Spring MVC, you might need to bind HTTP request data to the controller via DataBinder before executing processor methodsThe given arguments to the methodHowever, HTTP parameters are strings to the back end, and method parameters can be of various types, which may involve converting from String to a given type.

Spring provides three of the most common datatype converters: PropertyEditor, Formatter, and Converter. Both Spring MVC’s DataBinder and the underlying BeanWrapper support these converters for data conversion: PropertyEditor

  1. PropertyEditorIs the JDK’s built-in type conversion interface. It is used to convert strings to other types. Spring already provides an implementation of PropertyEditor for common type conversions.
  2. FormatterSpring 3.0 provides an interface that can only convert strings to other types and supports the SPI mechanism. Usually, for Spring MVC parameter bindings, use Formatter for type conversions. Spring already provides a Formatter implementation for common type conversions.
  3. ConverterConverter is an interface provided in Spring 3.0 that provides conversion from one object type to another, supports SPI mechanism, and should be used when common type conversion logic is needed. Spring already provides Converter implementations for common type conversions.

2 PropertyEditor

2.1 Overview of PropertyEditor

In the beginning, Spring used the concept of PropertyEditor to convert objects to strings. The PropertyEditor interface came not from Spring, but from Java’s rT.jar core dependency package, which came with the JDK. The utility of the property editor was discussed earlier:

  1. When Spring creates a bean, the data is converted to the corresponding type of the bean’s property.
  2. Analyze and transform HTTP request parameters in Spring MVC framework.

Common methods of the PropertyEditor interface are as follows:

public interface PropertyEditor {

    /** * sets (or changes) the object to edit. Primitive types (such as "int") must be wrapped with the corresponding object type, such as "java.lang.INTEGER" * *@paramValue Specifies the new target object to edit. The property editor should not modify this object, and the property editor should create a new object to hold any modified values */
    void setValue(Object value);

    /** * get the attribute value. * *@returnProperty value. Primitive types (such as "int") are wrapped with the corresponding object type, such as "java.lang.INTEGER" */
    Object getValue(a);

    /** * provides the property with a string representing its initial value, which the property editor uses as its default value */
    String getJavaInitializationString(a);

    /** * Gets an editable string representation of the attribute value **@returnIf the value cannot be represented as an editable string, null is returned. If a non-null value is returned, the property editor should be ready to parse the string */ in setAsText()
    String getAsText(a);

    /** * sets the property value by parsing the given string. If the string format error or such attributes cannot be expressed in text form, may lead to Java. Lang. IllegalArgumentException * *@paramText Specifies the string to parse. * /
    void setAsText(String text) throws java.lang.IllegalArgumentException;
}
Copy the code

Although it is common to read that PropertyEditor is only used to support String to object conversions. But in fact, in the current version of Spring, PropertyEditor has been supported to implement the transformation from object to object. Typical implementations are various PropertyEditor implementations from Spring-data-redis. For example, ValueOperationsEditor, we can directly rely on ValueOperations and inject a redisTemplate into it. When Spring detects type inconsistence, ValueOperations will eventually be retrieved in ValueOperationsEditor via the injected redisTemplate and returned.

The core method that supports the conversion from object to object is the PropertyEditor#setValue method.

2.2 Built-in PropertyEditor

Although it is now recommended to use Converter instead of PropertyEditor when custom converters are needed, custom PropertyEditor can still be configured and used normally, and many default PropertyEditor are used internally in Spring.

Spring has many built-in onesPropertyEditorThe implementation. They’re both locatedOrg. Springframework. Beans. Propertyeditors package. By default, most (but not all) byBeanWrapperImplTo register (located atAbstractBeanFactory#initBeanWrapperMethod, used by BeanWrapper after registration to create and populate Bean instances). Many default property editor implementations are also configurable, for exampleCustomDateEditor, you can specify the date mode.

The following table lists the common PropertyEditor provided by Spring:

type describe
ByteArrayPropertyEditor Editor for byte arrays. Converts String to the corresponding byte[] representation. BeanWrapperImpl is registered by default.
ClassEditor Supports String parsing representing classes and converting to and from the actual Class. When the class cannot be found, IllegalArgumentException is thrown. By default, BeanWrapperImpl is registered.
CustomBooleanEditor A customizable property editor for Boolean that resolves a specified String to a Boolean value. By default, BeanWrapperImpl is registered, but can be overridden by registering its custom instance as a custom editor.
CustomCollectionEditor A customizable property editor for collections that converts any source string or collection to a given target collection type. By default, BeanWrapperImpl is registered, but can be overridden by registering its custom instance as a custom editor.
CustomDateEditor Date customizable property editor for java.util.Date that supports custom DateFormat. Not registered by default. Manual registration must be done by the user as needed, using the appropriate format.
CustomNumberEditor Customizable property editor for any subclass of Number, such as Integer, Long, Float, or Double. By default, BeanWrapperImpl is registered, but can be overridden by registering its custom instance as a custom editor.
FileEditor Parse the string into a java.io.File object. By default, BeanWrapperImpl is registered.
InputStreamEditor Generate an InputStream from a String through the intermediate ResourceEditor and Resource. The default usage does not turn off inputStream. By default, BeanWrapperImpl is registered.
LocaleEditor String objects can be converted to and from Locale objects. The String format is the same as the toString() method for Locale. By default, BeanWrapperImpl is registered.
PatternEditor String and java.util.regex.Pattern objects can be converted to and from each other
PropertiesEditor You can convert strings to Properties objects (formatted using the format defined in Javadoc of the java.util.properties class). By default, BeanWrapperImpl is registered.
StringTrimmerEditor Trim the property editor for strings, and optionally convert an empty string to a null value. By default, the user must register manually.
URLEditor You can parse the URL string into an actual URL object. By default, BeanWrapperImpl is registered.

BeanWrapperImpl automatically registered PropertyEditor in PropertyEditorRegistrySupport# createDefaultEditors method, The findDefaultEditor method in the convertIfNecessary method is registered when the conversion type is needed but no suitable convertor can be found in the other custom converters. This is a lazy initialization!

2.3 PropertyEditorManager

Spring usejava.beans.PropertyEditorManagerTo register and search for any PropertyEditor you may need. The search path also includes sun.bean.editors in the Rt.jar package, which includes the PropertyEditor implementation for Font, Color, and most basic types.

In addition, if a type converter of a type is in the same package path as the Class of that type and is named ClassName+Editor, the converter will be discovered automatically without manual registration when it needs to be converted to that type, as in the following example:

com
  chank
    pop
      Something         / / Something
      SomethingEditor // Type conversion for Something will be found automatically
Copy the code

A manager to manage the default property editor: PropertyEditorManager, which holds property editors for common types. If a JavaBean’s common type properties are not explicitly specified via BeanInfo, The IDE will automatically use the corresponding default property editor registered in PropertyEditorManager.

In fact, the various PropertyEditor implementations of Spring-data-redis were discovered using this mechanism, without manual registration:

Of course, we can also use the standard BeanInfo JavaBeans mechanism to explicitly specify the relationship between a class and a property editor, as shown in the following example:

com
  chank
    pop
      Something
      SomethingBeanInfo
Copy the code

2.4 Registering custom PropertyEditor

Spring pre-registers many custom property editor implementations (for example, converting a Class name represented as a string to a Class object). In addition, Java’s standard JavaBeans PropertyEditor lookup mechanism allows the PropertyEditor of a class to be appropriately named and placed in the same package as the classes it supports so that it can be found automatically (as described above).

Spring provides a core implementation class for PropertyEditorPropertyEditorSupportIf we want to write a custom property editor, we just need to inherit this class. PropertyEditorSupport implements this classPropertyEditorAll methods of the interface, we inherit PropertyEditorSupport after only need to rewrite their own methods, more convenient!

If you need to register other custom PropertyEditors, several mechanisms can be used:

  1. The least recommended and inconvenient is to useThe registerCustomEditor() method of the ConfigurableBeanFactory interfaceBecause this requires us to get a reference to the BeanFactory. This method registers the custom PropertyEditor directly toThe AbstractBeanFactory customEditorsIn the cache, waiting for a subsequent BeanWrapper fetch.
  2. Another (and slightly more convenient) mechanism is to useCustomEditorConfigurerIt is a special oneBeanFactoryPostProcessor, the customized PropertyEditor or PropertyEditorRegistrar implementation can be stored internallyCustomEditors and propertyEditorRegistrarsProperty after starting the project, itspostProcessBeanFactoryThe beanFactory method calls the beanFactory method before all normal beans are instantiated and initialized (before BeanWrapper is created)PropertyEditor and propertyEditorRegistrarsRegistered toAbstractBeanFactory customEditors and propertyEditorRegistrarsThe cache.

Based on the above configuration, the Spring bean’s corresponding BeanWrapper is automatically initialized fromAbstractBeanFactory customEditors and propertyEditorRegistrarsThe cache registers the custom PropertyEditor internally (inAbstractBeanFactory#initBeanWrapperMethod), and then byBeanWrapper is the type conversion used to create and populate Bean instances.

Note, however, that this configuration does not work for Spring MVC data binding, because DataBinder by default does not look for the customEditors and propertyEditorRegistrars ccache here registered with AbstractBeanFactory, Custom Editor must be needed for the data binding in org. Springframework. Validation. The DataBinder manually register (by using the approach of Spring MVC @ InitBinder).

In the following example, a customized PropertyEditor is created in the format of yyyY-MM-DD:

/ * * *@author lx
 */
public class DateEditor extends PropertyEditorSupport {
    private String formatter = "yyyy-MM-dd";

    @Override
    public void setAsText(String text) throws IllegalArgumentException {
        SimpleDateFormat simpleDateFormat = new SimpleDateFormat(formatter);
        try {
            Date date = simpleDateFormat.parse(text);
            System.out.println("-----DateEditor-----");
            // The converted value is set to the value property inside PropertyEditorSupport
            setValue(date);
        } catch (ParseException e) {
            throw newIllegalArgumentException(e); }}public DateEditor(a) {}public DateEditor(String formatter) {
        this.formatter = formatter; }}Copy the code

An entity that needs to convert the string “2020-12-12” to an attribute of type Date:

@Component
public class TestDate {
    @Value("2020-12-12")
    private Date date;

    @PostConstruct
    public void test(a) { System.out.println(date); }}Copy the code

Registers the custom PropertyEditor to the customEditors property of CustomEditorConfigurer, which is Map<Class<? >, Class<? Extends PropertyEditor> type, i.e. both are Class types:

<bean 
 class="org.springframework.beans.factory.config.CustomEditorConfigurer">
    <property name="customEditors">
        <map>
            <entry key="java.util.Date" value="com.spring.mvc.config.DateEditor"/>
        </map>
    </property>
</bean>
Copy the code

Start the project and see the output:

-----DateEditor-----
Sat Dec 12 00:00:00 CST 2020
Copy the code

Through theCustomEditorConfigurer's customEditors propertyYou can register a type that adds a custom editor directly to theCustom PropertyEditorSpecify initialization parameters.

In fact, in earlier Spring versions, the value type in the Map was an instance, so it supported custom initialization parameters, but because PropertyEditor was statically, if multiple BeanWrappers shared the same PropertyEditor instance, Then it can cause problems that are invisible. Therefore, in the new version the Value of the Map for the customEditors property is of Class type, and each BeanWrapper creates its own PropertyEditor instance when setting up the converter. If you want to control the PropertyEditor instantiation process, such as setting initialization parameters, then you need to register them via PropertyEditorRegistrar.

Another disadvantage is that,PropertyEditor configured based on the customEditors property cannot be configured in the same way as Spring MVC's data binding, even though they both need to configure some of the same PropertyEditor.

Use against 2.4.1 PropertyEditorRegistrar

PropertyEditorRegistrarThe Registrar Registrar class is also used to register the data via the Registrar class name. The PropertyEditorRegistrar is used to register the PropertyEditor. An optional feature is that you can register multiple instances in a single method call and be more flexible!

In addition, the PropertyEditorRegistrar instance works with an interface called PropertyEditorRegistry that is implemented with both Spring’s BeanWrapper and DataBinder So the PropertyEditor configuration in PropertyEditorRegistrar is easily shared with BeanWrapper and DataBinder!

Spring provides the ResourceEditorRegistrar implementation of the PropertyEditorRegistrar. If we want to implement our own PropertyEditorRegistrar, we can parameter the PropertyEditorRegistrar via In particular, its registerCustomEditors method. Actually the ResourceEditorRegistrar will be automatically registered with the container (in the prepareBeanFactory method) by Spring by default, so the PropertyEditor in the class will be used by all beanwarppers!

The following is the PropertyEditorRegistrar:

/ * * *@author lx
 */
public final class CustomPropertyEditorRegistrar implements PropertyEditorRegistrar {

    private String formatter;


    /** * Pass an implementation of PropertyEditorRegistry, Registering custom PropertyEditor * BeanWrapperImpl and DataBinder with the given PropertyEditorRegistry both implement the PropertyEditorRegistry interface, It is usually passed a BeanWrapper or DataBinder. * <p> * This method simply defines the process of registering and only registers when a BeanWrapper or DataBinder is actually called@paramRegistry will register the PropertyEditorRegistry */ for the custom PropertyEditor
    @Override
    public void registerCustomEditors(PropertyEditorRegistry registry) {

        // It is expected that a new instance of the property editor will be created, allowing you to control the creation process
        registry.registerCustomEditor(Date.class, new DateEditor(formatter));

        // As many custom property editors can be registered here...
    }


    public String getFormatter(a) {
        return formatter;
    }

    public void setFormatter(String formatter) {
        this.formatter = formatter; }}Copy the code

Here’s how to configure CustomEditorConfigurer and inject CustomPropertyEditorRegistrar instances:

<bean 
class="org.springframework.beans.factory.config.CustomEditorConfigurer">
    <! PropertyEditorRegistrars is an array of custom PropertyEditorRegistrar applications
    <property name="propertyEditorRegistrars">
        <array>
            <ref bean="customPropertyEditorRegistrar"/>
        </array>
    </property>
</bean>

<! - custom CustomPropertyEditorRegistrar -- -- >
<bean id="customPropertyEditorRegistrar" class="com.spring.mvc.config.CustomPropertyEditorRegistrar">
    <property name="formatter" value="yyyy-MM-dd"/>
</bean>
Copy the code

Start the project, also successful conversion:

-----DateEditor-----
Sat Dec 12 00:00:00 CST 2020
Copy the code

Note that the purpose of Spring is for each BeanWarpper and DataBinder to initialize their own PropertyEditor instance. This is to prevent multiple instances from sharing the same statically PropertyEditor from causing data exceptions, if you are sure that this is ok. You can also configure the same PropertyEditor instance in PropertyEditorRegistrar.

2.4.1.1 Share Configuration

With the PropertyEditorRegistrar configuration configured, it is very simple to apply the PropertyEditor configuration to the Spring MVC DataBinder as follows:

@Controller
public class RegistrarController {
    @Resource
    private CustomPropertyEditorRegistrar customPropertyEditorRegistrar;

    @InitBinder
    public void init(WebDataBinder binder) {
        // Call the registerCustomEditors method to register PropertyEditor with the current DateBinder
        customPropertyEditorRegistrar.registerCustomEditors(binder);
    }

    // Other controller methods
}
Copy the code

You just need to introduce it into the controllercustomPropertyEditorRegistrarInstance, and then in@ initBinder methodIn the callregisterCustomEditorsMethod and pass inDataBinderTo register the internally configured PropertyEditor with the current DataBinder.

This type of PropertyEditor registry produces clean code (a single line of code registers multiple PropertyEditor implementations) and allows the common PropertyEditor registry code to be encapsulated in a class that can then be shared among multiple Controllers as needed.

3 Converter

Spring 3.0 was introducedcore.convertPackage, which provides the general type of conversion system as JavaBeans PropertyEditorsAlternative services to the property editor.

3.1 Converter SPI Interface

Compared to the complex PropertyEditor interface,org.springframework.core.convert.converter.ConverterSPI is a very simple and powerful SPI interface for type conversion, which translates to “converter” in Chinese.ConverterProvides core transformation behavior!

@FunctionalInterface
public interface Converter<S.T> {

    /** * Convert a source object of type S to a target object of type T **@paramSource The source object to convert, which must be an instance of type S (never NULL) *@returnConverted object, which must be an instance of type T (possibly NULL) *@throwsIllegalArgumentException If the source object cannot be converted to the desired target type */
    @Nullable
    T convert(S source);

}
Copy the code

You want to create your ownConverter, just implement the Converter interface, where S represents the type to be converted and T represents the type to be converted.

convert(S)Method should ensure that the input parameter is not null every time. If the conversion fails, the converter may throw any unchecked exceptions. The exception should be wrapped in an IllegalArgumentException when thrown, and we must ensure that The Converter isThread safety!!!!

Similar to PropertyEditor, for convenience,Spring in the core. The convert. SupportThe package already provides a lot ofConverter implementation, which includes string to number converters and other common types.

Here is a typical Converter implementation:

public final class StringToInteger implements Converter<String.Integer> {

    @Override
    public Integer convert(String source) {
        returnInteger.valueOf(source); }}Copy the code

3.2 use ConverterFactory

Conversions to Converter are explicit. If conversions are required for multiple subtypes that have the same parent or interface, it is obviously not sensible to write one Converter for each type. This can be done when the conversion logic of the entire class hierarchy needs to be centrally managedConverterFactoryInterface:

/ * * *@param<S> Source type *@param<R> The supertype of the target type */
public interface ConverterFactory<S.R> {

    /** * gets the converter from S to the target type T, where T is also a subtype of R. * *@param<T> Target type *@paramTargetType Class * of the targetType to be converted@returnConverter from S to T */
    <T extends R> Converter<S, T> getConverter(Class<T> targetType);

}
Copy the code

The argument S is the type to be converted, and R is the base class of the class to be converted. Then implementgetConverter(Class)Method, where T is a subtype of R.ConverterFactoryUsed to convert one type to N types.

Spring already provides the basicsThe realization of the ConverterFactory:

3.3 use GenericConverter

When you need to defineComplex Converter implementation, can be usedGenericConverterInterface, GenericConverter is not a subinterface of Converter, but aA separate top-level interface, translated into Chinese is “universal converter”!

Compared to Converter, GenericConverter is more flexible and does not have a strongly-typed signature. It supports conversions between multiple source and target types and is used for conversions from N types to N types. In addition, GenericConverter provides available source and target field contexts (TypeDescriptor), you can use them when implementing transformation logic. Such a context allows type conversions to be driven by generic information declared on field annotations or field signatures.

Here is the GenericConverter interface definition:

/** * A generic converter interface for converting between two or more types. * 

* This is the most flexible converter SPI interface, and also the most complex. Its flexibility is that GenericConverter may support conversions between multiple source/target type pairs * In addition, GenericConverter implementations can access source/target field context during type conversions. * This allows parsing of source and target field metadata, such as annotations and generic information, which can be used to influence transformation logic. * /

public interface GenericConverter { /** * Returns convertiblepairs of all source and target types that this converter can convert * each ConvertiblePair represents a set of convertable source types as well as target types. *

* For ConditionalConverter, this method may return NULL to indicate that all convertiblepairs */ should be considered

@Nullable Set<ConvertiblePair> getConvertibleTypes(a); /** * convert the source object to the target type described by the TypeDescriptor. * *@paramSource Source object to be converted (possibly NULL) *@paramSourceType Type descriptor * of the field being converted@paramTargetType The type descriptor * of the field to be converted to@returnConverted object */ @Nullable Object convert(@Nullable Object source, TypeDescriptor sourceType, TypeDescriptor targetType); /** * Source type to target type pair holder */ final class ConvertiblePair { private finalClass<? > sourceType;private finalClass<? > targetType;/** * Create a new ConvertiblePair **@paramSourceType sourceType *@paramTargetType 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; } publicClass<? > getSourceType() {return this.sourceType; } publicClass<? > getTargetType() {return this.targetType; } /* Determines whether a group of a source type to a target type exists */ @Override public boolean equals(@Nullable Object other) { if (this == other) { return true; } if (other == null|| other.getClass() ! = ConvertiblePair.class) {return false; } ConvertiblePair otherPair = (ConvertiblePair) other; return (this.sourceType == otherPair.sourceType && this.targetType == otherPair.targetType); } @Override public int hashCode(a) { return (this.sourceType.hashCode() * 31 + this.targetType.hashCode()); } @Override public String toString(a) { return (this.sourceType.getName() + "- >" + this.targetType.getName()); }}}Copy the code

GenericConverterHas an inner class inConvertiblePairThis inner class encapsulates a ConvertiblePair of source and target types. A GenericConverter can have multiple convertiblepairs.

To implement GenericConverter, you need to rewriteGetConvertibleTypes () methodTo return the source type supported by the transformation to the target type pair, which is ConvertiblePair. Then implementConvert (Object, TypeDescriptor, TypeDescriptor) method, which contains the logic for the transformation. Source TypeDescriptor (field descriptor) provides access to the source field that holds the value to be converted. The target TypeDescriptor provides access to the target field to which the conversion value is to be set.

TypeDescriptorAs a type descriptor, save the corresponding parameter, field metadata, from which you can get the corresponding parameter, field name, type, annotation, generic information!

Spring already provides basic GenericConverter implementations, and a good example is converters that convert between Java arrays and collections, such asArrayToCollectionConverterFirst, it creates the corresponding collection type, and then, when storing the array elements in the collection, it tries to convert the array element types to generic types of the collection elements if necessary!

3.3.1 use ConditionalGenericConverter

If you think it’s too easy to support conversions only by whether the source type matches the target type, you need to support conversions only when certain conditions are true, such as when you might want a specified annotation on the target field. Or you might want to indicate conversion only if a specific method (such as a static valueOf method) is defined on the type of the target field, which we can useConditionalGenericConverterInterface.

The combination of ConditionalGenericConverter GenericConverter and ConditionalConverter, allowing custom matching conditions to determine whether the conversion can be performed!

/** * Conditional converters, which allow conditional execution of conversions * <p> * are typically used to selectively match custom conversion logic based on the presence of field - or class-level features such as annotations or methods. * For example, when converting a String field to a Date field, if the target field is already in use@DateTimeFormatAnnotations, matches may return true */
public interface ConditionalConverter {

    /** * Can conversions from the source type be applied to the target type currently under consideration? * *@paramSourceType Type descriptor * of the field being converted@paramTargetType The type descriptor * of the field to be converted to@returnTrue if the conversion should be performed, false */ otherwise
    boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType);
}

/ * * * ConditionalGenericConverter inherited GenericConverter and ConditionalConverter interface at the same time, support more complex judge * /
public interface ConditionalGenericConverter extends GenericConverter.ConditionalConverter {}Copy the code

Spring has provided basic ConditionalGenericConverter implementationMost implementations are used to handle conversions involving collections or arrays, and a good example is alsoArrayToCollectionConverter, it will begetConvertibleTypesThe matches () method checks whether the source array element type can be converted to the target array element type. If the source array element type can be converted to the target array element type, the conversion logic is executed.

3.4 ConversionService API interface

Due to the complexity of the overall Conversion mechanism, Spring providesConversionService Interface, which defines a unified set of API methods for external calls to perform type conversions at run time, masking the specific internal call logic. This is based onFacade design patterns!

/** * Service interface for type conversion. * This is the entry point into the conversion system, which is used to perform thread-safe type conversions by calling convert(Object, Class). * /
public interface ConversionService {

    If this method returns true, it means that the convert(Object, Class) method can convert instances of the source type to the target type. * <p> * This method returns true for conversions between collection, array, and Map types, even though the conversion call may still throw a ConversionException (if the underlying element is not convertible). * Callers should handle this special case when using collections and maps. * *@paramSourceType sourceType to be converted (or null if the source object is null) *@paramTargetType the targetType to be converted to (must exist) *@returnTrue if the conversion can be performed, false * if not@throwsIllegalArgumentException if the targetType targetType is null */
    boolean canConvert(@NullableClass<? > sourceType, Class<? > targetType);

    /** * Returns true if objects of source type can be converted to target type. This means that the convert(Object, TypeDescriptor, TypeDescriptor) method can convert the source type instance to the target type. * <p> * This method returns true for conversions between collection, array, and Map types, even though the conversion call may still throw a ConversionException (if the underlying element is not convertible). * Callers should handle this special case when using collections and maps. * *@paramSourceType context about the sourceType to be converted, that is, TypeDescriptor (possibly null if the source object is null) *@paramTargetType the context of the targetType to be converted to, that is, TypeDescriptor (must exist) *@returnTrue if a conversion can be performed between source and target types, false * if not@throwsIllegalArgumentException if the targetType targetType is null */
    boolean canConvert(@Nullable TypeDescriptor sourceType, TypeDescriptor targetType);

    /** * Converts the given source object to the specified target type. * *@paramSource Source object to be converted (possibly NULL) *@paramTargetType the targetType to be converted to (must exist) *@returnConverted object, instance of the target type *@throwsConversionException if a ConversionException occurs *@throwsIllegalArgumentException if the targetType targetType is null */
    @Nullable
    <T> T convert(@Nullable Object source, Class<T> targetType);

    /** * Converts the given source object to the specified target type. * TypeDescriptor provides additional context about the source and target location (usually the object field or property location) where the conversion took place, from which information can be retrieved about field names, types, annotations, generics, and so on * *@paramSource Source object to be converted (possibly NULL) *@paramSourceType context about the sourceType to be converted, that is, TypeDescriptor (possibly null if the source object is null) *@paramTargetType the context of the targetType to be converted to, that is, TypeDescriptor (must exist) *@returnConverted object, instance of the target type *@throwsConversionException if a ConversionException occurs *@throwsIllegalArgumentException If the target type is NULL, or the source type is NULL, but the source object is not NULL */
    @Nullable
    Object convert(@Nullable Object source, @Nullable TypeDescriptor sourceType, TypeDescriptor targetType);

}
Copy the code

ConversionService is simply a service interface for type conversion. Most implementations of ConversionService also implement the ConverterRegistry interface (an interface that provides the registration of Converter methods), It provides an SPI mechanism for registering Converter converters. As a result,Implementations of ConversionService typically have methods that support registering multiple Converter converters.

/** * Is used to register converters using the type conversion system. * /
public interface ConverterRegistry {

    /** * Adds a normal converter to this registry, converting the convertable source/target type to the generic parameter type derived from the converter. * /
    void addConverter(Converter
        converter);

    /** * Adds a normal converter to this registry that displays the convertable source/target type pairs */ for the specified convertable
    <S, T> void addConverter(Class<S> sourceType, Class<T> targetType, Converter<? super S, ? extends T> converter);

    /** * Adds a generic converter to this registry. * /
    void addConverter(GenericConverter converter);

    /** * Adds the range converter factory to this registry. The convertable source/target type pair is a generic parameter type derived from the converter factory. * /
    void addConverterFactory(ConverterFactory
        factory);

    /** * Delete all Converter */ corresponding source/target type pairs
    void removeConvertible(Class
        sourceType, Class
        targetType);

}
Copy the code

Normally, when a conversion is required, we simply call the ConversionService method, and the actual conversion logic is delegated to its internal registered converter instance! Spring provides a robust set of conversionService implementations and related configuration classes in the core.convert.support package. GenericConversionService, for example, is a generic implementation for most environments that provides the capability to configure Converter. For example, ConversionServiceFactory is a generic factory that provides a service to register Converters for ConversionService.

3.5 configuration ConversionService

ConversionService is a stateless object designed to be instantiated at application startup and then shared between multiple threads. In Spring applications, one ConversionService instance is typically configured for each Spring container (or ApplicationContext). Spring receives the conversion service and uses it whenever the framework needs to perform type conversions. We can also inject this ConversionService into any bean and call its transformation methods directly.

3.5.1 Configuring ConversionService of BeanWarpper

If you want to manually register a globally valid default ConversionService, you need to name the id”conversionService“. In the containerfinishBeanFactoryInitializationThe Spring container initializes this first before the method initializes all ordinary bean instancesConversionService Indicates the conversionServiceAnd set toAbstractBeanFactory conversionService propertyIn the.

In the subsequentBeanWarpperIn the initialization method ofAbstractBeanFactory# initBeanWrapper methodC), will get the registrationconversionServiceAnd keeps it internally for subsequent property populations:

For theNormal Spring projectsThe default ConversionService is not registered, so the ConversionService of the underlying BeanWarpper isnullforThe boot programOne is registered by defaultApplicationConversionServiceService. So, if ConversionService is not registered with Spring, BeanWarpper uses thePropertyEditorThe original transformation system for the property editor.

Here is an example of registering a default ConversionService:

<bean id="conversionService" class="org.springframework.context.support.Con
versionServiceFactoryBean"/>
Copy the code

Instead of configuring the ConversionService directly, we configure oneConversionServiceFactoryBeanObject, which is aFactoryBeanObject of the factory patternConversionServiceIn keeping with Spring’s tradition, heavier and more complex classes are constructed in a factory pattern that produces objects of the actual typeDefaultConversionService.

DefaultConversionService registers some common Converters by default when it is created, which can be set if you want to supplement or override the default Converter with your own custom ConverterConversionServiceFactoryBean converters attributes, the property value can be configured asAny Converter, ConverterFactory, or GenericConverter implementation.

Here is a custom Converter implementation:

/** * convert the string to Date **@author lx
 */
@Component
public class StringToDateConverter implements Converter<String.Date> {

    /** * String source The incoming String **@paramSource The string passed in to be converted *@returnConverted format type */
    @Override
    public Date convert(String source) {
        SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
        Date parse;
        try {
            parse = dateFormat.parse(source);
            System.out.println("--------StringToDateConverter---------");
        } catch (ParseException e) {
            throw new IllegalArgumentException(e);
        }
        returnparse; }}Copy the code

Then can be configured to ConversionServiceFactoryBean:

<! Configure the type conversion service factory, which creates DefaultConversionService by default and supports injection of custom type converters.
<bean id="conversionService" class="org.springframework.context.support.ConversionServiceFactoryBean">
    <property name="converters">
        <set>
            <! Inject a custom converter instance -->
            <ref bean="stringToDateConverter"/>
        </set>
    </property>
</bean>
Copy the code

3.5.1.1 Directly Use ConversionService

We can use DefaultConversionService programmatically and do not need to create an instance of DefaultConversionService because DefaultConversionService is implementedLazy load singleton patternWe can go throughgetSharedInstance()Method to get the shared instance directly.

public class DefaultConversionService extends GenericConversionService {

    @Nullable
    private static volatile DefaultConversionService sharedInstance;

    public static ConversionService getSharedInstance(a) {
        DefaultConversionService cs = sharedInstance;
        if (cs == null) {
            synchronized (DefaultConversionService.class) {
                cs = sharedInstance;
                if (cs == null) {
                    cs = newDefaultConversionService(); sharedInstance = cs; }}}return cs;
    }

    / /.....................
}
Copy the code

3.5.2 Configuring the ConversionService of DataBinder

The default global ConversionService registered above only appliesBeanWarpperforSpring MVC的DataBinderInvalid because DataBinder is initialized and not used when binding ConversionServiceAbstractBeanFactory conversionService propertyDataBinder’s ConversionService has another configuration method:

  1. forThe XML configurationIn terms of configuration<mvc:annotation-driven>The tag means one will be used by defaultDefaultFormattingConversionServiceInstance, can passconversion-serviceProperty specifies a certainConversionServiceInstance.
  2. forJavaConfig configurationBy joining@ EnableWebMvc annotationsYou can also register a defaultDefaultFormattingConversionService“And sign up for oneThe ConversionService whose ID is mvcConversionServiceThat means replace the defaultDefaultFormattingConversionService.
  3. Without these two configurations, DataBinder also does not have ConversionService.

The following configuration makes BeanWarpper and DataBinder use the same conversionService:

<! The --conversion-service attribute specifies the bean name of the conversion service used for type conversion during field binding. -->
<! -- if not specified, the default DefaultFormattingConversionService said registration -- -- >
<mvc:annotation-driven conversion-service="conversionService"/>

<! Configure the type conversion service factory, which creates DefaultConversionService by default and supports injection of custom type converters.
<bean id="conversionService" class="org.springframework.context.support.ConversionServiceFactoryBean">
    <property name="converters">
        <set>
            <! Inject a custom converter instance -->
            <ref bean="stringToDateConverter"/>
        </set>
    </property>
</bean>
Copy the code

Regarding DefaultFormattingConversionService, we will introduce below!

4 Formarter

As mentioned in the previous section,core.convert Is a general purpose type conversion system. It provides a unified ConversionService API and a strongly typed Converter SPI for implementing conversion logic from one type to another, Provides even more extended functionality GenericConverter and ConditionalGenericConverter, Spring is usually recommended to use this system for beanWarpper binding bean property values. In addition, both the Spring Expression Language (SPEL) and DataBinder can use this system to bind field values. For example, when SPEL needs to cast Short to Long to complete the expression.setValue(Object bean, Object value) operation, the core.convert system performs the cast.

inSpring MVCSource data in HTTP areStringType. The data binding requires that the String be converted to another type. It may also require that the data be converted to a String style with a local format for presentation. The more general Converter SPI in Core.convert does not directly satisfy this format. To address these issues, Spring 3 introduces a handyFormatter SPIIs used to replace PropertyEditor in the Web environment.

In general, you can use Converter SPI when you need to implement common type conversion logic, for example, between java.util.Date and Long. Used when working in a client environment, such as a Web application, and you need to parse and output localized field valuesFormatter SPI. ConversionService provides a unified type conversion API for both SPIs.

4.1 the Formatter SPI

org.springframework.format.FormatterIs a very simple and strongly typed SPI interface for implementing field formatting logic.

public interface Formatter<T> extends Printer<T>, Parser<T> {}
Copy the code

Formatter inherits Printer and Parser interfaces. Here are definitions of both interfaces:

@FunctionalInterface
public interface Printer<T> {

    /** * Prints objects of type T for display **@paramObject Specifies the instance to be printed@paramLocale Locale of the current user *@returnThe printed text string */
    String print(T object, Locale locale);

}

@FunctionalInterface
public interface Parser<T> {

    /** * parses the text string to generate T **@paramText Indicates the text string *@paramLocale Specifies the current user area *@returnInstance of T *@throwsParseException When a parsing exception occurs in the java.text parser library@throwsIllegalArgumentException */ when a parsing exception occurs
    T parse(String text, Locale locale) throws ParseException;

}
Copy the code

If you want to create your own Formatter, you need to implement the aboveFormatterInterface to complete T type object formatting and parsing functions.

implementationprint()Method according to the client’s regional Settings to print T instances, to achieveparse()Method parses an instance of T based on client locale and text string. If parsing attempts fail, Formatter should raise ParseException or IllegalArgumentException. Be careful to ensure that the Formatter implementation is thread-safe.

Org. Springframework. Format. Support sub package provides a common convenient use the Formatter.

Org. Springframework. Format. The number package provides NumberStyleFormatter, CurrencyStyleFormatter and PercentStyleFormatter to format a number object, It uses java.text.numberFormat internally.

Org. Springframework. Format. The number. The money provided with JSR – 354 formatter for monetary integration, such as CurrencyUnitFormatter, MonetaryAmountFormatter.

Org. Springframework. Format. The datetime provided DateFormatter sub-package, internal use Java. The text. The DateFormat to format the Java. Util. The Date object. Org. Springframework. Format. The datetime. Joda sub-package based on joda time library offers comprehensive datetime format support.

4.2 Annotation-driven formatting

Field formatting can be configured by field type or annotation. Binding an annotation to a Formatter can be implementedAnnotationFormatterFactoryInterface.

BeanWarpper and DataBinderBoth support custom annotation-driven type conversions, but note that Spring MVC request data binding can only perform annotation-driven type conversions on a complete variable (URI path variable, request parameters, request body data) if@RequestBodyOr when a variable is used to represent an entity, the data inside the variable does not support annotation-driven conversions.

/** * factory used to create formatter to format field values using a particular annotation. * < p > * DateTimeFormatAnnotationForMatterFactory, for example, might create a formatter, the formatter for use@DateTimeFormaAnnotated fields are formatted as Date * *@param<A> The type of annotation that should trigger formatting */
public interface AnnotationFormatterFactory<A extends Annotation> {

    /** * The field type that can be annotated with type A. * /Set<Class<? >> getFieldTypes();Print a field value of type fieldType with the specified annotation **@paramAnnotation Annotation instance *@paramFieldType specifies the fieldType of the annotation@return the printer
     */Printer<? > getPrinter(A annotation, Class<? > fieldType);/** * Get Parser to parse fields of type fieldType with specified annotations **@paramAnnotation Annotation instance *@paramFieldType specifies the fieldType of the annotation@return the parser
     */Parser<? > getParser(A annotation, Class<? > fieldType); }Copy the code

Generic A represents annotations associated with formatting logic, for exampleorg.springframework.format.annotation.DateTimeFormat.getFieldTypes()Method returns the type of field on which an annotation can be used.getprinter()Return Printer to print the value of the annotation field.getParser()Return a Parser to parse the clientValue of the annotation field.

Org. Springframework. Format. The annotation package provides AnnotationFormatterFactory associated annotations of implementation:

  1. @NumberFormatWith the formatted Number/type of field (such as Double, Long), the corresponding NumberFormatAnnotationFormatterFactory, Jsr354NumberFormatAnnotationFormatterFactory.
  2. @DateTimeFormat Used to format java.util.Date, java.util.Calendar, Long (timestamp millisecond), and jSR-310 java.time and joda-time value types. Corresponding DateTimeFormatAnnotationFormatterFactory, JodaDateTimeFormatAnnotationFormatterFactory, Jsr310DateTimeFormatAnnotationFo RmatterFactory.

When using, also is very simple, open Spring MVC configuration, Spring MVC default registers these AnnotationFormatterFactory, we can directly use the above comment.

The following example uses @datetimeFormat to format a Date string parameter of type YYYY-MM-DD passed in front to Date!

public class MyDate {
    @DateTimeFormat(pattern = "yyyy-MM-dd")
    private Date date;

    public Date getDate(a) {
        return date;
    }

    public void setDate(Date date) {
        this.date = date; }}Copy the code

A controller method:

@RequestMapping("/dateTimeFormat/{date}")
@ResponseBody
public MyDate annotationFormatterFactory(MyDate date) {
    System.out.println(DateFormat.getDateTimeInstance().format(date.getDate()));
    return date;
}
Copy the code

Visit /dateTimeFormat/2021-01-29 and you can see the following output:

2021-1-29 0:00:00
Copy the code

The formatting is successful. The following JSON string styles are displayed on the page:

4.3 FormatterRegistry SPI

org.springframework.format.FormatterRegistryIs the SPI interface that can be used to register formatters, and it also inheritsConverterRegistry, so you can register converters as well

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);
}
Copy the code

In fact, seen above FormatterRegistry provide registered Formatter, Parser, Printer, AnnotationFormatterFactory registered method, department will be converted to the corresponding Converter, Because the essence of their functions is the same, and Converter features all the functions of Formatter! So Formatter and Converter can be seen as different surfaces of the same underlying class:

The FormatterRegistry SPI allows us to configure formatting rules centrally, rather than copying such configurations between controllers. For example, you might want to force all date fields to be formatted in a certain way, or fields with specific annotations to be formatted in a certain way. With the shared FormatterRegistry, we only need to define these rules once, and they are used automatically when formatting is required.

FormattingConversionService is an environment suitable for most FormatTerregistry implementation. And because FormattingConversionService inherits GenericConversionService, So can directly use FormattingConversionService configure the Converter and the Formatter all together.

Spring MVC uses it by defaultDefaultFormattingConversionServiceTo implement FormattingConversionService.

4.4 FormatterRegistrar SPI

org.springframework.format.FormatterRegistrarThe Generic SPI interface to register Formatters and converters for the FormatterRegistry is similar to the PropertyEditorRegistrar we learned earlierRegister multiple Formatters and Converters at onceFormatterRegistrar is useful when registering multiple related converters and Formatters for a given format, such as a date format.

public interface FormatterRegistrar {

    /** * Register formatters and converters for FormatterRegistry **@paramRegistry Specifies the FormatterRegistry instance */ to use
    void registerFormatters(FormatterRegistry registry);

}
Copy the code

4.5 Configuring global Converters

As we mentioned in the section “Configuring DataBinder’s ConversionService”, after enabling the MVC configuration, DataBinder will default to a DefaultFormattingConversionService as conversionService, of course, we also can configure custom conversionService.

After learning Fromatter, we can use itFormattingConversionServiceFactoryBeanFactory not ConversionServiceFactoryBean as a true BeanWrapper and DataBinder share conversionService, because it supports more features, Like registering formatters and converters at the same time!

Below we provide a simple custom global conversionService configuration, through its internal FormatterRegistrar registered two AnnotationFormatterFactory instance, to look forward to realize annotation-based format conversion!

Custom two annotations:

/ * * *@author lx
 */
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD, ElementType.FIELD, ElementType.PARAMETER, ElementType.ANNOTATION_TYPE})
public @interface PersonFormat {

}
/ * * *@author lx
 */
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD, ElementType.FIELD, ElementType.PARAMETER, ElementType.ANNOTATION_TYPE})
public @interface SexFormat {

    String value(a) default "";

}
Copy the code

The Person entity, whose sex field is annotated with the @sexFormat annotation, is used to test data binding for the Spring MVC DataBinder:

/ * * *@author lx
 */
public class Person {
    private Long id;
    private String tel;
    private Integer age;
    @SexFormat
    private String sex;

    public Person(Long id, String tel, Integer age, String sex) {
        this.id = id;
        this.tel = tel;
        this.age = age;
        this.sex = sex;
    }

    public Person(a) {}public Long getId(a) {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getTel(a) {
        return tel;
    }

    public void setTel(String tel) {
        this.tel = tel;
    }

    public Integer getAge(a) {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }

    public String getSex(a) {
        return sex;
    }

    public void setSex(String sex) {
        this.sex = sex; }}Copy the code

PersonFormatter implementation, is used to transfer the front of the person the string into the person, we agreed in the passed person to use “|” string separated:

/ * * *@author lx
 */
public class PersonFormatter implements Formatter<Person> {

    @Override
    public Person parse(String text, Locale locale) throws ParseException {
        String[] split = text.split("\ \ |");
        if (split.length == 4) {
            return new Person(Long.valueOf(split[0]), split[1], Integer.valueOf(split[2]), split[3]);
        }
        throw new ParseException("Parameter format is incorrect:" + text, 0);
    }

    @Override
    public String print(Person object, Locale locale) {
        returnobject.toString(); }}Copy the code

Implementation of SexFormatter to convert a gender number to a gender string:

public class SexFormatter implements Formatter<String> {
    private static final String MAN = "Male";
    private static final String WOMAN = "Female";
    private static final String OTHER = "Unknown";


    @Override
    public String parse(String text, Locale locale) {
        if ("0".equals(text)) {
            return MAN;
        }
        if ("1".equals(text)) {
            return WOMAN;
        }
        return OTHER;
    }

    @Override
    public String print(String object, Locale locale) {
        return object;
    }


    public static class WomanFormatter extends SexFormatter {

        @Override
        public String parse(String text, Locale locale) {
            returnWOMAN; }}public static class ManFormatter extends SexFormatter {

        @Override
        public String parse(String text, Locale locale) {
            returnMAN; }}}Copy the code

The realization of the PersonAnnotationFormatterFactory:

/ * * *@author lx
 */
@Component
public class PersonAnnotationFormatterFactory implements AnnotationFormatterFactory<PersonFormat> { Set<Class<? >> classSet = Collections.singleton(Person.class);@Override
    publicSet<Class<? >> getFieldTypes() {return classSet;
    }

    @Override
    publicParser<? > getParser(PersonFormat annotation, Class<? > fieldType) {return configureFormatterFrom(annotation);
    }

    @Override
    publicPrinter<? > getPrinter(PersonFormat annotation, Class<? > fieldType) {return configureFormatterFrom(annotation);
    }

    private Formatter<Person> configureFormatterFrom(PersonFormat annotation) {
        return newPersonFormatter(); }}Copy the code

The realization of the SexAnnotationFormatterFactory:

/ * * *@author lx
 */
@Component
public class SexAnnotationFormatterFactory implements AnnotationFormatterFactory<SexFormat> { Set<Class<? >> classSet = Collections.singleton(String.class);@Override
    publicSet<Class<? >> getFieldTypes() {return classSet;
    }

    @Override
    publicParser<? > getParser(SexFormat annotation, Class<? > fieldType) {return configureFormatterFrom(annotation);
    }

    @Override
    publicPrinter<? > getPrinter(SexFormat annotation, Class<? > fieldType) {return configureFormatterFrom(annotation);
    }

    private Formatter<String> configureFormatterFrom(SexFormat annotation) {
        String value = annotation.value();
        if ("0".equals(value)) {
            return new SexFormatter.ManFormatter();
        }
        if ("1".equals(value)) {
            return new SexFormatter.WomanFormatter();
        }
        return newSexFormatter(); }}Copy the code

A Controller Controller used to test the Spring MVC DataBinder with the @sexFormat annotation inside the sex property, which is used to test Spring’s BeanWrapper:

/ * * *@author lx
 */
@RestController
public class AnnotationFormatterFactoryController {

    /* Data conversion for testing DataBinder */


    @RequestMapping("/annotationFormatterFactory/{person}")
    @ResponseBody
    public Person annotationFormatterFactory(@PersonFormat Person person, @SexFormat String sex) {
        System.out.println(sex);
        return person;
    }

    /* Used to test data conversion to BeanWrapper */

    @SexFormat("2")
    @Value("1")
    private String sex;

    @PostConstruct
    public void test(a) { System.out.println(sex); }}Copy the code

A custom FormatterRegistrar, two custom AnnotationFormatterFactory registered in FormatterRegistry:

/ * * *@author lx
 */
@Component
public class CustomFormatterRegistrar implements FormatterRegistrar {

    @Resource
    private PersonAnnotationFormatterFactory personAnnotationFormatterFactory;
    @Resource
    private SexAnnotationFormatterFactory sexAnnotationFormatterFactory;
    
    @Override
    public void registerFormatters(FormatterRegistry registry) { registry.addFormatterForFieldAnnotation(personAnnotationFormatterFactory); registry.addFormatterForFieldAnnotation(sexAnnotationFormatterFactory); }}Copy the code

Here is the configuration file for Spring’s BeanWrapper and Spring MVC’s DataBinder to support conversionService:

<! The --conversion-service attribute specifies the bean name of the conversion service used for type conversion during field binding. -->
<! -- if not specified, the default DefaultFormattingConversionService said registration -- -- >
<mvc:annotation-driven conversion-service="conversionService"/>

<! - configuration factory, it will default to create DefaultFormattingConversionService, and supporting injection custom converters and formatters - >
<! If you name it conversionService, then both BeanWrapper and DataBinder share this conversionService-->
<bean id="conversionService" class="org.springframework.format.support.FormattingConversionServiceFactoryBean">
    <! - injection custom formatters, support the Formatter and AnnotationFormatterFactory instance -- >
    <property name="formatters">
        <set/>
    </property>
    <! Converters, ConverterFactory, GenericConverter
    <property name="converters">
        <set/>
    </property>
    <! Inject custom formatterRegistrars-->
    <property name="formatterRegistrars">
        <set>
            <ref bean="customFormatterRegistrar"/>
        </set>
    </property>
</bean>
Copy the code

When we start the project, we see the “female” character output saying that our configured conversionService has successfully converted the property data to Spring BeanWrapper!

Access/annotationFormatterFactory / 1234134 | 123456 | | 1 11, the results are as follows:

The Spring MVC DataBinder test successfully converted the Person instance from the specified format string, but found that the internal sex property is still 1, not converted to the sex string. Why? When Spring MVC requests data binding, only one complete variable (URI path variable, request parameter, request body data) can be converted to an overall annotation-driven type. For example, the person path variable above can be converted to a person object successfully, but some data inside the variable does not support annotation-driven type conversion. For example, the gender data identifier “1” above is inside the person variable string, so annotation-driven conversions cannot be applied!

If you want to process, you can add a separate request parameter “sex”. Access/annotationFormatterFactory / 1234134 | 123456 | | 1? Sex =1 and the result is as follows:

Successful conversion of internal attributes!

5 HttpMessageConverter

HttpMessageConverter Spring 3.0 also joined a Converter, but it does not belong to org. Springframework. Core. Convert system, But the org. Springframework. HTTP. The converter package, it will not be involved in BeanWrapper, DataBinder, SPEL type conversion in the operation, It is often used in data transformation between HTTP clients (such as RestTemplate) and servers (such as Spring MVC Restful controllers) as described below:

  1. In the Spring MVC controller method, if you use@requestBody, HttpEntity<B>, @requestPartWill be used when the request parameters are setHttpMessageConverterComplete the request body (request body) to the method parameter type conversion, and the conversion operation here.
  2. In the Spring MVC controller method, if you useResponseBody, HttpEntity<B>, ResponseBodyEmitter, SseEmitterResponseEntity<B>When the response is set, it will be usedHttpMessageConverterPerform the conversion on the returned entity object and write the response body.
  3. Through theRestTemplateWhen a remote HTTP call is made, it will passHttpMessageConverterConverts the body of the response returned by the call to an object of the specified type. The developer can retrieve the converted object directly.

The transformation for the request body occurs before the DataBinder is created. What HttpMessageConverter is used by the body of a request or response to convert, depending on the media type (MIME) in the request or response, Spring MVC already provides implementations of HttpMessageConverter for the main media types, and by default, HttpMessageConverter registered in RequestMappingHandlerAdapter RestTemplate for the client and the server side.

All converters support their own default media types, which can be overridden by setting the supportedMediaTypes property.

The following is a common implementation of HttpMessageConverter with an introduction:

MessageConverter describe
StringHttpMessageConverter String data can be read and written from HTTP requests and responses. By default, this converter supports all media types (/), and write using the content-type text/plain Content Type.
FormHttpMessageConverter Form data can be read and written from HTTP requests and responses. By default, this converter supports reading and writing application/ X-www-form-urlencoded media type MultiValueMap<String, String>. It also supports writing “multipart/ form-data “and “multipart/mixed” media types MultiValueMap<String, Object> by default, but cannot read the data requested by these two media types, that is, file upload cannot be supported. If you want to support multipart/form-data media type requests, configure the MultipartResolver component
ByteArrayHttpMessageConverter Byte [] byte arrays can be read and written from HTTP requests and responses. By default, this converter supports all media types (/), and write using the content-type of application/octet-stream. This can be overridden by setting the supported media type property.
MarshallingHttpMessageConverter XML data can be read and written from HTTP requests and responses via Marshaller and Unmarshaller in the org.Spring Framework.oxm package. By default, this converter supports text/ XML and Application/XML.
MappingJackson2HttpMessageConverter The most common one is Converter. Json data can be read and written from HTTP requests and responses via Jackson 2.x ObjectMapper. You can use the annotations provided by Jackson to customize JSON mapping rules (such as @jsonView) as needed. When you need further control over JSON serialization and deserialization rules, you can customize the ObjectMapper implementation and inject it through the ObjectMapper property of the Converter. By default, this converter supports Application/JSON.
MappingJackson2XmlHttpMessageConverter XML data can be read and written from HTTP requests and responses via THE Jackson XML extension XmlMapper. You can customize XML mapping rules as needed using annotations provided by JAXB or Jackson. When you need further control over XML serialization and deserialization rules, you can customize the implementation of XmlMapper and inject it through the objectMapper attribute of this Converter. By default, this converter supports Application/XML.
SourceHttpMessageConverter Can read and write from the HTTP request and response javax.mail. XML. The transform. The Source data, support only DOMSource, SAXSource and StreamSource type. By default, this converter supports text/ XML and Application/XML.
BufferedImageHttpMessageConverter Can read and write from the HTTP request and response. Java awt. Image. BufferedImage data. By default, this converter can read all media types returned by the ImageIO#getReaderMIMETypes() method and write using the first available media type returned by the ImageIO#getWriterMIMETypes() method.

5.1 configured MessageConverter

After enabling MVC configuration through annotations and JavaConfig, we can override itConfigureMessageConverters methodTo replace the default converter created by Spring MVC, such as configuring the FastJson converter, or overrideExtendMessageConverters methodTo extend or modify the converter!

In the figureAddDefaultHttpMessageConverters methodIt is used to register the default converter, and as you can see from the source code for this method, it is automatically registered if there is a Jackson dependencyMappingJackson2HttpMessageConverter:

The JavaConfig configuration is as follows:

/ * * *@author lx
 */
@Configuration
@EnableWebMvc
public class WebConfig implements WebMvcConfigurer {

    @Override
    public void configureMessageConverters(List
       
        > converters)
       > {
        Jackson2ObjectMapperBuilder builder = new Jackson2ObjectMapperBuilder()
                .indentOutput(true)
                .dateFormat(new SimpleDateFormat("yyyy-MM-dd"));
        converters.add(newMappingJackson2HttpMessageConverter(builder.build())); }}Copy the code

This represents our custom configurationMappingJackson2HttpMessageConverterOnly “is supported when serializing and deserializing dates.yyyy-MM-dd“Format.

The following XML configuration can achieve the same effect as JavaConfg configuration:

<mvc:annotation-driven>
    <! Configure a custom message converter -->
    <mvc:message-converters>
        <bean class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter">
            <! - the configuration objectMapper -- -- >
            <property name="objectMapper">
                <bean class="org.springframework.http.converter.json.Jackson2ObjectMapperFactoryBean"
                      p:indentOutput="true"
                      p:simpleDateFormat="yyyy-MM-dd"/>
            </property>
        </bean>
    </mvc:message-converters>
</mvc:annotation-driven>
Copy the code

JavaConfg configuration is recommended.

DataBinder for Spring MVC

And what we introduced earlier@ModelAttributeThe method is similar to Spring MVC@InitBinderThe method also executes before the controller method and also supports many of the same parameters as the @requestMapping controller method. However, they are commonly usedWebDataBinderParameter (used to register type converters) and return the value void.

Similar to the @ModelAttribute method introduced earlier, the @Controller and @ControllerAdvice classes can define @initBinder methods. The @initBinder method in the @Controller class supports requests from the current class by default. Methods in the @controllerAdvice class support all requests by default!

The @initbinder method does the following:

  1. Binding request parameters (that is, form or query data) to a Model object is similar to @ModelAttribute, but is not its primary function.
  2. Type converters are used to convert string-based request values (such as request parameters, path variables, request headers, cookies, etc.) to target types for controller method parameters by registering type converters.

For each request, a WebDataBinder object is created within the HandlerAdapter handler method:

After you create the WebDataBinder, you will try to add the preconfiguredconversionServiceIf theSpring MVC configuration is enabled (via the @enableWebMVC annotation or the < MVC: Annotation-driven /> tag)If the conversionService configuration is not changed, the default is a DefaultFormattingConversionService, which internally encapsulates the common globally available Converter and Formarter (the Formatter will be converted to Converter). The Converter in this conversionService isGlobally available!

It then calls back all @initbinder methods that comply with the rules, We can then register our custom PropertyEditor and Formatter converter (which will be converted to PropertyEditor) for the current request in the WebDataBinder object in this method, But the converter registered here binds the value to the Binder object itself, which means that the converter registered with WebDataBinder’s @initBinder method on each request is not reusable.

When parsing, first use the registered local PropertyEditor and then use the global conversionService!

In the following case, there is a Controller:

@RestController
public class InitBinderController {

    @GetMapping("/initBinder")
    public void handle(Date date) { System.out.println(date); }}Copy the code

If we access /initBinder? Date =2012/12/12

Wed Dec 12 00:00:00 CST 2012
Copy the code

As you can see, it is possible to convert strings to time because the default conversionService provides a Converter of this format of string to Date. What if we tried a different format?

If we access /initBinder? Date =2012-12-12, the result is as follows:

This throws an exception directly! Since the string format does not match, we can now customize the type converters. As mentioned above, we can define three converters. If registered in the @initBinder method, we can register the PropertyEditor and Formatter converters!

Both PropertyEditor and Formatter already provide their own implementations for converting time strings to dates, so we can just pass in the given string pattern, or we can do it ourselves!

If we register PropertyEditor, we declare:

@RestController
public class InitBinderController {

    @InitBinder
    public void initBinder(WebDataBinder binder) {
        System.out.println("----initBinder------");
        // Register a custom PropertyEditor
        // The first argument represents the type of the converted property, and the second argument is an instance of the custom PropertyEditor
        // This CustomDateEditor is Spring's built-in PropertyEditor for formatting time strings. We just need to set the mode of the time string
        binder.registerCustomEditor(Date.class, new CustomDateEditor(new SimpleDateFormat("yyyy-MM-dd"), false));
    }

    @GetMapping("/initBinder")
    public void handle(Date date) { System.out.println(date); }}Copy the code

Access /initBinder again? Date =2012-12-12, the result is as follows:

----initBinder------
Wed Dec 12 00:00:00 CST 2012
Copy the code

Successful conversion!

If we register a Formatter, we declare:

@InitBinder
public void initBinder(WebDataBinder binder) {
    System.out.println("----initBinder------");
    // Register a custom Formatter
    // This DateFormatter is Spring's built-in Formatter for formatting time
    // Just set the mode of the time string in the constructor argument. This is not set because the DateFormatter has built-in local mode support for parsing YYYY-MM-DD
    binder.addCustomFormatter(new DateFormatter());
}
Copy the code

Access /initBinder again? Date =2012-12-12, the result is as follows:

----initBinder------
Wed Dec 12 00:00:00 CST 2012
Copy the code

The conversion was also successful!

Related articles:

  1. spring.io/
  2. Spring Framework 5.x learning
  3. Spring Framework 5.x source code

If you need to communicate, or the article is wrong, please leave a message directly. In addition, I hope to like, collect, pay attention to, I will continue to update a variety of Java learning blog!