>>>> 😜😜😜 Github: 👉 github.com/black-ant CASE Backup: 👉 gitee.com/antblack/ca…

A. The preface

This article will focus on just one small point and learn how SpringMVC does data transformation.

Ii. Data undertaking

2.1 Common Usage of data Conversion

@JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd")
private Date createDate;

@JsonIgnore
private String ignoreField;

@JSONField(name = "age")
private String testAge;
Copy the code

For example, fasterXML is outsourced, but SpringMVC integrates it, so how does that function work?

2.2 Data conversion source comb

JSON conversion process is mainly the HttpMessageConverter module, first take a look at the previous flow chart

As you can see, will first through HandlerMethodArgumentResolverComposite to parse attributes through HandlerMethodReturnValueHandlerComposite to parse of return, They will be unified by AbstractMessageConverterMethodArgumentResolver processing.

2.2.1 Loading and initialization of MessageConverter

// Note that this class is in Spring AutoConfigure and is not exclusive to MVC
private static class MessageConverterInitializer implements Runnable {

   @Override
   public void run(a) {
      // A corresponding FormHttpMessageConverter extension was created here to add support for XML and JSON-based widgets
      newAllEncompassingFormHttpMessageConverter(); }}// There are a variety of parsed classes preloaded
public AllEncompassingFormHttpMessageConverter(a) {

   addPartConverter(new SourceHttpMessageConverter<>());
   
   // JAXB stands for Java Architecture for XML Binding and can convert a Java object into AN XML format and vice versa
   if(jaxb2Present && ! jackson2XmlPresent) { addPartConverter(new Jaxb2RootElementHttpMessageConverter());
   }
   // Handle JSON and XML formatting libraries
   if (jackson2Present) {
      addPartConverter(new MappingJackson2HttpMessageConverter());
   }
   
   // Gson is a Java serialization/deserialization library that supports JSON -- Java Object conversions
   else if (gsonPresent) {
      addPartConverter(new GsonHttpMessageConverter());
   }
   // Unlike JSON, jsonb is saved in binary format
   //- Jsonb usually takes up more disk space than JSON (in some cases not)
   //- Jsonb takes longer to write than JSON
   //- json operations are significantly more time-consuming than jsonb operations (handling a json value requires parsing every time)
   else if (jsonbPresent) {
      addPartConverter(new JsonbHttpMessageConverter());
   }
   // Jackson is not a value handler for JSON, this converter is used to read and write xmL-encoded data extensions
   if (jackson2XmlPresent) {
      addPartConverter(new MappingJackson2XmlHttpMessageConverter());
   }
   // Can read and write Smile data format
   if (jackson2SmilePresent) {
      addPartConverter(new MappingJackson2SmileHttpMessageConverter());
   }
}

- jaxb2Present = ClassUtils.isPresent("javax.xml.bind.Binder", classLoader);
- jackson2Present = ClassUtils.isPresent("com.fasterxml.jackson.databind.ObjectMapper", classLoader) &&
               ClassUtils.isPresent("com.fasterxml.jackson.core.JsonGenerator", classLoader);
- jackson2XmlPresent = ClassUtils.isPresent("com.fasterxml.jackson.dataformat.xml.XmlMapper", classLoader);
- jackson2SmilePresent = ClassUtils.isPresent("com.fasterxml.jackson.dataformat.smile.SmileFactory", classLoader);
- gsonPresent = ClassUtils.isPresent("com.google.gson.Gson", classLoader);
- jsonbPresent = ClassUtils.isPresent("javax.json.bind.Jsonb", classLoader);
Copy the code

2.2.2 Entrance of transformation

MessageConvert transformation at the core of the entry for AbstractMessageConverterMethodArgumentResolver, look at the processing logic (relevant code has seen before, Here is a partial excerpt -> juejin.cn/post/696584…)

// C- AbstractMessageConverterMethodArgumentResolver # readWithMessageConverters

