1. 程式人生 > >精盡Spring MVC原始碼分析 - HandlerAdapter 元件(五)之 HttpMessageConverter

精盡Spring MVC原始碼分析 - HandlerAdapter 元件(五)之 HttpMessageConverter

> 該系列文件是本人在學習 Spring MVC 的原始碼過程中總結下來的,可能對讀者不太友好,請結合我的原始碼註釋 [Spring MVC 原始碼分析 GitHub 地址](https://github.com/liu844869663/spring-framework) 進行閱讀 > > Spring 版本:5.2.4.RELEASE > > 該系列其他文件請檢視:[**《精盡 Spring MVC 原始碼分析 - 文章導讀》**](https://www.cnblogs.com/lifullmoon/p/14123963.html) ## HandlerAdapter 元件 HandlerAdapter 元件,處理器的介面卡。因為處理器 `handler` 的型別是 Object 型別,需要有一個呼叫者來實現 `handler` 是怎麼被執行。Spring 中的處理器的實現多變,比如使用者的處理器可以實現 Controller 介面或者 HttpRequestHandler 介面,也可以用 `@RequestMapping` 註解將方法作為一個處理器等,這就導致 Spring MVC 無法直接執行這個處理器。所以這裡需要一個處理器介面卡,由它去執行處理器 由於 HandlerMapping 元件涉及到的內容較多,考慮到內容的排版,所以將這部分內容拆分成了五個模組,依次進行分析: - [**《HandlerAdapter 元件(一)之 HandlerAdapter》**](https://www.cnblogs.com/lifullmoon/p/14137467.html) - [**《HandlerAdapter 元件(二)之 ServletInvocableHandlerMethod》**](https://www.cnblogs.com/lifullmoon/p/14137483.html) - [**《HandlerAdapter 元件(三)之 HandlerMethodArgumentResolver》**](https://www.cnblogs.com/lifullmoon/p/14137494.html) - [**《HandlerAdapter 元件(四)之 HandlerMethodReturnValueHandler》**](https://www.cnblogs.com/lifullmoon/p/14137508.html) - [**《HandlerAdapter 元件(五)之 HttpMessageConverter》**](https://www.cnblogs.com/lifullmoon/p/14137520.html) ## HandlerAdapter 元件(五)之 HttpMessageConverter 本文是接著[**《HandlerAdapter 元件(四)之 HandlerMethodReturnValueHandler》**](https://www.cnblogs.com/lifullmoon/p/14137508.html)一文來分享 **HttpMessageConverter** 元件。在 `HandlerAdapter` 執行處理器的過程中,具體的執行過程交由 `ServletInvocableHandlerMethod` 物件來完成,其中需要先通過 `HandlerMethodArgumentResolver` 引數解析器從請求中解析出方法的入參,然後再通過反射機制呼叫對應的方法,獲取到執行結果後需要通過 `HandlerMethodReturnValueHandler` 結果處理器來進行處理。在處理返回結果的過程中,可能需要通過 **HttpMessageConverter** 訊息轉換器將返回結果設定到響應體中,當然也可能需要通過它從請求體獲取入參。 在使用 Spring MVC 時,`@RequestBody` 和 `@ResponseBody` 兩個註解,分別完成**請求報文到 Java 物件**、**Java 物件到響應報文**的轉換,底層的實現就是通過 Spring 3.x 中引入的 **HttpMessageConverter** 訊息轉換機制來實現的。 再開始閱讀本文之前,先來理解一些概念。在處理 HTTP 請求的過程中,需要解析請求體,返回結果設定到響應體。在 Servlet 標準中,`javax.servlet.ServletRequest` 和 `javax.servlet.ServletResponse` 分別有有以下方法: ```java // 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》**](https://www.cnblogs.com/lifullmoon/p/14137508.html) 中 **RequestResponseBodyMethodProcessor** 小節下面的 `handleReturnValue` 方法和 `writeWithMessageConverters` 方法 #### handleReturnValue ```java // 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 ```java // AbstractMessageConverterMethodProcessor.java protected 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 請求報文的抽象 ```java 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 響應報文的抽象 ```java 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`:對訊息轉換器最高層次的介面抽象,描述了一個訊息轉換器的一般特徵 ```java public interface HttpMessageConverter { /** 能否讀取 */ boolean canRead(Class clazz, @Nullable MediaType mediaType); /** 能夠寫入 */ boolean canWrite(Class clazz, @Nullable MediaType mediaType); /** 獲取支援的 MediaType */ List getSupportedMediaTypes(); /** 讀取請求體 */ T read(Class 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 ### 示例 ```java @RestController public class UserController { @Autowired UserService userService; /** 這裡的 @RequestBody 標註入參僅示例,是為了後續的分析 */ @GetMapping(value = "/query") public Result> 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` 返回結果寫入響應。 > 提示:RequestResponseBodyMethodProcessor 既是引數解析器,也是返回結果處理器 總結下來,整個過程如下所示:
### AbstractHttpMessageConverter `org.springframework.http.converter.AbstractHttpMessageConverter`,實現 HttpMessageConverter 介面,提供通用的骨架方法 #### 構造方法 ```java public abstract class AbstractHttpMessageConverter implements HttpMessageConverter { /** * 支援的 MediaType */ private List 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,如下: ```java @Override public List getSupportedMediaTypes() { return Collections.unmodifiableList(this.supportedMediaTypes); } ``` #### canRead 實現 `canRead(Class clazz, @Nullable MediaType mediaType)` 方法,是否支援從請求中讀取該型別的方法引數,如下: ```java @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)` 方法,是否支援往響應中寫入該型別的返回結果,如下: ```java @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 clazz, HttpInputMessage inputMessage)` 方法,從請求中讀取該型別的方法引數,如下: ```java @Override public final T read(Class clazz, HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException { return readInternal(clazz, inputMessage); } protected abstract T readInternal(Class clazz, HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException; ``` 其中 `readInternal(Class clazz, HttpInputMessage inputMessage)` 抽象方法,交由子類去實現 #### write 實現 `write(final T t, @Nullable MediaType contentType, HttpOutputMessage outputMessage)` 方法,往響應中寫入該型別的返回結果,如下: ```java @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; ``` 1. 獲取響應頭 2. 如果 `Content-Type` 為空則設定預設的 3. 往響應中寫入資料 1. 如果是流,則再封裝一層,StreamingHttpOutputMessage 物件 2. 普通物件,則直接呼叫 `writeInternal(T t, HttpOutputMessage outputMessage)` 抽象方法 4. 刷出流 ### StringHttpMessageConverter `org.springframework.http.converter.StringHttpMessageConverter`,繼承 AbstractHttpMessageConverter 抽象類,String 型別的訊息轉換器 #### supports 實現 `supports(Class clazz)` 方法,是否支援從請求中讀取該型別的方法引數,或者是否支援往響應中寫入該型別的返回結果,如下: ```java @Override public boolean supports(Class clazz) { return String.class == clazz; } ``` 是 `String` 類就可以,所以在示例中,會使用 `StringHttpMessageConverter` 訊息轉換器 #### readInternal 實現 `readInternal(Class clazz, HttpInputMessage inputMessage)` 方法,從請求中讀取該型別的方法引數,如下: ```java @Override protected String readInternal(Class 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 訊息轉換器 #### 構造方法 ```java public abstract class AbstractJackson2HttpMessageConverter extends AbstractGenericHttpMessageConverter { /** * 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)` 方法,是否支援從請求中讀取該型別的方法引數,如下: ```java @Override public boolean canRead(Type type, @Nullable Class contextClass, @Nullable MediaType mediaType) { if (!canRead(mediaType)) { return false; } // 獲得方法入參的型別 JavaType javaType = getJavaType(type, contextClass); AtomicReference 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)` 方法,從請求中讀取該型別的方法引數,如下: ```java @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)` 方法,判斷是否支援往響應中寫入該型別的返回結果,如下: ```java @Override public boolean canWrite(Class clazz, @Nullable MediaType mediaType) { // 判斷是否支援該 MediaType,也就是 Content-Type if (!canWrite(mediaType)) { return false; } AtomicReference causeRef = new AtomicReference<>(); // 通過 ObjectMapper 判斷是否能夠序列化 if (this.objectMapper.canSerialize(clazz, causeRef)) { return true; } logWarningIfNecessary(clazz, causeRef.get()); return false; } ``` 1. 判斷是否支援該 MediaType,也就是 Content-Type,支援 `application/json`、`application/*+json` 2. 通過 ObjectMapper 判斷是否能夠序列化 #### writeInternal 實現 `writeInternal(Object object, @Nullable Type type, HttpOutputMessage outputMessage)` 方法,往響應中寫入該型別的返回結果,如下: ```java @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); } } ``` 1. 獲取編碼方式 1. 獲取 Content-Type,例如 `application/json;charset=UTF-8` 2. 從 Content-Type 獲取編碼方式,預設 UTF8 2. 構建一個 Json 生成器 `generator`,指定`輸出流(響應)`和編碼 3. 呼叫`writePrefix(JsonGenerator generator, Object object)`方法,設定字首,MappingJackson2HttpMessageConverter 預設沒有 4. 獲得方法的返回結果物件 `value`,返回結果型別 `javaType` 1. 如果返回結果物件是 MappingJacksonValue 型別,則從該物件中相關屬性中獲取,沒使用過