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

月圓吖發表於2020-12-21

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

上圖只列出了部分實現類,因為在 Spring MVC 和 Sping Boot 中預設的 HttpMessageConverter 實現類差不多就上面幾個,我們來看看有哪些實現類:

  • Spring MVC

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

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

示例

@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 既是引數解析器,也是返回結果處理器

總結下來,整個過程如下所示:

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

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 型別,則從該物件中相關屬性中獲取,沒使用過?
    2. 獲取方法的返回結果的型別 javaType
  5. 建立 ObjectWriter 物件 objectWriter,沒有特殊配置通過 this.objectMapper.writer() 生成

  6. 獲取序列化配置

  7. 【重點】通過 objectWriter 將返回結果進行序列化,設定到 generator

  8. 呼叫 writeSuffix(JsonGenerator generator, Object object) 方法,設定字尾,MappingJackson2HttpMessageConverter 預設沒有

  9. 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 格式的資料,需要通過 ObjectMapperObjectWriter 進行反序列化和序列化等操作,也需要通過 JsonGenerator 進行 JSON 格式的訊息輸出

Spring MVC 預設的 JSON 訊息格式的轉換器是 MappingJackson2HttpMessageConverter 這個類,不過他僅定義了一個 JSON 字首屬性,主要的實現在其父類 AbstractJackson2HttpMessageConverter 完成的

本文對 Spring MVC 中的 HttpMessageConverter 僅做了一個淺顯的分析,對訊息轉換機制有個認識就好了。至此,關於 Spring MVC 中 HandlerAdapter 元件涉及到的 HandlerAdapterServletInvocableHandlerMethodHandlerMethodArgumentResolverHandlerMethodReturnValueHandlerHttpMessageConverter 五個元件都分析完了

HandlerAdapter 真的是 Spring MVC 九大元件裡,最複雜的一個?

參考文章:芋道原始碼《精盡 Spring MVC 原始碼分析》

相關文章