// Add one: messageConverters list
// - org.springframework.http.converter.ByteArrayHttpMessageConverter
// - org.springframework.http.converter.StringHttpMessageConverter
// - org.springframework.http.converter.ResourceHttpMessageConverter
// - org.springframework.http.converter.ResourceRegionHttpMessageConverter
// - org.springframework.http.converter.xml.SourceHttpMessageConverter
// - org.springframework.http.converter.support.AllEncompassingFormHttpMessageConverter
// - org.springframework.http.converter.xml.Jaxb2RootElementHttpMessageConverter
// - org.springframework.http.converter.json.MappingJackson2HttpMessageConverter

for(HttpMessageConverter<? > converter :this.messageConverters) {

   // Get Converter classClass<HttpMessageConverter<? >> converterType = (Class<HttpMessageConverter<? >>) converter.getClass();/ / if it is GenericHttpMessageConverter need for conversion
   / / GenericHttpMessageConverter interface inherits the HttpMessageConverter interfaceGenericHttpMessageConverter<? > genericConverter = (converterinstanceofGenericHttpMessageConverter ? (GenericHttpMessageConverter<? >) converter :null);
         
   // Determine if the Converter can process the data
   if(genericConverter ! =null? genericConverter.canRead(targetType, contextClass, contentType) : (targetClass ! =null && converter.canRead(targetClass, contentType))) {
         
      // Converter mainly handles RequestBody and ResponseBody data
      if (message.hasBody()) {
      
         / / pre processing operations, mainly Advice - > JsonViewRequestBodyAdvice
         HttpInputMessage msgToUse =getAdvice().beforeBodyRead(message, parameter, targetType, converterType);
         
         // Core processing, carry out conversion parsing operationsbody = (genericConverter ! =null ? genericConverter.read(targetType, contextClass, msgToUse) :
               ((HttpMessageConverter<T>) converter).read(targetClass, msgToUse));
               
         // post-processing
         body = getAdvice().afterBodyRead(body, msgToUse, parameter, targetType, converterType);
      }
      else {
         body = getAdvice().handleEmptyBody(null, message, parameter, targetType, converterType);
      }
      break; }}Copy the code

The Converter has a lot of kinds, here in AbstractJackson2HttpMessageConverter, for example, its downstream processing into:

  • Step 1: AbstractJackson2HttpMessageConverter # read: enter the Converter parsing operations
  • Step 2: AbstractJackson2HttpMessageConverter # readJavaType: access to the Java type
  • Step 3: CollectionDeserializer # deserialize: Enable transcode parsing
  • Step 4: BeanDeserializer # deserializeFromObject
  • Step 5: deserializeAndSet: parse and set data

Step 4: Loop through the Object Param

The main operation is to start with deserializeFromObject, where the value is resolved to a Bean

