In Spring MVC, two annotations @RequestBody and @ResponseBody can be used to complete the conversion of request message to object and object to response message respectively. The underlying flexible message conversion mechanism is described. The HTTP MessageConverter, which is configured by default, is used for parsing, and then the corresponding data is bound to the object to be returned.
HttpInputMessage
This class is the abstraction of a Http request message in Spring MVC. In the read() method of HttpMessageConverter, there is a parameter of HttpInputMessage, which is the internal abstraction of the "request message" of the receptor that the message converter of Spring MVC acts on. The message converter extracts the message from the "request message" according to the rules. Converts to the object declared in the method parameter.
package org.springframework.http;
import java.io.IOException;
import java.io.InputStream;
public interface HttpInputMessage extends HttpMessage {
InputStream getBody() throws IOException;
}
HttpOutputMessage
In the write() method of HttpMessageConverter, there is a parameter of HttpOutputMessage, which is the internal abstraction of the "response message" of the message converter of Spring MVC. The message converter writes the "response message" into the response message according to certain rules.
package org.springframework.http;
import java.io.IOException;
import java.io.OutputStream;
public interface HttpOutputMessage extends HttpMessage {
OutputStream getBody() throws IOException;
}
HttpMessageConverter
/*
* Copyright 2002-2010 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.http.converter;
import java.io.IOException;
import java.util.List;
import org.springframework.http.HttpInputMessage;
import org.springframework.http.HttpOutputMessage;
import org.springframework.http.MediaType;
public interface HttpMessageConverter<T> {
boolean canRead(Class<?> clazz, MediaType mediaType);
boolean canWrite(Class<?> clazz, MediaType mediaType);
List<MediaType> getSupportedMediaTypes();
T read(Class<? extends T> clazz, HttpInputMessage inputMessage)
throws IOException, HttpMessageNotReadableException;
void write(T t, MediaType contentType, HttpOutputMessage outputMessage)
throws IOException, HttpMessageNotWritableException;
}
The HttpMessageConverter interface provides five methods:
- canRead: Determines whether the converter can convert request content into Java objects
- canWrite: Determines whether the converter can convert Java objects into returned content
- getSupportedMediaTypes: The MediaType type supported by the converter
- read: read the request content and convert it into Java objects
- write: write the returned content after converting the Java object
The parameters of read and write methods are HttpInputMessage and HttpOutputMessage objects, which represent the request and response parts of a Http communication respectively. The corresponding input and output streams can be obtained by getBody method.
Currently, quite a number of converters have been provided by default in Spring, including:
Name | Effect | Reading Support MediaType | Write Support MediaType |
---|---|---|---|
ByteArrayHttpMessageConverter | Conversion between data and byte arrays | / | application/octet-stream |
StringHttpMessageConverter | Conversion between Data and String Types | text/* | text/plain |
FormHttpMessageConverter | Conversion between forms and MultiValueMap <string, string="> | application/x-www-form-urlencoded | application/x-www-form-urlencoded |
SourceHttpMessageConverter | Conversion between data and javax.xml.transform.Source | text/xml and application/xml | text/xml and application/xml |
MarshallingHttpMessageConverter | Conversion of XML data using Spring Marshaller/Unmarshaller | text/xml and application/xml | text/xml and application/xml |
MappingJackson2HttpMessageConverter | Conversion of Json data using Jackson's ObjectMapper | application/json | application/json |
MappingJackson2XmlHttpMessageConverter | Conversion of XML data using Jackson's Xml Mapper | application/xml | application/xml |
BufferedImageHttpMessageConverter | Conversion between data and java.awt.image.BufferedImage | All types supported by Java I/O API | All types supported by Java I/O API |
HttpMessageConverter matching process:
@ RequestBody annotation: According to the Content-Type type of the header part of the Request object, the appropriate HttpMessageConverter is matched one by one to read the data.
private Object readWithMessageConverters(MethodParameter methodParam, HttpInputMessage inputMessage, Class paramType) throws Exception {
MediaType contentType = inputMessage.getHeaders().getContentType();
if (contentType == null) {
StringBuilder builder = new StringBuilder(ClassUtils.getShortName(methodParam.getParameterType()));
String paramName = methodParam.getParameterName();
if (paramName != null) {
builder.append(' ');
builder.append(paramName);
}
throw new HttpMediaTypeNotSupportedException("Cannot extract parameter (" + builder.toString() + "): no Content-Type found");
}
List<MediaType> allSupportedMediaTypes = new ArrayList<MediaType>();
if (this.messageConverters != null) {
for (HttpMessageConverter<?> messageConverter : this.messageConverters) {
allSupportedMediaTypes.addAll(messageConverter.getSupportedMediaTypes());
if (messageConverter.canRead(paramType, contentType)) {
if (logger.isDebugEnabled()) {
logger.debug("Reading [" + paramType.getName() + "] as \"" + contentType + "\" using [" + messageConverter + "]");
}
return messageConverter.read(paramType, inputMessage);
}
}
}
throw new HttpMediaTypeNotSupportedException(contentType, allSupportedMediaTypes);
}
@ ResponseBody annotation: According to the Accept attribute (comma-separated) of the header part of the Request object, press the type in the accept one by one to traverse to find the HttpMessageConverter that can be processed.
private void writeWithMessageConverters(Object returnValue, HttpInputMessage inputMessage, HttpOutputMessage outputMessage)
throws IOException, HttpMediaTypeNotAcceptableException {
List<MediaType> acceptedMediaTypes = inputMessage.getHeaders().getAccept();
if (acceptedMediaTypes.isEmpty()) {
acceptedMediaTypes = Collections.singletonList(MediaType.ALL);
}
MediaType.sortByQualityValue(acceptedMediaTypes);
Class<?> returnValueType = returnValue.getClass();
List<MediaType> allSupportedMediaTypes = new ArrayList<MediaType>();
if (getMessageConverters() != null) {
for (MediaType acceptedMediaType : acceptedMediaTypes) {
for (HttpMessageConverter messageConverter : getMessageConverters()) {
if (messageConverter.canWrite(returnValueType, acceptedMediaType)) {
messageConverter.write(returnValue, acceptedMediaType, outputMessage);
if (logger.isDebugEnabled()) {
MediaType contentType = outputMessage.getHeaders().getContentType();
if (contentType == null) {
contentType = acceptedMediaType;
}
logger.debug("Written [" + returnValue + "] as \"" + contentType +
"\" using [" + messageConverter + "]");
}
this.responseArgumentUsed = true;
return;
}
}
}
for (HttpMessageConverter messageConverter : messageConverters) {
allSupportedMediaTypes.addAll(messageConverter.getSupportedMediaTypes());
}
}
throw new HttpMediaTypeNotAcceptableException(allSupportedMediaTypes);
}
Customize a Json converter
class CustomJsonHttpMessageConverter implements HttpMessageConverter {
//Jackson's Json Mapping Class
private ObjectMapper mapper = new ObjectMapper();
//The type of support for this converter: application/json
private List supportedMediaTypes = Arrays.asList(MediaType.APPLICATION_JSON);
/**
* Determine whether the converter can convert input into Java type
* @param clazz Java types that need to be converted
* @param mediaType MediaType for this request
* @return
*/
@Override
public boolean canRead(Class clazz, MediaType mediaType) {
if (mediaType == null) {
return true;
}
for (MediaType supportedMediaType : getSupportedMediaTypes()) {
if (supportedMediaType.includes(mediaType)) {
return true;
}
}
return false;
}
/**
* Determine whether the converter can convert Java types to specified output
* @param clazz Java types that need to be converted
* @param mediaType MediaType for this request
* @return
*/
@Override
public boolean canWrite(Class clazz, MediaType mediaType) {
if (mediaType == null || MediaType.ALL.equals(mediaType)) {
return true;
}
for (MediaType supportedMediaType : getSupportedMediaTypes()) {
if (supportedMediaType.includes(mediaType)) {
return true;
}
}
return false;
}
/**
* MediaType to get support for this converter
* @return
*/
@Override
public List getSupportedMediaTypes() {
return supportedMediaTypes;
}
/**
* Read the request content and convert the Json into a Java object
* @param clazz Java types that need to be converted
* @param inputMessage Request object
* @return
* @throws IOException
* @throws HttpMessageNotReadableException
*/
@Override
public Object read(Class clazz, HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException {
return mapper.readValue(inputMessage.getBody(), clazz);
}
/**
* Converting Java objects to Json return content
* @param o Objects to be transformed
* @param contentType Return type
* @param outputMessage Receipt object
* @throws IOException
* @throws HttpMessageNotWritableException
*/
@Override
public void write(Object o, MediaType contentType, HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException {
mapper.writeValue(outputMessage.getBody(), o);
}
}
As can be seen from the write method in AbstractHttpMessageConverter, the parent of Mapping Jackson 2HttpMessageConverter, the method writes data to the output stream of the returned result through the writeInternal method, so it only needs to rewrite the method:
@Bean
public MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter() {
return new MappingJackson2HttpMessageConverter() {
//Rewrite the writeInternal method to encrypt before returning the content
@Override
protected void writeInternal(Object object,
HttpOutputMessage outputMessage) throws IOException,
HttpMessageNotWritableException {
//Converting Java objects into Json String using Jackson's ObjectMapper
ObjectMapper mapper = new ObjectMapper();
String json = mapper.writeValueAsString(object);
LOGGER.error(json);
//encryption
String result = json + "Encrypted!";
LOGGER.error(result);
//output
outputMessage.getBody().write(result.getBytes());
}
};
}
After that, you need to configure this custom converter into Spring, where you add a custom converter by rewriting the configureMessageConverters method in WebMvcConfigurer:
//Add custom converter
@Override
public void configureMessageConverters(List<httpmessageconverter<?>> converters) {
converters.add(mappingJackson2HttpMessageConverter());
super.configureMessageConverters(converters);
}