Spring MVC 源码- HandlerAdapter 组件(五)之 HttpMessageConverter

news2024/11/19 3:49:56

HandlerAdapter 组件

HandlerAdapter 组件,处理器的适配器。因为处理器 handler 的类型是 Object 类型,需要有一个调用者来实现 handler 是怎么被执行。Spring 中的处理器的实现多变,比如用户的处理器可以实现 Controller 接口或者 HttpRequestHandler 接口,也可以用 @RequestMapping 注解将方法作为一个处理器等,这就导致 Spring MVC 无法直接执行这个处理器。所以这里需要一个处理器适配器,由它去执行处理器

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. 获取响应头

  1. 如果 Content-Type 为空则设置默认的

  1. 往响应中写入数据

  1. 如果是流,则再封装一层,StreamingHttpOutputMessage 对象

  1. 普通对象,则直接调用 writeInternal(T t, HttpOutputMessage outputMessage) 抽象方法

  1. 刷出流

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

  1. 通过 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

  1. 从 Content-Type 获取编码方式,默认 UTF8

  1. 构建一个 Json 生成器 generator,指定输出流(响应)和编码

  1. 调用writePrefix(JsonGenerator generator, Object object)方法,设置前缀,MappingJackson2HttpMessageConverter 默认没有

  1. 获得方法的返回结果对象 value,返回结果类型 javaType

  1. 如果返回结果对象是 MappingJacksonValue 类型,则从该对象中相关属性中获取,没使用过

  1. 获取方法的返回结果的类型 javaType

  1. 创建 ObjectWriter 对象 objectWriter,没有特殊配置通过 this.objectMapper.writer() 生成

  1. 获取序列化配置

  1. 【重点】通过 objectWriter 将返回结果进行序列化,设置到 generator

  1. 调用 writeSuffix(JsonGenerator generator, Object object) 方法,设置后缀,MappingJackson2HttpMessageConverter 默认没有

  1. 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 九大组件里,最复杂的一个

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/370244.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

数据结构预算法之买卖股票的最好时机(三)动态规划

目录&#xff1a;一.题目知识点&#xff1a;动态规划二.动态规划数组思路确定1.dp数组以及下标的含义2.确定递推公式3.dp数组如何初始化4.确定遍历顺序5.举例推导dp数组一.题目知识点&#xff1a;动态规划动态规划算法的基本思想是&#xff1a;将待求解的问题分解成若干个相互联…

惠普m1136打印机驱动程序安装教程

惠普m113打印机是一款功能强大的多功能打印机&#xff0c;它能够打印、复印、扫描和传真等。如果你要使用这款打印机&#xff0c;你需要下载并安装驱动程序&#xff0c;以确保它能够在你的计算机上正常工作。在本文中&#xff0c;我们将介绍如何下载和安装惠普m1136打印机驱动程…

Python实现贝叶斯优化器(Bayes_opt)优化支持向量机回归模型(SVR算法)项目实战

说明&#xff1a;这是一个机器学习实战项目&#xff08;附带数据代码文档视频讲解&#xff09;&#xff0c;如需数据代码文档视频讲解可以直接到文章最后获取。1.项目背景贝叶斯优化器 (BayesianOptimization) 是一种黑盒子优化器&#xff0c;用来寻找最优参数。贝叶斯优化器是…

【华为OD机试模拟题】用 C++ 实现 - 跳格子(2023.Q1)

最近更新的博客 华为OD机试 - 入栈出栈(C++) | 附带编码思路 【2023】 华为OD机试 - 箱子之形摆放(C++) | 附带编码思路 【2023】 华为OD机试 - 简易内存池 2(C++) | 附带编码思路 【2023】 华为OD机试 - 第 N 个排列(C++) | 附带编码思路 【2023】 华为OD机试 - 考古…

sklearn学习-朴素贝叶斯(二)

文章目录一、概率类模型的评估指标1、布里尔分数Brier Score对数似然函数Log Loss二、calibration_curve&#xff1a;校准可靠性曲线三、多项式朴素贝叶斯以及其变化四、伯努利朴素贝叶斯五、改进多项式朴素贝叶斯&#xff1a;补集朴素贝叶斯ComplementNB六、文本分类案例TF-ID…

excel核对技巧:这么多数据对比的方法应该够用了

日常工作不时会需要对比数据&#xff0c;查找差异&#xff0c;查找重复值等。有的是对比同一工作表中的数据&#xff0c;有的是对比不同工作表之间的数据。希望接下来介绍的多种Excel数据对比方法&#xff0c;让大家能在不同情况下都能快速完成数据的对比。第一部分&#xff1a…

pytorch入门1--数据操作(张量)

一、张量的定义和变换 1.张量表示一个数值组成的数组&#xff0c;这个数组可能有多个维度。 说明&#xff0c;torch.arange(12)可以得到一个一维的&#xff08;有几层中括号就是几维数组&#xff0c;注意是层&#xff0c;不是个数&#xff09;&#xff0c;一个最内层的一个中括…

Qt图片定时滚动播放器

