“This is the 28th day of my participation in the Gwen Challenge in November. Check out the details: The Last Gwen Challenge in 2021.”
This article was included in the column “Spring Boot Actual Combat”.
Hello, I’m looking at the mountains.
As we discussed in Principles for Elegant Use of enumerated Parameters, Spring uses different processing classes for different parameter forms, which are somewhat similar to policy patterns. Split the processing logic for different parameter forms into different processing classes, reducing coupling and various if-else logic. This article will take a look at how enumeration parameters are used in the RequestBody parameter.
To find the entrance
For those of you who are familiar with Spring, the request entry is DispatcherServlet, All requests end up in the ha.handle(processedRequest, Response, mappedHandler.gethandler ()) logic of the doDispatch method. We start here, layer by layer, and work our way in.
Following code, we will find org. Springframework. Web. Method. Support. InvocableHandlerMethod# invokeForRequest logic:
public Object invokeForRequest(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer,
Object... providedArgs) throws Exception {
Object[] args = getMethodArgumentValues(request, mavContainer, providedArgs);
if (logger.isTraceEnabled()) {
logger.trace("Arguments: " + Arrays.toString(args));
}
return doInvoke(args);
}
Copy the code
As you can see, the parameter is handled by the getMethodArgumentValues method, and then the doInvoke method is called to get the return value. GetMethodArgumentValues within a method and processing parameters through HandlerMethodArgumentResolverComposite instance. This class is a HandlerMethodArgumentResolver instance list, the list is a collection of Spring processing parameter logic, follow the Debug code, you can see there are 27 elements. These classes can also be custom extended to implement their own parameter parsing logic, which I’ll cover later.
Choose the Resolver
This Resolver list contains several commonly used processing classes. Get request of ordinary parameters by RequestParamMethodArgumentResolver processing, wrapper classes by ModelAttributeMethodProcessor processing parameters, RequestBody form of parameters, By RequestResponseBodyMethodProcessor processing parameters. This is the use of the strategy pattern in the Spring, by implementing the org. Springframework. Web. Method. Support. HandlerMethodArgumentResolver# supportsParameter method, Determines whether the input parameters can be parsed. The realization of the labeled RequestResponseBodyMethodProcessor below:
public boolean supportsParameter(MethodParameter parameter) {
return parameter.hasParameterAnnotation(RequestBody.class);
}
Copy the code
As you can see, RequestResponseBodyMethodProcessor is judged by judging whether parameters with RequestBody annotations, whether can parse the current parameters.
Analytical parameters
RequestResponseBodyMethodProcessor inherited from AbstractMessageConverterMethodArgumentResolver, True parsing RequestBody parameters of logic in the org. Springframework. Web. Servlet. MVC) method. The annotation. AbstractMessageConverterMethodArgumentResolve R# readWithMessageConverters method. Let’s take a look at the source code (because the source code is quite long, only the core logic is left in the article). :
protected <T> Object readWithMessageConverters(HttpInputMessage inputMessage, MethodParameter parameter, Type targetType) throws IOException, HttpMediaTypeNotSupportedException, HttpMessageNotReadableException {
MediaType contentType = inputMessage.getHeaders().getContentType();/ / 1Class<? > contextClass = parameter.getContainingClass();/ / 2
Class<T> targetClass = (targetType instanceof Class ? (Class<T>) targetType : null);/ / 3
Object body = NO_VALUE;
EmptyBodyCheckingHttpInputMessage message = new EmptyBodyCheckingHttpInputMessage(inputMessage);/ / 4
for(HttpMessageConverter<? > converter :this.messageConverters) {/ / 5Class<HttpMessageConverter<? >> converterType = (Class<HttpMessageConverter<? >>) converter.getClass(); GenericHttpMessageConverter<? > genericConverter = (converterinstanceofGenericHttpMessageConverter ? (GenericHttpMessageConverter<? >) converter :null);
if(genericConverter ! =null? genericConverter.canRead(targetType, contextClass, contentType) : (targetClass ! =null && converter.canRead(targetClass, contentType))) {
if(message.hasBody()) { HttpInputMessage msgToUse = getAdvice().beforeBodyRead(message, parameter, targetType, converterType); body = (genericConverter ! =null ? genericConverter.read(targetType, contextClass, msgToUse) :
((HttpMessageConverter<T>) converter).read(targetClass, msgToUse));/ / 6
body = getAdvice().afterBodyRead(body, msgToUse, parameter, targetType, converterType);
}
else {
body = getAdvice().handleEmptyBody(null, message, parameter, targetType, converterType);
}
break; }}return body;
}
Copy the code
Follow the code to explain the purpose of each part:
- Gets the request Content-type
- Gets the parameter container class
- Gets the target parameter type
- Convert the request parameters to
EmptyBodyCheckingHttpInputMessage
type - Loop through various RequestBody parameter parsers, all of which are
HttpMessageConverter
The implementation class of the interface. Spring covers every situation, and there’s always one for you. At the end of the article is givenHttpMessageConverter
A class diagram of each extended class. - The body of the for loop is to select the appropriate one and parse it
- First call
canRead
Method to check whether it is available - Determine whether the request request parameter is null, and if so, pass the AOP
advice
Process the empty request body and return - Not null, first through AOP
advice
Do the pre-processing and then callread
Method to convert an object in the passadvice
Do the post-processing
- First call
Spring’S AOP is outside the scope of this article, so I’ll skip it. Special notes will follow.
This case, the HttpMessageConverter is using MappingJackson2HttpMessageConverter, this class inherits from AbstractJackson2HttpMessageConverter. As the name indicates, this class uses Jackson to handle request parameters. After the read method, the internal private readJavaType method is called. The core logic of this method is given below:
private Object readJavaType(JavaType javaType, HttpInputMessage inputMessage) throws IOException {
MediaType contentType = inputMessage.getHeaders().getContentType();/ / 1
Charset charset = getCharset(contentType);
ObjectMapper objectMapper = selectObjectMapper(javaType.getRawClass(), contentType);/ / 2Assert.state(objectMapper ! =null."No ObjectMapper for " + javaType);
boolean isUnicode = ENCODINGS.containsKey(charset.name()) ||
"UTF-16".equals(charset.name()) ||
"UTF-32".equals(charset.name());/ / 3
try {
if (isUnicode) {
return objectMapper.readValue(inputMessage.getBody(), javaType);/ / 4
} else {
Reader reader = new InputStreamReader(inputMessage.getBody(), charset);
returnobjectMapper.readValue(reader, javaType); }}catch (InvalidDefinitionException ex) {
throw new HttpMessageConversionException("Type definition error: " + ex.getType(), ex);
}
catch (JsonProcessingException ex) {
throw new HttpMessageNotReadableException("JSON parse error: "+ ex.getOriginalMessage(), ex, inputMessage); }}Copy the code
Follow the code to explain the purpose of each part:
- Fetch requested
content-type
This is the extension logic implemented by Spring, depending on thecontent-type
You can choose different onesObjectMapper
Instance. That’s the logic of step 2 - According to the
content-type
And target type, selectObjectMapper
Instance. In this case, the direct return is the default, which is passedJackson2ObjectMapperBuilder.cbor().build()
Method created by. - Check if the request is a Unicode character, which is what everyone currently uses
UTF-8
the - through
ObjectMapper
Convert the request JSON to an object. There’s actually a judgment hereinputMessage
Whether it isMappingJacksonInputMessage
For example, given the version you’re using, I won’t mention that.
At this point, the Spring logic is all over, and we still don’t seem to have found the JsonCreator annotation or JsonDeserialize logic we used. But you can also imagine that these are both Jackson’s classes, so that must be Jackson’s logic. Next, take a look at Jackson’s conversion logic.
Dig into Jackson’s ObjectMapper logic
With Jackson’s logic are mainly distributed in AbstractMessageConverterMethodArgumentResolver# readWithMessageConverters and ObjectMapper# readValue these two methods. Take a look at the logic of the ObjectMapper#readValue method, which calls the GenderIdCodeEnum#create method to perform the type conversion.
The ObjectMapper#readValue method directly calls the _readMapAndClose method in the current class, Ctxt. readRootValue(p, valueType, _findRootDeserializer(CTXT, valueType), null). This method converts the input JSON to an object. DeserializeFromObject = BeanDeserializer = BeanDeserializer = deserializeFromObject = BeanDeserializer = BeanDeserializer = BeanDeserializer = BeanDeserializer = BeanDeserializer = BeanDeserializer = BeanDeserializer = BeanDeserializer = BeanDeserializer = BeanDeserializer = BeanDeserializer = BeanDeserializer = BeanDeserializer
public Object deserializeFromObject(JsonParser p, DeserializationContext ctxt) throws IOException {
_valueInstantiator is an instance of StdValueInstantiator.
final Object bean = _valueInstantiator.createUsingDefault(ctxt);
// [databind#631]: Assign current value, to be accessible by custom deserializers
p.setCurrentValue(bean);
if (p.hasTokenId(JsonTokenId.ID_FIELD_NAME)) {
String propName = p.currentName();
do {
p.nextToken();
// Find the property object based on the field name. For the gender field, the type is MethodProperty.
SettableBeanProperty prop = _beanProperties.find(propName);
if(prop ! =null) { // normal case
try {
// Start decoding and write the decoding result to the object
prop.deserializeAndSet(p, ctxt, bean);
} catch (Exception e) {
wrapAndThrow(e, bean, propName, ctxt);
}
continue;
}
handleUnknownVanilla(p, ctxt, bean, propName);
} while((propName = p.nextFieldName()) ! =null);
}
return bean;
}
Copy the code
Let’s look at the logic for MethodProperty#deserializeAndSet (leaving only the key code) :
public void deserializeAndSet(JsonParser p, DeserializationContext ctxt, Object instance) throws IOException {
Object value;
/ / call FactoryBasedEnumDeserializer instance decoding method
value = _valueDeserializer.deserialize(p, ctxt);
// Write values to objects by reflection
_setter.invoke(instance, value);
}
Copy the code
Which is _valueDeserializer FactoryBasedEnumDeserializer instance, almost close to the target, look at this piece of logic:
public Object deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
// Get the value in json
Object value = _deser.deserialize(p, ctxt);
// Call the GenderIdCodeEnum#create method
return _factory.callOnWith(_valueClass, value);
}
Copy the code
_factory is an AnnotatedMethod instance, which is essentially a wrapper around a method defined in the JsonCreator annotation, and then calls the Java.lang.reflect. Method#invoke reflection method in callOnWith, Perform GenderIdCodeEnum# create.
At this point, we finally string together all the logic.
At the end of the article to summarize
This article uses an example to string together the @JsonCreator annotation logic in action. The logic of the JsonDeserializer interface and its type can be debug patiently. The class diagram for the main classes is shown below:
Recommended reading
- SpringBoot: elegant response to achieve results in one move
- SpringBoot: How to handle exceptions gracefully
- SpringBoot: Dynamically injects the ID generator through the BeanPostProcessor
- SpringBoot: Customizes Filter to gracefully obtain request parameters and response results
- SpringBoot: Elegant use of enumeration parameters
- SpringBoot: Elegant use of enumeration parameters (Principles)
- SpringBoot: Gracefully use enumeration parameters in the RequestBody
- SpringBoot: Gracefully using enumeration parameters in the RequestBody
- Do unit tests with JUnit5+MockMvc+Mockito
- SpringBoot: Loads and reads resource files
Hello, I’m looking at the mountains. Swim in the code, play to enjoy life. If this article is helpful to you, please like, bookmark, follow. Welcome to follow the public account “Mountain Hut”, discover a different world.