>>>> 😜😜😜 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…