public Object deserializeFromObject(JsonParser p, DeserializationContext ctxt) throws IOException
{
    
    Does the ObjectIdReader object know how to deserialize the object ID
    // If TokenId is represented as an attribute name, this parameter is handled directly because no additional serialization is required
    if((_objectIdReader ! =null) && _objectIdReader.maySerializeAsObject()) {
        if (p.hasTokenId(JsonTokenId.ID_FIELD_NAME)
                && _objectIdReader.isValidReferencePropertyName(p.getCurrentName(), p)) {
            returndeserializeFromObjectId(p, ctxt); }}// If the JVM does not add a default no-argument constructor to the entity class, this is true
    if (_nonStandardCreation) {
        // If one of the properties has a value of "unwrapped", a separate helper object is required
        // PS : 
        if(_unwrappedPropertyHandler ! =null) {
            return deserializeWithUnwrapped(p, ctxt);
        }
        
        // The attribute uses an external type ID
        if(_externalTypeIdHandler ! =null) {
            return deserializeWithExternalTypeId(p, ctxt);
        }
        Object bean = deserializeFromObjectUsingNonDefault(p, ctxt);
        if(_injectables ! =null) {
            injectValues(ctxt, bean);
        }

        return bean;
    }
    
    // Create a Bean corresponding to the current Body using the default constructor
    final Object bean = _valueInstantiator.createUsingDefault(ctxt);
    // Set the Bean into the container for subsequent serialization processing
    p.setCurrentValue(bean);
    if (p.canReadObjectId()) {
        Object id = p.getObjectId();
        if(id ! =null) { _handleTypedObjectId(p, ctxt, bean, id); }}if(_injectables ! =null) {
        injectValues(ctxt, bean);
    }
    
    // Indicates certain aspects of deserialization depending on the active view being used
    if (_needViewProcesing) {
        TODO: This seems like an interesting placeClass<? > view = ctxt.getActiveView();if(view ! =null) {
            returndeserializeWithView(p, ctxt, bean, view); }}if (p.hasTokenId(JsonTokenId.ID_FIELD_NAME)) {
        
        // Get the attribute name
        String propName = p.getCurrentName();
        
        // core: loop over attributes
        do {
            / / iteration token
            p.nextToken();
            
            // Get the metadata parameter corresponding to the current attribute
            SettableBeanProperty prop = _beanProperties.find(propName);
            if(prop ! =null) { // normal case
                try {
                    // Parse and set
                    prop.deserializeAndSet(p, ctxt, bean);
                } catch (Exception e) {
                    wrapAndThrow(e, bean, propName, ctxt);
                }
                continue;
            }
            
            // Process objects that cannot be parsed
            handleUnknownVanilla(p, ctxt, bean, propName);
        } while((propName = p.nextFieldName()) ! =null);
    }
    return bean;
}

// PS: What is TokenID?
// TokenID is an enumeration of the basic tag types used to return results, since the data in JSON is unformatted,
JsonTokenId contains the following types:
public final static int ID_NO_TOKEN = 0;
public final static int ID_START_OBJECT = 1;
public final static int ID_END_OBJECT = 2;
public final static int ID_START_ARRAY = 3;
public final static int ID_END_ARRAY = 4;
/ / the property name
public final static int ID_FIELD_NAME = 5;
public final static int ID_STRING = 6; / / string
public final static int ID_NUMBER_INT = 7; // INT
public final static int ID_NUMBER_FLOAT = 8; // Float 
public final static int ID_TRUE = 9;
public final static int ID_FALSE = 10;
public final static int ID_NULL = 11;
public final static int ID_EMBEDDED_OBJECT = 12;
Copy the code

Step 5: Data conversion and setup

public void deserializeAndSet(JsonParser p, DeserializationContext ctxt,Object instance) throws IOException{

    Object value;
    
    // null is worth processing
    if (p.hasToken(JsonToken.VALUE_NULL)) {
        if (_skipNulls) {
            return;
        }
        value = _nullProvider.getNullValue(ctxt);
    } else if (_valueTypeDeserializer == null) {
        // The value has been converted, and there is a corresponding serialized class
        // - DateDeserializers
        value = _valueDeserializer.deserialize(p, ctxt);
        
        // 04-May-2018, tatu: [databind#2023] Coercion from String (mostly) can give null
        if (value == null) {
            if (_skipNulls) {
                return;
            }
            // If the processing is still null, then null is worth processingvalue = _nullProvider.getNullValue(ctxt); }}else {
        value = _valueDeserializer.deserializeWithType(p, ctxt, _valueTypeDeserializer);
    }
    try {
    
        // Finally: reflection is set to setter methods
        // This is also why the value will not be set if the setter does not exist
        _setter.invoke(instance, value);
    } catch(Exception e) { _throwAsIOE(p, e, value); }}Copy the code

Step 6: Serialize the concrete classes

If you’re interested in DateDeserializers, there are plenty more deserializers in there

// C- DateDeserializers
protected java.util.Date _parseDate(JsonParser p, DeserializationContext ctxt)throws IOException
    {
        if(_customFormat ! =null) {
            if (p.hasToken(JsonToken.VALUE_STRING)) {
                / / the 2021-10-21 14:20:55
                String str = p.getText().trim();
                if (str.length() == 0) {
                    return (Date) getEmptyValue(ctxt);
                }
                // It is locked for processing
                synchronized (_customFormat) {
                    try {
                        // Core: format handles time
                        return _customFormat.parse(str);
                    } catch (ParseException e) {
                        return (java.util.Date) ctxt.handleWeirdStringValue(handledType(), str,
                                "expected format "%s"", _formatString); }}}}// If it can't be handled, it will be handled by the parent class
        return super._parseDate(p, ctxt); }}Copy the code

