主頁 > 後端開發 > 精盡Spring MVC原始碼分析 - HandlerAdapter 組件(五)之 HttpMessageConverter

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

2020-12-22 08:47:57 後端開發

該系列檔案是本人在學習 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.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 = "https://www.cnblogs.com/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 = https://www.cnblogs.com/lifullmoon/p/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 原始碼分析》

轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/238328.html

標籤:Java

上一篇:極簡的藝術-方法參考

下一篇:Google 開源的 Guava 工具庫,太強大了~

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 【C++】Microsoft C++、C 和匯編程式檔案

    ......

    uj5u.com 2020-09-10 00:57:23 more
  • 例外宣告

    相比于斷言適用于排除邏輯上不可能存在的狀態,例外通常是用于邏輯上可能發生的錯誤。 例外宣告 Item 1:當函式不可能拋出例外或不能接受拋出例外時,使用noexcept 理由 如果不打算拋出例外的話,程式就會認為無法處理這種錯誤,并且應當盡早終止,如此可以有效地阻止例外的傳播與擴散。 示例 //不可 ......

    uj5u.com 2020-09-10 00:57:27 more
  • Codeforces 1400E Clear the Multiset(貪心 + 分治)

    鏈接:https://codeforces.com/problemset/problem/1400/E 來源:Codeforces 思路:給你一個陣列,現在你可以進行兩種操作,操作1:將一段沒有 0 的區間進行減一的操作,操作2:將 i 位置上的元素歸零。最終問:將這個陣列的全部元素歸零后操作的最少 ......

    uj5u.com 2020-09-10 00:57:30 more
  • UVA11610 【Reverse Prime】

    本人看到此題沒有翻譯,就附帶了一個自己的翻譯版本 思考 這一題,它的第一個要求是找出所有 $7$ 位反向質數及其質因數的個數。 我們應該需要質數篩篩選1~$10^{7}$的所有數,這里就不慢慢介紹了。但是,重讀題,我們突然發現反向質數都是 $7$ 位,而將它反過來后的數字卻是 $6$ 位數,這就說明 ......

    uj5u.com 2020-09-10 00:57:36 more
  • 統計區間素數數量

    1 #pragma GCC optimize(2) 2 #include <bits/stdc++.h> 3 using namespace std; 4 bool isprime[1000000010]; 5 vector<int> prime; 6 inline int getlist(int ......

    uj5u.com 2020-09-10 00:57:47 more
  • C/C++編程筆記:C++中的 const 變數詳解,教你正確認識const用法

    1、C中的const 1、區域const變數存放在堆疊區中,會分配記憶體(也就是說可以通過地址間接修改變數的值)。測驗代碼如下: 運行結果: 2、全域const變數存放在只讀資料段(不能通過地址修改,會發生寫入錯誤), 默認為外部聯編,可以給其他源檔案使用(需要用extern關鍵字修飾) 運行結果: ......

    uj5u.com 2020-09-10 00:58:04 more
  • 【C++犯錯記錄】VS2019 MFC添加資源不懂如何修改資源宏ID

    1. 首先在資源視圖中,添加資源 2. 點擊新添加的資源,復制自動生成的ID 3. 在解決方案資源管理器中找到Resource.h檔案,編輯,使用整個專案搜索和替換的方式快速替換 宏宣告 4. Ctrl+Shift+F 全域搜索,點擊查找全部,然后逐個替換 5. 為什么使用搜索替換而不使用屬性視窗直 ......

    uj5u.com 2020-09-10 00:59:11 more
  • 【C++犯錯記錄】VS2019 MFC不懂的批量添加資源

    1. 打開資源頭檔案Resource.h,在其中預先定義好宏 ID(不清楚其實ID值應該設定多少,可以先新建一個相同的資源項,再在這個資源的ID值的基礎上遞增即可) 2. 在資源視圖中選中專案資源,按F7編輯資源檔案,按 ID 型別 相對路徑的形式添加 資源。(別忘了先把檔案拷貝到專案中的res檔案 ......

    uj5u.com 2020-09-10 01:00:19 more
  • C/C++編程筆記:關于C++的參考型別,專供新手入門使用

    今天要講的是C++中我最喜歡的一個用法——參考,也叫別名。 參考就是給一個變數名取一個變數名,方便我們間接地使用這個變數。我們可以給一個變數創建N個參考,這N + 1個變數共享了同一塊記憶體區域。(參考型別的變數會占用記憶體空間,占用的記憶體空間的大小和指標型別的大小是相同的。雖然參考是一個物件的別名,但 ......

    uj5u.com 2020-09-10 01:00:22 more
  • 【C/C++編程筆記】從頭開始學習C ++:初學者完整指南

    眾所周知,C ++的學習曲線陡峭,但是花時間學習這種語言將為您的職業帶來奇跡,并使您與其他開發人員區分開。您會更輕松地學習新語言,形成真正的解決問題的技能,并在編程的基礎上打下堅實的基礎。 C ++將幫助您養成良好的編程習慣(即清晰一致的編碼風格,在撰寫代碼時注釋代碼,并限制類內部的可見性),并且由 ......

    uj5u.com 2020-09-10 01:00:41 more
最新发布
  • Rust中的智能指標:Box<T> Rc<T> Arc<T> Cell<T> RefCell<T> Weak

    Rust中的智能指標是什么 智能指標(smart pointers)是一類資料結構,是擁有資料所有權和額外功能的指標。是指標的進一步發展 指標(pointer)是一個包含記憶體地址的變數的通用概念。這個地址參考,或 ” 指向”(points at)一些其 他資料 。參考以 & 符號為標志并借用了他們所 ......

    uj5u.com 2023-04-20 07:24:10 more
  • Java的值傳遞和參考傳遞

    值傳遞不會改變本身,參考傳遞(如果傳遞的值需要實體化到堆里)如果發生修改了會改變本身。 1.基本資料型別都是值傳遞 package com.example.basic; public class Test { public static void main(String[] args) { int ......

    uj5u.com 2023-04-20 07:24:04 more
  • [2]SpinalHDL教程——Scala簡單入門

    第一個 Scala 程式 shell里面輸入 $ scala scala> 1 + 1 res0: Int = 2 scala> println("Hello World!") Hello World! 檔案形式 object HelloWorld { /* 這是我的第一個 Scala 程式 * 以 ......

    uj5u.com 2023-04-20 07:23:58 more
  • 理解函式指標和回呼函式

    理解 函式指標 指向函式的指標。比如: 理解函式指標的偽代碼 void (*p)(int type, char *data); // 定義一個函式指標p void func(int type, char *data); // 宣告一個函式func p = func; // 將指標p指向函式func ......

    uj5u.com 2023-04-20 07:23:52 more
  • Django筆記二十五之資料庫函式之日期函式

    本文首發于公眾號:Hunter后端 原文鏈接:Django筆記二十五之資料庫函式之日期函式 日期函式主要介紹兩個大類,Extract() 和 Trunc() Extract() 函式作用是提取日期,比如我們可以提取一個日期欄位的年份,月份,日等資料 Trunc() 的作用則是截取,比如 2022-0 ......

    uj5u.com 2023-04-20 07:23:45 more
  • 一天吃透JVM面試八股文

    什么是JVM? JVM,全稱Java Virtual Machine(Java虛擬機),是通過在實際的計算機上仿真模擬各種計算機功能來實作的。由一套位元組碼指令集、一組暫存器、一個堆疊、一個垃圾回收堆和一個存盤方法域等組成。JVM屏蔽了與作業系統平臺相關的資訊,使得Java程式只需要生成在Java虛擬機 ......

    uj5u.com 2023-04-20 07:23:31 more
  • 使用Java接入小程式訂閱訊息!

    更新完微信服務號的模板訊息之后,我又趕緊把微信小程式的訂閱訊息給實作了!之前我一直以為微信小程式也是要企業才能申請,沒想到小程式個人就能申請。 訊息推送平臺🔥推送下發【郵件】【短信】【微信服務號】【微信小程式】【企業微信】【釘釘】等訊息型別。 https://gitee.com/zhongfuch ......

    uj5u.com 2023-04-20 07:22:59 more
  • java -- 緩沖流、轉換流、序列化流

    緩沖流 緩沖流, 也叫高效流, 按照資料型別分類: 位元組緩沖流:BufferedInputStream,BufferedOutputStream 字符緩沖流:BufferedReader,BufferedWriter 緩沖流的基本原理,是在創建流物件時,會創建一個內置的默認大小的緩沖區陣列,通過緩沖 ......

    uj5u.com 2023-04-20 07:22:49 more
  • Java-SpringBoot-Range請求頭設定實作視頻分段傳輸

    老實說,人太懶了,現在基本都不喜歡寫筆記了,但是網上有關Range請求頭的文章都太水了 下面是抄的一段StackOverflow的代碼...自己大修改過的,寫的注釋挺全的,應該直接看得懂,就不解釋了 寫的不好...只是希望能給視頻網站開發的新手一點點幫助吧. 業務場景:視頻分段傳輸、視頻多段傳輸(理 ......

    uj5u.com 2023-04-20 07:22:42 more
  • Windows 10開發教程_編程入門自學教程_菜鳥教程-免費教程分享

    教程簡介 Windows 10開發入門教程 - 從簡單的步驟了解Windows 10開發,從基本到高級概念,包括簡介,UWP,第一個應用程式,商店,XAML控制元件,資料系結,XAML性能,自適應設計,自適應UI,自適應代碼,檔案管理,SQLite資料庫,應用程式到應用程式通信,應用程式本地化,應用程式 ......

    uj5u.com 2023-04-20 07:22:35 more