自定义MessageConverter【原创】

作者: elijah777 | 来源:发表于2019-07-01 23:33 被阅读19次

    自定义MessageConverter

    简介

    HttpMessageConverter是用来处理request和reponse里的数据的。spring内置了大量的HttpMessageConvert,例如。MappingJackson2HttpMessageConverter、StringHttpMessageConverter等。本篇文章只要讲自定义的HttpMessageConvert,并注册 HttpMessageConverter到SpringMVC。

    代码示例

    1、自定义HttpMessageConverter

    import com.ch4.domain.DemoObj;
    import org.springframework.http.HttpInputMessage;
    import org.springframework.http.HttpOutputMessage;
    import org.springframework.http.MediaType;
    import org.springframework.http.converter.AbstractHttpMessageConverter;
    import org.springframework.http.converter.HttpMessageNotReadableException;
    import org.springframework.http.converter.HttpMessageNotWritableException;
    import org.springframework.util.StreamUtils;
    import org.springframework.util.StringUtils;
    ​
    import java.io.IOException;
    import java.nio.charset.Charset;
    ​
    /**
     * @description: 自定义MessageConverter
     *
     * @author: Shenshuaihu
     * @version: 1.0
     * @data: 2019-06-24 23:31
     */
    public class MyMessageConverter extends AbstractHttpMessageConverter<DemoObj> {
    ​
     /**
     * 自定义的媒体类型 application/x-wisely
     */
     public MyMessageConverter() {
     super(new MediaType("application", "x-wisely", Charset.forName("UTF-8")));
     }
    ​
     /**
     * HttpMessageConverter 只处理 DemoObj类
     * @param aClass
     * @return
     */
     @Override
     protected boolean supports(Class<?> aClass) {
     return DemoObj.class.isAssignableFrom(aClass);
     }
    ​
     /**
     * 重写 readInternal 处理请求数据 。此处由‘-’隔开的数据,转化DemoObj对象
     * @param aClass
     * @param httpInputMessage
     * @return
     * @throws IOException
     * @throws HttpMessageNotReadableException
     */
     @Override
     protected DemoObj readInternal(Class<? extends DemoObj> aClass, HttpInputMessage httpInputMessage) throws IOException, HttpMessageNotReadableException {
    ​
     String temp = StreamUtils.copyToString(httpInputMessage.getBody(), Charset.forName("UTF-8"));
     String[] tempArr = temp.split("-");
     return new DemoObj(new Long(tempArr[0]), tempArr[1]);
     }
    ​
     /**
     *  重写writeInternal 处理出如何输出数据到response 此处在原样中输出加上“hello”
     * @param obj
     * @param httpOutputMessage
     * @throws IOException
     * @throws HttpMessageNotWritableException
     */
     @Override
     protected void writeInternal(DemoObj obj, HttpOutputMessage httpOutputMessage) throws IOException, HttpMessageNotWritableException {
    ​
     String out = "hello:" + obj.getId() + "-" +obj.getName();
     httpOutputMessage.getBody().write(out.getBytes());
     }
    }
    

    继承了AbstractHttpMessageConverter,重写了里面的几个方法,readInternal ,处理请求数据 。此处由‘-’隔开的数据,转化DemoObj对象;writeInternal 处理出如何输出数据到response 此处在原样中输出加上“hello”; 自定义的媒体类型 application/x-wisely

    2、配置自定义的HttpMessageConverter的Bean

    需要在Spring MVC里注册 HttpMessageConverter 两个方法

     /**
     * 添加一个自定义的HttpMessageConverter,不会覆盖默认注册的HttpMessageConverter
     * @param converters
     */
     @Override
     public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
     converters.add(converter());
     }
    ​
     /**
     * 自定义的bean注册进来
     * @return
     */
     @Bean
     public MyMessageConverter converter() {
     return new MyMessageConverter();
     }
    

    extendMessageConverters 仅添加一个自定义的 HttpMessageConverter,不会覆盖默认的HttpMessageConverter。

    configureMessageConverters 方法 重载会覆盖掉SpringMVC 默认注册的多个HttpMessageConverter

    3、 控制器ConverterController

    import com.ch4.domain.DemoObj;
    import org.springframework.stereotype.Controller;
    import org.springframework.web.bind.annotation.RequestBody;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.ResponseBody;
    ​
    /**
     * @description:
     *
     * @author: Shenshuaihu
     * @version: 1.0
     * @data: 2019-06-25 08:37
     */
    ​
    @Controller
    public class ConverterController {
    ​
     @RequestMapping(value = "/convert", produces = {"application/x-wisely"})
     public @ResponseBody DemoObj converter(@RequestBody DemoObj demoObj) {
     return demoObj;
     }
    }
    

    这是是接受一个 DemoObj 类型的参数并返回,实际上用参入字符串格式然后通过HttpMessageConverter中的readInternal 解析为 dto,并使用HttpMessageConverter中的writeInternal 处理输出!

    简单的DTO DemoObj

    import lombok.AllArgsConstructor;
    import lombok.Data;
    import lombok.NoArgsConstructor;
    ​
    /**
     * @description:  用于获取request对象参数和返回此对象到response
     *
     * @author: Shenshuaihu
     * @version: 1.0
     * @data: 2019-06-15 10:49
     */
    @Data
    @NoArgsConstructor
    @AllArgsConstructor
    public class DemoObj {
     private Long id;
     private String name;
    }
    

    4、页面 converter.jsp

    <%@ page language="java" contentType="text/html; charset=UTF-8"
     pageEncoding="UTF-8"%>
    <%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
    <!DOCTYPE html>
    <html>
    <head>
     <title>Spting-Code</title>
     <link rel="stylesheet" type="text/css" value="">
    </head>
    <title>converter page</title>
    <body>
    <h2> converter.jsp </h2>
    ​
    <div id="resp"></div>
    <input type="button" onclick="req();" value="请求"/> <br/>
    <script src="http://libs.baidu.com/jquery/2.0.0/jquery.min.js"></script>
    ​
    <script>
     function req() {
     $.ajax({
     url: "convert",
     data: "1-ssh",
     type: "POST",
     contentType: "application/x-wisely",
     success: function (data) {
     console.log("data :" + data)
     $("#resp").html(data);
     }
     })
     }
    </script>
    </body>
    </html>
    

    传入服务器的数据是 1-ssh 会被处理为DTO 媒体类型是 application/x-wisely

    5、演示效果图

    ![json调用链.png](https://img.haomeiwen.com/i16573347/c19438c0abe3d2b5.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

    源码j简单分析

    org.springframework.http.converter.json.MappingJackson2HttpMessageConverter.java

    同意是定义数据类型,在转格式

    // Json 格式数据
     public MappingJackson2HttpMessageConverter(ObjectMapper objectMapper) {
     super(objectMapper, new MediaType[]{new MediaType("application", "json", DEFAULT_CHARSET), new MediaType("application", "*+json", DEFAULT_CHARSET)});
     }
    
    protected void writePrefix(JsonGenerator generator, Object object) throws IOException {
     if (this.jsonPrefix != null) {
     generator.writeRaw(this.jsonPrefix);
     }
    ​
     String jsonpFunction = object instanceof MappingJacksonValue ? ((MappingJacksonValue)object).getJsonpFunction() : null;
     if (jsonpFunction != null) {
     generator.writeRaw(jsonpFunction + "(");
     }
    ​
     }
    ​
    ​
     public void writeRaw(String text) throws IOException {
     int len = text.length();
     int room = this._outputEnd - this._outputTail;
     if (room == 0) {
     this._flushBuffer();
     room = this._outputEnd - this._outputTail;
     }
    ​
     if (room >= len) {
     text.getChars(0, len, this._outputBuffer, this._outputTail);
     this._outputTail += len;
     } else {
     this.writeRawLong(text);
     }
    ​
     }
    

    在AbstractMessageConverterMethodArgumentResolver的readWithMessageConverters方法里调用HttpMessageConverter处理消息

     protected <T> Object readWithMessageConverters(HttpInputMessage inputMessage, MethodParameter methodParam, Type targetType) throws IOException, HttpMediaTypeNotSupportedException {
     MediaType contentType;
     try {
     contentType = inputMessage.getHeaders().getContentType();
     } catch (InvalidMediaTypeException var10) {
     throw new HttpMediaTypeNotSupportedException(var10.getMessage());
     }
    ​
     if (contentType == null) {
     contentType = MediaType.APPLICATION_OCTET_STREAM;
     }
    ​
     Class<?> contextClass = methodParam.getContainingClass();
     Class<T> targetClass = ResolvableType.forMethodParameter(methodParam, targetType).resolve(Object.class);
     Iterator var7 = this.messageConverters.iterator();
    ​
     HttpMessageConverter converter;
     do {
     if (!var7.hasNext()) {
     throw new HttpMediaTypeNotSupportedException(contentType, this.allSupportedMediaTypes);
     }
    ​
     converter = (HttpMessageConverter)var7.next();
     if (converter instanceof GenericHttpMessageConverter) {
     GenericHttpMessageConverter<?> genericConverter = (GenericHttpMessageConverter)converter;
     if (genericConverter.canRead(targetType, contextClass, contentType)) {
     if (this.logger.isDebugEnabled()) {
     this.logger.debug("Reading [" + targetType + "] as \"" + contentType + "\" using [" + converter + "]");
     }
    ​
     return genericConverter.read(targetType, contextClass, inputMessage);
     }
     }
     } while(!converter.canRead(targetClass, contentType));
    ​
     if (this.logger.isDebugEnabled()) {
     this.logger.debug("Reading [" + targetClass.getName() + "] as \"" + contentType + "\" using [" + converter + "]");
     }
    ​
     return converter.read(targetClass, inputMessage);
     }
    

    主要就是取出所有的HttpMessageConverter,然后调用read方法读取消息,再处理MediaType,返回body。 在拦截器链执行后,HttpMessageConverter处理的消息就是返回的http消息了。

    json调用链.png

    MappingJackson2HttpMessageConverter extends AbstractJackson2HttpMessageConverter

    abstract class AbstractJackson2HttpMessageConverter extends AbstractHttpMessageConverter<Object> implements GenericHttpMessageConverter<Object>

    abstract class AbstractHttpMessageConverter<T> implements HttpMessageConverter<T>

    
    public interface HttpMessageConverter<T> {
        boolean canRead(Class<?> var1, MediaType var2);
    
        boolean canWrite(Class<?> var1, MediaType var2);
    
        List<MediaType> getSupportedMediaTypes();
    
        T read(Class<? extends T> var1, HttpInputMessage var2) throws IOException, HttpMessageNotReadableException;
    
        void write(T var1, MediaType var2, HttpOutputMessage var3) throws IOException, HttpMessageNotWritableException;
    }
    
    

    参考内容:
    Spring源码分析(五) MappingJackson2HttpMessageConverter

    2019/07/01 晚于成都

    相关文章

      网友评论

        本文标题:自定义MessageConverter【原创】

        本文链接:https://www.haomeiwen.com/subject/mgpocctx.html