When was DateFormat injected?

When attributes are added, the createContextual operation creates a container context for the object to deal with.

  • CollectionDeserializer # createContextual: Create containers
  • DeserializationContext # findContextualValueDeserializer: find the current value corresponding to the class
public staticJsonDeserializer<? > find(Class<? > rawType, String clsName) {if (_classNames.contains(clsName)) {
        // Start with the most common type
        if (rawType == Calendar.class) {
            return new CalendarDeserializer();
        }
        // Finally select the corresponding time serialization class according to the time type
        Public DateDeserializer() {super(date.class); }
        if (rawType == java.util.Date.class) {
            return DateDeserializer.instance;
        }
        if (rawType == java.sql.Date.class) {
            return new SqlDateDeserializer();
        }
        if (rawType == Timestamp.class) {
            return new TimestampDeserializer();
        }
        if (rawType == GregorianCalendar.class) {
            return newCalendarDeserializer(GregorianCalendar.class); }}return null;
}
Copy the code

The container is like a little garage, and when you’re ready to buy a car, you have all kinds of tools for your own maintenance, for your own maintenance, to solve all kinds of problems, to modify it, but this idea, in a monotonous system, is really hard to achieve

3. Data export

So how is the data exported to be transformed?

Write data is also processed through the for loop messageConverters, which is called as follows:

  • RequestMappingHandlerAdapter # handleInternal: at the moment in which invokeAndHandle method for processing
  • RequestMappingHandlerAdapter # invokeHandlerMethod :
  • ServletInvocableHandlerMethod # invokeAndHandle: ready to Return a Value
  • HandlerMethodReturnValueHandlerComposite # handleReturnValue :
  • RequestResponseBodyMethodProcessor # handleReturnValue :
  • List AbstractMessageConverterMethodProcessor # writeWithMessageConverters: dealing with the converter

In writeWithMessageConverters core processes as follows, we focus on:

for(HttpMessageConverter<? > converter :this.messageConverters) {
   GenericHttpMessageConverter genericConverter = (converter instanceofGenericHttpMessageConverter ? (GenericHttpMessageConverter<? >) converter :null);
   if(genericConverter ! =null ?
         ((GenericHttpMessageConverter) converter).canWrite(targetType, valueType, selectedMediaType) :
         converter.canWrite(valueType, selectedMediaType)) {
         
      // Step 1: Unlike read, afterWrite is not availablebody = getAdvice().beforeBodyWrite(body, returnType, selectedMediaType, (Class<? extends HttpMessageConverter<? >>) converter.getClass(), inputMessage, outputMessage);if(body ! =null) {
         Object theBody = body;
         addContentDispositionHeader(inputMessage, outputMessage);
         if(genericConverter ! =null) {
         
            // Step 2 :  
            genericConverter.write(body, targetType, selectedMediaType, outputMessage);
         }
         else{ ((HttpMessageConverter) converter).write(body, selectedMediaType, outputMessage); }}else{}return; }}Copy the code

As you can see, the Same Converter is used and the processing logic is roughly the same. The main process is divided into two parts:

  • Step 1: pre-processing, call RequestResponseBodyAdviceChain chain processing
  • Step 2: Call the specific Converter for the write operation

Advice is divided into RequestBodyAdvice and ResponseBodyAdvice

conclusion

It’s a little off topic. It’s not the core content of MVC. It’s mainly a problem that doesn’t take effect in daily use. A document is included for future use.

Jackson’s bottom layer is very interesting. There are many places that I want to go into, but I don’t have enough energy. It shows the core process of serialization

Reference documentation

Developer.aliyun.com/article/769…