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

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

該系列文件是本人在學習 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 元件(五)之 HttpMessageConverter

本文是接著《HandlerAdapter 元件(四)之 HandlerMethodReturnValueHandler》一文來分享 HttpMessageConverter 元件。在 HandlerAdapter 執行處理器的過程中,具體的執行過程交由 ServletInvocableHandlerMethod 物件來完成,其中需要先通過 HandlerMethodArgumentResolver 引數解析器從請求中解析出方法的入參,然後再通過反射機制呼叫對應的方法,獲取到執行結果後需要通過 HandlerMethodReturnValueHandler

結果處理器來進行處理。在處理返回結果的過程中,可能需要通過 HttpMessageConverter 訊息轉換器將返回結果設定到響應體中,當然也可能需要通過它從請求體獲取入參。

在使用 Spring MVC 時,@RequestBody@ResponseBody 兩個註解,分別完成請求報文到 Java 物件Java 物件到響應報文的轉換,底層的實現就是通過 Spring 3.x 中引入的 HttpMessageConverter 訊息轉換機制來實現的。

再開始閱讀本文之前,先來理解一些概念。在處理 HTTP 請求的過程中,需要解析請求體,返回結果設定到響應體。在 Servlet 標準中,javax.servlet.ServletRequestjavax.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 這個類,往響應體中寫資料,其中會通過 MappingJackson2HttpMessageConverterList<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;
  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) 方法,是否支援從請求中讀取該型別的方法引數,或者是否支援往響應中寫入該型別的返回結果,如下:

@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;
}
  1. 判斷是否支援該 MediaType,也就是 Content-Type,支援 application/jsonapplication/*+json
  2. 通過 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);
    }
}
  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 型別,則從該物件中相關屬性中獲取,沒使用過