目录参考结构PicturePlay.promain.cpppictureplay.hpictureplay.cpppictureplay.ui效果参考 Qt图片浏览器 QT制作一个图片播放器 Qt中自适应的labelpixmap充满窗口后&#xff0c;无法缩小只能放大 可以显示jpg、jpeg、png、bmp。可以从电脑上拖动图到窗口并显示出来或者打开文件…

371. 两整数之和

题目&#xff1a; 给你两个整数 a 和 b &#xff0c;不使用 运算符 和 - &#xff0c;计算并返回两整数之和。 示例 1&#xff1a; 输入&#xff1a;a 1, b 2 输出&#xff1a;3 示例 2&#xff1a; 输入&#xff1a;a 2, b 3 输出&#xff1a;5 提示&#xff1a; -…

虚拟机VMware Workstation Pro环境搭建

VMware Workstation Pro是一款虚拟化工具&#xff0c;允许用户在Windows PC上运行多个操作系统。这个平台提供一个安全和独立的环境&#xff0c;让用户在使用前&#xff0c;可以建立和测试应用程序、检查修补程序&#xff0c;以及尝试不同的操作系统。它附有虚拟机库 它允许用户…

【大数据】大数据Hadoop生态圈

文章目录大数据Hadoop生态圈-组件介绍1、HDFS&#xff08;分布式文件系统&#xff09;2、MapReduce&#xff08;分布式计算框架&#xff09;3、Spark&#xff08;分布式计算框架&#xff09;4、Flink&#xff08;分布式计算框架&#xff09;5、Yarn/Mesos&#xff08;分布式资源…

LeetCode 热题 C++ 148. 排序链表 152. 乘积最大子数组 160. 相交链表

力扣148 给你链表的头结点 head &#xff0c;请将其按 升序 排列并返回 排序后的链表 。 示例 1&#xff1a; 输入&#xff1a;head [4,2,1,3] 输出&#xff1a;[1,2,3,4]示例 2&#xff1a; 输入&#xff1a;head [-1,5,3,4,0] 输出&#xff1a;[-1,0,3,4,5]示例 3&#x…

php mysql校园帮忙领取快递平台

1、后台管理员用户名hsg 密码hsg 2、开发语言&#xff1a;PHP&#xff0c;数据库为MySql 3、数据库连接字符串在conn.php中修改 4、运行环境wamp5.1.7或者appserv2.5.9 5.程序编码gbk.不支持php5.3以上版本 6.本人发布的程序一律享有免费运行一次…

西安银行就业总结

引 进银行性价比最高的时刻是本科&#xff0c;研究生的话可以去需要研究生较多的银行&#xff0c;比如邮储或者证券类的中信建投。中信建投很香&#xff0c;要求本硕西电。研究生学历的话&#xff0c;一般情况下银行不会卡本科&#xff0c;只看最高学历&#xff0c;部分银行需…

内核并发消杀器(KCSAN)技术分析

一、KCSAN介绍KCSAN(Kernel Concurrency Sanitizer)是一种动态竞态检测器&#xff0c;它依赖于编译时插装&#xff0c;并使用基于观察点的采样方法来检测竞态&#xff0c;其主要目的是检测数据竞争。KCSAN是一种检测LKMM(Linux内核内存一致性模型)定义的数据竞争(data race)的工…

网络应用之URL

URL学习目标能够知道URL的组成部分1. URL的概念URL的英文全拼是(Uniform Resoure Locator),表达的意思是统一资源定位符&#xff0c;通俗理解就是网络资源地址&#xff0c;也就是我们常说的网址。2. URL的组成URL的样子:https://news.163.com/18/1122/10/E178J2O4000189FH.html…

最好的个人品牌策略是什么样的

在这个自我营销的时代&#xff0c;个人品牌越来越受到人们的重视。您的个人品牌的成功与否取决于您在专业领域拥有的知识&#xff0c;以及拥有将这些知识传达给其他用户的能力。如果人们认为您没有能力并且无法有效地分享有用的知识&#xff0c;那么您就很难获得关注并实现长远…

树莓派Linux内核配置

文章目录一、嵌入式带操作系统的启动过程二、Linux内核源码树扫盲分析三、树莓派Linux源码配置1.树莓派Linux的内核配置2.树莓派Linux内核编译3、更换树莓派内核一、嵌入式带操作系统的启动过程 1.x86&#xff0c;Intel的启动过程&#xff1a; 电源上电->BIOS->Windows内…

PHP基础(2)

PHP基础常用函数数组及多维数组数组遍历强制类型转换运算符赋值与基本运算字符串运算逻辑运算符常用函数 substr的用法是&#xff1a;substr&#xff08;目标字符串&#xff0c;从字符串的哪个位置开始&#xff0c;然后返回往后的几个字符&#xff09;strchr的用法是&#xff1…

【华为OD机试模拟题】用 C++ 实现 - 滑动求和(2023.Q1)

最近更新的博客 华为OD机试 - 入栈出栈(C++) | 附带编码思路 【2023】 华为OD机试 - 箱子之形摆放(C++) | 附带编码思路 【2023】 华为OD机试 - 简易内存池 2(C++) | 附带编码思路 【2023】 华为OD机试 - 第 N 个排列(C++) | 附带编码思路 【2023】 华为OD机试 - 考古…