該系列文件是本人在學習 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 原始碼分析》