精盡Spring MVC源碼分析 – HandlerAdapter 組件(五)之 HttpMessageConverter
- 2020 年 12 月 21 日
- 筆記
- Spring MVC, 源碼解析, 精盡Spring MVC源碼分析
該系列文檔是本人在學習 Spring MVC 的源碼過程中總結下來的,可能對讀者不太友好,請結合我的源碼注釋 Spring MVC 源碼分析 GitHub 地址 進行閱讀
Spring 版本:5.2.4.RELEASE
該系列其他文檔請查看:《精盡 Spring MVC 源碼分析 – 文章導讀》
HandlerAdapter 組件
HandlerAdapter 組件,處理器的適配器。因為處理器 handler
的類型是 Object 類型,需要有一個調用者來實現 handler
是怎麼被執行。Spring 中的處理器的實現多變,比如用戶的處理器可以實現 Controller 接口或者 HttpRequestHandler 接口,也可以用 @RequestMapping
註解將方法作為一個處理器等,這就導致 Spring MVC 無法直接執行這個處理器。所以這裡需要一個處理器適配器,由它去執行處理器
由於 HandlerMapping 組件涉及到的內容較多,考慮到內容的排版,所以將這部分內容拆分成了五個模塊,依次進行分析:
- 《HandlerAdapter 組件(一)之 HandlerAdapter》
- 《HandlerAdapter 組件(二)之 ServletInvocableHandlerMethod》
- 《HandlerAdapter 組件(三)之 HandlerMethodArgumentResolver》
- 《HandlerAdapter 組件(四)之 HandlerMethodReturnValueHandler》
- 《HandlerAdapter 組件(五)之 HttpMessageConverter》
HandlerAdapter 組件(五)之 HttpMessageConverter
本文是接着《HandlerAdapter 組件(四)之 HandlerMethodReturnValueHandler》一文來分享 HttpMessageConverter 組件。在 HandlerAdapter
執行處理器的過程中,具體的執行過程交由 ServletInvocableHandlerMethod
對象來完成,其中需要先通過 HandlerMethodArgumentResolver
參數解析器從請求中解析出方法的入參,然後再通過反射機制調用對應的方法,獲取到執行結果後需要通過 HandlerMethodReturnValueHandler
結果處理器來進行處理。在處理返回結果的過程中,可能需要通過 HttpMessageConverter 消息轉換器將返回結果設置到響應體中,當然也可能需要通過它從請求體獲取入參。
在使用 Spring MVC 時,@RequestBody
和 @ResponseBody
兩個註解,分別完成請求報文到 Java 對象、Java 對象到響應報文的轉換,底層的實現就是通過 Spring 3.x 中引入的 HttpMessageConverter 消息轉換機制來實現的。
再開始閱讀本文之前,先來理解一些概念。在處理 HTTP 請求的過程中,需要解析請求體,返回結果設置到響應體。在 Servlet 標準中,javax.servlet.ServletRequest
和 javax.servlet.ServletResponse
分別有有以下方法:
// javax.servlet.ServletRequest
public ServletInputStream getInputStream() throws IOException;
// javax.servlet.ServletResponse
public ServletOutputStream getOutputStream() throws IOException;
通過上面兩個方法可以獲取到請求體和響應體,ServletInputStream 和 ServletOutputStream 分別繼承 java 中的 InputStream 和 OutputStream 流對象,可以通過它們獲取請求報文和設置響應報文。我們只能從流中讀取原始的字符串報文,或者往流中寫入原始的字符串,而 Java 是面向對象編程的,字符串與 Java 對象之間的轉換不可能交由開發者去實現。在 Sping MVC 中,會將 Servlet 提供的請求和響應進行一層抽象封裝,便於操作讀取和寫入,再通過 HttpMessageConverter 消息轉換機制來解析請求報文或者設置響應報文。
回顧
先來回顧一下 HandlerMethodReturnValueHandler
如何處理放回結果的,可以回到 《HandlerAdapter 組件(四)之 HandlerMethodReturnValueHandler》 中 RequestResponseBodyMethodProcessor 小節下面的 handleReturnValue
方法和 writeWithMessageConverters
方法
handleReturnValue
// RequestResponseBodyMethodProcessor.java
@Override
public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType,
ModelAndViewContainer mavContainer, NativeWebRequest webRequest)
throws IOException, HttpMediaTypeNotAcceptableException, HttpMessageNotWritableException {
// <1> 設置已處理
mavContainer.setRequestHandled(true);
// <2> 創建請求和響應
ServletServerHttpRequest inputMessage = createInputMessage(webRequest);
ServletServerHttpResponse outputMessage = createOutputMessage(webRequest);
// Try even with null return value. ResponseBodyAdvice could get involved.
// <3> 使用 HttpMessageConverter 對對象進行轉換,並寫入到響應
writeWithMessageConverters(returnValue, returnType, inputMessage, outputMessage);
}
// AbstractMessageConverterMethodProcessor.java
protected ServletServerHttpRequest createInputMessage(NativeWebRequest webRequest) {
HttpServletRequest servletRequest = webRequest.getNativeRequest(HttpServletRequest.class);
Assert.state(servletRequest != null, "No HttpServletRequest");
return new ServletServerHttpRequest(servletRequest);
}
// AbstractMessageConverterMethodProcessor.java
protected ServletServerHttpResponse createOutputMessage(NativeWebRequest webRequest) {
HttpServletResponse response = webRequest.getNativeResponse(HttpServletResponse.class);
Assert.state(response != null, "No HttpServletResponse");
return new ServletServerHttpResponse(response);
}
上面會將請求封裝成 ServletServerHttpRequest 和 ServletServerHttpResponse 對象
- ServletServerHttpRequest:實現了 ServerHttpRequest、HttpRequest、HttpInputMessage、HttpMessage接口
- ServletServerHttpResponse:實現 ServerHttpResponse、HttpOutputMessage 接口
上面這些接口定義了一些獲取請求和設置響應相關信息的方法,便於獲取請求和設置響應
writeWithMessageConverters
// AbstractMessageConverterMethodProcessor.java
protected <T> void writeWithMessageConverters(@Nullable T value, MethodParameter returnType,
ServletServerHttpRequest inputMessage, ServletServerHttpResponse outputMessage)
throws IOException, HttpMediaTypeNotAcceptableException, HttpMessageNotWritableException {
// <1> 獲得 body、valueType、targetType
Object body; Class<?> valueType; Type targetType;
// <3> 選擇使用的 MediaType
MediaType selectedMediaType = null;
// <4> 如果匹配到,則進行寫入邏輯
if (selectedMediaType != null) {
// <4.1> 移除 quality 。例如,application/json;q=0.8 移除後為 application/json
selectedMediaType = selectedMediaType.removeQualityValue();
// <4.2> 遍歷 messageConverters 數組
for (HttpMessageConverter<?> converter : this.messageConverters) {
// <4.3> 判斷 HttpMessageConverter 是否支持轉換目標類型
GenericHttpMessageConverter genericConverter = (converter instanceof GenericHttpMessageConverter
? (GenericHttpMessageConverter<?>) converter : null);
if (genericConverter != null ?
((GenericHttpMessageConverter) converter).canWrite(targetType, valueType, selectedMediaType)
: converter.canWrite(valueType, selectedMediaType)) {
// <5.2> body 非空,則進行寫入
if (body != null) {
if (genericConverter != null) {
genericConverter.write(body, targetType, selectedMediaType, outputMessage);
} else {
((HttpMessageConverter) converter).write(body, selectedMediaType, outputMessage);
}
}
// <5.4> return 返回,結束整個邏輯
return;
}
}
}
// ... 上面省略了大量代碼
}
<4.2>
處,遍歷所有的 HttpMessageConverter 實現類
<4.3>
處,調用當前 HttpMessageConverter 實現類的 canWrite
方法,判斷是否支持寫入
<5.2>
處,調用該 HttpMessageConverter 實現類的 write
方法,進行寫入
HttpInputMessage 接口
org.springframework.http.HttpInputMessage
:對一次 Http 請求報文的抽象
public interface HttpInputMessage extends HttpMessage {
/**
* Return the body of the message as an input stream.
* @return the input stream body (never {@code null})
* @throws IOException in case of I/O errors
*/
InputStream getBody() throws IOException;
}
在 HttpMessageConverter 的 read
方法中,有一個 HttpInputMessage 的形參,它正是 Spring MVC 的消息轉換器所作用的受體請求消息的內部抽象,消息轉換器從請求消息中按照規則提取消息,轉換為方法形參中聲明的對象。
HttpOutputMessage 接口
org.springframework.http.HttpOutputMessage
:對一次 Http 響應報文的抽象
public interface HttpOutputMessage extends HttpMessage {
/**
* Return the body of the message as an output stream.
* @return the output stream body (never {@code null})
* @throws IOException in case of I/O errors
*/
OutputStream getBody() throws IOException;
}
在 HttpMessageConverter 的 write
方法中,有一個 HttpOutputMessage 的形參,它正是 Spring MVC 的消息轉換器所作用的受體響應消息的內部抽象,消息轉換器將響應消息按照一定的規則寫到響應報文中
HttpMessageConverter 接口
org.springframework.http.converter.HttpMessageConverter
:對消息轉換器最高層次的接口抽象,描述了一個消息轉換器的一般特徵
public interface HttpMessageConverter<T> {
/** 能否讀取 */
boolean canRead(Class<?> clazz, @Nullable MediaType mediaType);
/** 能夠寫入 */
boolean canWrite(Class<?> clazz, @Nullable MediaType mediaType);
/** 獲取支持的 MediaType */
List<MediaType> getSupportedMediaTypes();
/** 讀取請求體 */
T read(Class<? extends T> clazz, HttpInputMessage inputMessage)
throws IOException, HttpMessageNotReadableException;
/** 設置響應體 */
void write(T t, @Nullable MediaType contentType, HttpOutputMessage outputMessage)
throws IOException, HttpMessageNotWritableException;
}
類圖
HttpMessageConverter 接口體系的結構如下:
上圖只列出了部分實現類,因為在 Spring MVC 和 Sping Boot 中默認的 HttpMessageConverter 實現類差不多就上面幾個,我們來看看有哪些實現類:
-
Spring MVC
-
Spring Boot
示例
@RestController
public class UserController {
@Autowired
UserService userService;
/** 這裡的 @RequestBody 標註入參僅示例,是為了後續的分析 */
@GetMapping(value = "/query")
public Result<List<User>> queryUser(@RequestBody String name) {
try {
return Result.success().data(userService.queryUserByName(name));
} catch (Exception e) {
return Result.fail(e);
}
}
}
當你發起一個 HTTP 請求 GET /query
,因為你添加了@RequestBody 註解,所以是從請求體讀請求報文的,可以設置請求體中的數據為 ming
,也就是我想要拿到名字為 ming
的所有用戶的信息
Spring MVC 處理該請求時,會先進入到 RequestResponseBodyMethodProcessor 這個類,獲取方法入參,其中會通過 StringHttpMessageConverter
從請求體中讀取 ming
數據,作為調用方法的入參。
在 Spring MVC 獲取到方法的返回結果後,又會進入到 RequestResponseBodyMethodProcessor 這個類,往響應體中寫數據,其中會通過 MappingJackson2HttpMessageConverter
將 List<User>
返回結果寫入響應。
提示:RequestResponseBodyMethodProcessor 既是參數解析器,也是返回結果處理器
總結下來,整個過程如下所示:
AbstractHttpMessageConverter
org.springframework.http.converter.AbstractHttpMessageConverter
,實現 HttpMessageConverter 接口,提供通用的骨架方法
構造方法
public abstract class AbstractHttpMessageConverter<T> implements HttpMessageConverter<T> {
/**
* 支持的 MediaType
*/
private List<MediaType> supportedMediaTypes = Collections.emptyList();
/**
* 默認的字符集
*/
@Nullable
private Charset defaultCharset;
protected AbstractHttpMessageConverter() {
}
protected AbstractHttpMessageConverter(MediaType supportedMediaType) {
setSupportedMediaTypes(Collections.singletonList(supportedMediaType));
}
protected AbstractHttpMessageConverter(MediaType... supportedMediaTypes) {
setSupportedMediaTypes(Arrays.asList(supportedMediaTypes));
}
protected AbstractHttpMessageConverter(Charset defaultCharset, MediaType... supportedMediaTypes) {
this.defaultCharset = defaultCharset;
setSupportedMediaTypes(Arrays.asList(supportedMediaTypes));
}
}
-
supportedMediaTypes
:支持的 MediaType -
defaultCharset
:默認的字符集
上面兩個屬性可以由子類去設置
getSupportedMediaTypes
實現 getSupportedMediaTypes()
方法,獲得支持的 MediaType,如下:
@Override
public List<MediaType> getSupportedMediaTypes() {
return Collections.unmodifiableList(this.supportedMediaTypes);
}
canRead
實現 canRead(Class<?> clazz, @Nullable MediaType mediaType)
方法,是否支持從請求中讀取該類型的方法參數,如下:
@Override
public boolean canRead(Class<?> clazz, @Nullable MediaType mediaType) {
return supports(clazz) && canRead(mediaType);
}
protected abstract boolean supports(Class<?> clazz);
protected boolean canRead(@Nullable MediaType mediaType) {
if (mediaType == null) {
return true;
}
for (MediaType supportedMediaType : getSupportedMediaTypes()) {
if (supportedMediaType.includes(mediaType)) {
return true;
}
}
return false;
}
其中 supports(Class<?> clazz)
抽象方法,交由子類去實現
canWrite
實現 canWrite(Class<?> clazz, @Nullable MediaType mediaType)
方法,是否支持往響應中寫入該類型的返回結果,如下:
@Override
public boolean canWrite(Class<?> clazz, @Nullable MediaType mediaType) {
return supports(clazz) && canWrite(mediaType);
}
protected abstract boolean supports(Class<?> clazz);
protected boolean canWrite(@Nullable MediaType mediaType) {
if (mediaType == null || MediaType.ALL.equalsTypeAndSubtype(mediaType)) {
return true;
}
for (MediaType supportedMediaType : getSupportedMediaTypes()) {
if (supportedMediaType.isCompatibleWith(mediaType)) {
return true;
}
}
return false;
}
其中 supports(Class<?> clazz)
抽象方法,交由子類去實現
read
實現 read(Class<? extends T> clazz, HttpInputMessage inputMessage)
方法,從請求中讀取該類型的方法參數,如下:
@Override
public final T read(Class<? extends T> clazz, HttpInputMessage inputMessage)
throws IOException, HttpMessageNotReadableException {
return readInternal(clazz, inputMessage);
}
protected abstract T readInternal(Class<? extends T> clazz, HttpInputMessage inputMessage)
throws IOException, HttpMessageNotReadableException;
其中 readInternal(Class<? extends T> clazz, HttpInputMessage inputMessage)
抽象方法,交由子類去實現
write
實現 write(final T t, @Nullable MediaType contentType, HttpOutputMessage outputMessage)
方法,往響應中寫入該類型的返回結果,如下:
@Override
public final void write(final T t, @Nullable MediaType contentType, HttpOutputMessage outputMessage)
throws IOException, HttpMessageNotWritableException {
// <1> 獲取響應頭
final HttpHeaders headers = outputMessage.getHeaders();
// <2> 如果 Content-Type 為空則設置默認的
addDefaultHeaders(headers, t, contentType);
// <3> 往響應中寫入數據
if (outputMessage instanceof StreamingHttpOutputMessage) { // <3.1> 如果是流,則再封裝一層
StreamingHttpOutputMessage streamingOutputMessage = (StreamingHttpOutputMessage) outputMessage;
streamingOutputMessage.setBody(outputStream -> writeInternal(t, new HttpOutputMessage() {
@Override
public OutputStream getBody() {
return outputStream;
}
@Override
public HttpHeaders getHeaders() {
return headers;
}
}));
}
else { // <3.2> 普通對象
writeInternal(t, outputMessage);
outputMessage.getBody().flush();
}
}
protected abstract void writeInternal(T t, HttpOutputMessage outputMessage)
throws IOException, HttpMessageNotWritableException;
-
獲取響應頭
-
如果
Content-Type
為空則設置默認的 -
往響應中寫入數據
- 如果是流,則再封裝一層,StreamingHttpOutputMessage 對象
- 普通對象,則直接調用
writeInternal(T t, HttpOutputMessage outputMessage)
抽象方法
-
刷出流
StringHttpMessageConverter
org.springframework.http.converter.StringHttpMessageConverter
,繼承 AbstractHttpMessageConverter 抽象類,String 類型的消息轉換器
supports
實現 supports(Class<?> clazz)
方法,是否支持從請求中讀取該類型的方法參數,或者是否支持往響應中寫入該類型的返回結果,如下:
@Override
public boolean supports(Class<?> clazz) {
return String.class == clazz;
}
是 String
類就可以,所以在示例中,會使用 StringHttpMessageConverter
消息轉換器
readInternal
實現 readInternal(Class<? extends String> clazz, HttpInputMessage inputMessage)
方法,從請求中讀取該類型的方法參數,如下:
@Override
protected String readInternal(Class<? extends String> clazz, HttpInputMessage inputMessage) throws IOException {
Charset charset = getContentTypeCharset(inputMessage.getHeaders().getContentType());
return StreamUtils.copyToString(inputMessage.getBody(), charset);
}
// org.springframework.util.StreamUtils.java
public static String copyToString(@Nullable InputStream in, Charset charset) throws IOException {
if (in == null) {
return "";
}
StringBuilder out = new StringBuilder();
InputStreamReader reader = new InputStreamReader(in, charset);
char[] buffer = new char[BUFFER_SIZE];
int bytesRead = -1;
while ((bytesRead = reader.read(buffer)) != -1) {
out.append(buffer, 0, bytesRead);
}
return out.toString();
}
邏輯不複雜,直接從請求的 ServletInputStream 流中讀取出來,轉換成字符串
AbstractJackson2HttpMessageConverter
org.springframework.http.converter.json.AbstractJackson2HttpMessageConverter
:繼承 AbstractGenericHttpMessageConverter 抽象類,JSON 格式的消息讀取或者寫入,也就是我們熟悉的 @RequestBody
和 @ResponseBody
註解對應的 HttpMessageConverter 消息轉換器
構造方法
public abstract class AbstractJackson2HttpMessageConverter extends AbstractGenericHttpMessageConverter<Object> {
/**
* The default charset used by the converter.
*/
public static final Charset DEFAULT_CHARSET = StandardCharsets.UTF_8;
protected ObjectMapper objectMapper;
@Nullable
private Boolean prettyPrint;
@Nullable
private PrettyPrinter ssePrettyPrinter;
protected AbstractJackson2HttpMessageConverter(ObjectMapper objectMapper) {
this.objectMapper = objectMapper;
setDefaultCharset(DEFAULT_CHARSET);
DefaultPrettyPrinter prettyPrinter = new DefaultPrettyPrinter();
prettyPrinter.indentObjectsWith(new DefaultIndenter(" ", "\ndata:"));
this.ssePrettyPrinter = prettyPrinter;
}
protected AbstractJackson2HttpMessageConverter(ObjectMapper objectMapper, MediaType supportedMediaType) {
this(objectMapper);
setSupportedMediaTypes(Collections.singletonList(supportedMediaType));
}
protected AbstractJackson2HttpMessageConverter(ObjectMapper objectMapper, MediaType... supportedMediaTypes) {
this(objectMapper);
setSupportedMediaTypes(Arrays.asList(supportedMediaTypes));
}
}
canRead
實現 canRead(Type type, @Nullable Class<?> contextClass, @Nullable MediaType mediaType)
方法,是否支持從請求中讀取該類型的方法參數,如下:
@Override
public boolean canRead(Type type, @Nullable Class<?> contextClass, @Nullable MediaType mediaType) {
if (!canRead(mediaType)) {
return false;
}
// 獲得方法入參的類型
JavaType javaType = getJavaType(type, contextClass);
AtomicReference<Throwable> causeRef = new AtomicReference<>();
// 通過 ObjectMapper 判斷是否能夠反序列化
if (this.objectMapper.canDeserialize(javaType, causeRef)) {
return true;
}
logWarningIfNecessary(javaType, causeRef.get());
return false;
}
read
實現 read(Type type, @Nullable Class<?> contextClass, HttpInputMessage inputMessage)
方法,從請求中讀取該類型的方法參數,如下:
@Override
public Object read(Type type, @Nullable Class<?> contextClass, HttpInputMessage inputMessage)
throws IOException, HttpMessageNotReadableException {
// 獲得方法入參的類型
JavaType javaType = getJavaType(type, contextClass);
// 從請求中讀取該類型的方法入參
return readJavaType(javaType, inputMessage);
}
private Object readJavaType(JavaType javaType, HttpInputMessage inputMessage) throws IOException {
try {
// 如果請求是 MappingJacksonInputMessage 類型,默認不是
if (inputMessage instanceof MappingJacksonInputMessage) {
Class<?> deserializationView = ((MappingJacksonInputMessage) inputMessage).getDeserializationView();
if (deserializationView != null) {
return this.objectMapper.readerWithView(deserializationView).forType(javaType).
readValue(inputMessage.getBody());
}
}
// 通過 ObjectMapper 從請求中讀取該類型的方法入參
return this.objectMapper.readValue(inputMessage.getBody(), 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);
}
}
canWrite
實現 canWrite(Class<?> clazz, @Nullable MediaType mediaType)
方法,判斷是否支持往響應中寫入該類型的返回結果,如下:
@Override
public boolean canWrite(Class<?> clazz, @Nullable MediaType mediaType) {
// 判斷是否支持該 MediaType,也就是 Content-Type
if (!canWrite(mediaType)) {
return false;
}
AtomicReference<Throwable> causeRef = new AtomicReference<>();
// 通過 ObjectMapper 判斷是否能夠序列化
if (this.objectMapper.canSerialize(clazz, causeRef)) {
return true;
}
logWarningIfNecessary(clazz, causeRef.get());
return false;
}
- 判斷是否支持該 MediaType,也就是 Content-Type,支持
application/json
、application/*+json
- 通過 ObjectMapper 判斷是否能夠序列化
writeInternal
實現 writeInternal(Object object, @Nullable Type type, HttpOutputMessage outputMessage)
方法,往響應中寫入該類型的返回結果,如下:
@Override
protected void writeInternal(Object object, @Nullable Type type, HttpOutputMessage outputMessage)
throws IOException, HttpMessageNotWritableException {
// <1> 獲取編碼方式
// <1.1> 獲取 Content-Type,例如 `application/json;charset=UTF-8`
MediaType contentType = outputMessage.getHeaders().getContentType();
// <1.2> 從 Content-Type 獲取編碼方式,默認 UTF8
JsonEncoding encoding = getJsonEncoding(contentType);
// <2> 構建一個 Json 生成器 `generator`,指定`輸出流(響應)`和編碼
// 例如:UTF8JsonGenerator 對象(jackson-core 包)
JsonGenerator generator = this.objectMapper.getFactory().createGenerator(outputMessage.getBody(), encoding);
try {
// <3> 設置前綴,默認沒有
writePrefix(generator, object);
// <4> 獲得方法的返回結果對象 `value`,返回結果類型 `javaType`
Object value = object;
Class<?> serializationView = null;
FilterProvider filters = null;
JavaType javaType = null;
// <4.1> 如果返回結果對象是 MappingJacksonValue 類型,沒使用過
if (object instanceof MappingJacksonValue) {
MappingJacksonValue container = (MappingJacksonValue) object;
value = container.getValue();
serializationView = container.getSerializationView();
filters = container.getFilters();
}
// <4.2> 獲取方法的返回結果的類型 `javaType`
if (type != null && TypeUtils.isAssignable(type, value.getClass())) {
javaType = getJavaType(type, null);
}
// <5> 創建 ObjectWriter 對象 `objectWriter`,沒有特殊配置通過 `this.objectMapper.writer()` 生成
ObjectWriter objectWriter = (serializationView != null ?
this.objectMapper.writerWithView(serializationView) : this.objectMapper.writer());
if (filters != null) {
objectWriter = objectWriter.with(filters);
}
if (javaType != null && javaType.isContainerType()) {
objectWriter = objectWriter.forType(javaType);
}
// <6> 獲取序列化配置
SerializationConfig config = objectWriter.getConfig();
if (contentType != null && contentType.isCompatibleWith(MediaType.TEXT_EVENT_STREAM) &&
config.isEnabled(SerializationFeature.INDENT_OUTPUT)) {
objectWriter = objectWriter.with(this.ssePrettyPrinter);
}
// <7> **【重點】**通過 `objectWriter` 將返回結果進行序列化,設置到 `generator` 中
objectWriter.writeValue(generator, value);
// <8> 設置後綴,默認沒有
writeSuffix(generator, object);
// <9> 讓 `generator` 刷出數據,以 Json 格式輸出,也就是會往響應中刷出 Json 格式的返回結果
generator.flush();
}
catch (InvalidDefinitionException ex) {
throw new HttpMessageConversionException("Type definition error: " + ex.getType(), ex);
}
catch (JsonProcessingException ex) {
throw new HttpMessageNotWritableException("Could not write JSON: " + ex.getOriginalMessage(), ex);
}
}
-
獲取編碼方式
- 獲取 Content-Type,例如
application/json;charset=UTF-8
- 從 Content-Type 獲取編碼方式,默認 UTF8
- 獲取 Content-Type,例如
-
構建一個 Json 生成器
generator
,指定輸出流(響應)
和編碼 -
調用
writePrefix(JsonGenerator generator, Object object)
方法,設置前綴,MappingJackson2HttpMessageConverter 默認沒有 -
獲得方法的返回結果對象
value
,返回結果類型javaType
- 如果返回結果對象是 MappingJacksonValue 類型,則從該對象中相關屬性中獲取,沒使用過😈
- 獲取方法的返回結果的類型
javaType
-
創建 ObjectWriter 對象
objectWriter
,沒有特殊配置通過this.objectMapper.writer()
生成 -
獲取序列化配置
-
【重點】通過
objectWriter
將返回結果進行序列化,設置到generator
中 -
調用
writeSuffix(JsonGenerator generator, Object object)
方法,設置後綴,MappingJackson2HttpMessageConverter 默認沒有 -
讓
generator
刷出數據,以 Json 格式輸出,也就是會往響應中刷出 Json 格式的返回結果
MappingJackson2HttpMessageConverter
org.springframework.http.converter.json.MappingJackson2HttpMessageConverter
,繼承 AbstractJackson2HttpMessageConverter 抽象類,JSON 格式的消息讀取或者寫入,也就是我們熟悉的 @RequestBody
和 @ResponseBody
註解對應的 HttpMessageConverter 消息轉換器
public class MappingJackson2HttpMessageConverter extends AbstractJackson2HttpMessageConverter {
@Nullable
private String jsonPrefix;
public MappingJackson2HttpMessageConverter() {
this(Jackson2ObjectMapperBuilder.json().build());
}
public MappingJackson2HttpMessageConverter(ObjectMapper objectMapper) {
super(objectMapper, MediaType.APPLICATION_JSON, new MediaType("application", "*+json"));
}
public void setJsonPrefix(String jsonPrefix) {
this.jsonPrefix = jsonPrefix;
}
public void setPrefixJson(boolean prefixJson) {
this.jsonPrefix = (prefixJson ? ")]}', " : null);
}
@Override
protected void writePrefix(JsonGenerator generator, Object object) throws IOException {
if (this.jsonPrefix != null) {
generator.writeRaw(this.jsonPrefix);
}
}
}
可以看到僅是添加了一個 jsonPrefix
屬性,JSON 的前綴,默認為空,但是只有前綴,沒有後綴嗎?沒搞明白😈
思考
張小龍在談微信的本質時候說:「微信只是個平台,消息在其中流轉」。在 Spring MVC 的 HttpMessageConverter 機制中可以領悟到類似的道理,一次請求報文和一次響應報文,分別被抽象為一個請求消息 HttpInputMessage 和一個響應消息 HttpOutputMessage
處理請求時,由合適的 HttpMessageConverter 消息轉換器將請求報文綁定為方法中的形參對象,同一個對象就有可能出現多種不同的消息形式,比如 json 和 xml,同樣,當響應請求時,方法的返回值也同樣可能被返回為不同的消息形式,比如 json 和 xml
在 Spring MVC 中,針對不同的消息形式,有不同的 HttpMessageConverter 實現類來處理各種消息形式。但是,只要這些消息所蘊含的「有效信息」是一致的,那麼各種不同的消息轉換器,都會生成同樣的轉換結果。至於各種消息間解析細節的不同,就被屏蔽在不同的 HttpMessageConverter 實現類中了
總結
在 HandlerAdapter
執行 HandlerMethod
處理器的過程中,會將該處理器封裝成 ServletInvocableHandlerMethod
對象,通過該對象來執行處理器。該對象通過反射機制調用對應的方法,在調用方法之前,藉助 HandlerMethodArgumentResolver
參數解析器從請求中獲取到對應的方法參數值,在調用方法之後,需要藉助於HandlerMethodReturnValueHandler
返回值處理器將返回結果設置到響應中,或者設置相應的 Model 和 View 用於後續的視圖渲染。在這整個過程中需要 HttpMessageConverter 消息轉換器從請求中獲取方法入參,或者往響應中設置返回結果。
HttpMessageConverter 的實現類非常多,本文分析了我們常用的兩種方法出入參格式,標註 @RequestBody
註解方法參數和標註 @ResponseBody
註解的方法
StringHttpMessageConverter
:處理 String 類型的方法入參,直接從請求體中讀取,轉換成字符串,當然也可以往響應中寫入 String 類型的返回結果MappingJackson2HttpMessageConverter
:處理標有@RequestBody
註解的方法參數或者返回結果,解析或者輸出 JSON 格式的數據,需要通過ObjectMapper
和ObjectWriter
進行反序列化和序列化等操作,也需要通過JsonGenerator
進行 JSON 格式的消息輸出
Spring MVC 默認的 JSON 消息格式的轉換器是 MappingJackson2HttpMessageConverter
這個類,不過他僅定義了一個 JSON 前綴屬性,主要的實現在其父類 AbstractJackson2HttpMessageConverter
完成的
本文對 Spring MVC 中的 HttpMessageConverter 僅做了一個淺顯的分析,對消息轉換機制有個認識就好了。至此,關於 Spring MVC 中 HandlerAdapter
組件涉及到的 HandlerAdapter
、ServletInvocableHandlerMethod
、HandlerMethodArgumentResolver
、HandlerMethodReturnValueHandler
、HttpMessageConverter
五個組件都分析完了
HandlerAdapter 真的是 Spring MVC 九大組件里,最複雜的一個😜
參考文章:芋道源碼《精盡 Spring MVC 源碼分析》