HttpMessageConverter是如何进行http序列化和反序列化的

587 阅读6分钟

一、背景介绍:

大家在使用spring进行开发接口的时候有没有思考过,框架对于接口的入参和返参的格式都是怎么处理的?比如正常的上传文件,下载文件,查询某个信息,入参的格式都不一样,有 json、文件流,返回信息也是,那这块框架帮助我们做了什么,所以下来咱们揭开 HttpMessageConverter 的面纱。

二、HttpMessageConverter介绍

org.springframework.http.converter.HttpMessageConverter 是一个策略接口,HTTP request (请求)和response (响应)的转换器,该类衍生出不同的子类处理不同的格式的请求。

在讲解子类关系之前首先需要了解一些HTTP的基本知识:

1、决定resp.body的Content-Type的第一要素是对应的req.headers.Accept属性的值,又叫做MediaType。如果服务端支持这个Accept,那么应该按照这个Accept来确定返回resp.body对应的格式,同时把resp.headers.Content-Type设置成自己支持的符合那个Accept的MediaType。服务端不支持Accept指定的任何MediaType时,应该返回错误406 Not Acceptable. 例如:req.headers.Accept = text/html,服务端支持的话应该让resp.headers.Content-Type = text/html,并且resp.body按照html格式返回。 例如:req.headers.Accept = text/asdfg,服务端不支持这种MediaType,应该返回406 Not Acceptable。

2、如果Accept指定了多个MediaType,并且服务端也支持多个MediaType,那么Accept应该同时指定各个MediaType的QualityValue,也就是q值,服务端根据q值的大小来决定这几个MediaType类型的优先级,一般是大的优先。q值不指定时,默认视为q=1. Chrome的默认请求的Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,/;q=0.8,表示服务端在支持的情况下应该优先返回text/html,其次是application/xhtml+xml. 前面几个都不支持时,服务器可以自行处理 /,返回一种服务器自己支持的格式。

3、一个HTTP请求没有指定Accept,默认视为指定 Accept: /;没有指定Content-Type,默认视为 null,就是没有。当然,服务端可以根据自己的需要改变默认值。

4、Content-Type必须是具体确定的类型,不能包含 *.

SpringMvc基本遵循上面这几点。

然后是启动时默认加载的Converter。在mvc启动时默认会加载下面的几种HttpMessageConverter,相关代码在 org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport中的addDefaultHttpMessageConverters方法中,代码如下:

protected final void addDefaultHttpMessageConverters(List<HttpMessageConverter<?>> messageConverters) {
        messageConverters.add(new ByteArrayHttpMessageConverter());
        messageConverters.add(new StringHttpMessageConverter());
        messageConverters.add(new ResourceHttpMessageConverter());
        messageConverters.add(new ResourceRegionHttpMessageConverter());

        try {
            messageConverters.add(new SourceHttpMessageConverter());
        } catch (Throwable var3) {
        }

        messageConverters.add(new AllEncompassingFormHttpMessageConverter());
        if (romePresent) {
            messageConverters.add(new AtomFeedHttpMessageConverter());
            messageConverters.add(new RssChannelHttpMessageConverter());
        }

        Jackson2ObjectMapperBuilder builder;
        if (jackson2XmlPresent) {
            builder = Jackson2ObjectMapperBuilder.xml();
            if (this.applicationContext != null) {
                builder.applicationContext(this.applicationContext);
            }

            messageConverters.add(new MappingJackson2XmlHttpMessageConverter(builder.build()));
        } else if (jaxb2Present) {
            messageConverters.add(new Jaxb2RootElementHttpMessageConverter());
        }

        if (jackson2Present) {
            builder = Jackson2ObjectMapperBuilder.json();
            if (this.applicationContext != null) {
                builder.applicationContext(this.applicationContext);
            }

            messageConverters.add(new MappingJackson2HttpMessageConverter(builder.build()));
        } else if (gsonPresent) {
            messageConverters.add(new GsonHttpMessageConverter());
        } else if (jsonbPresent) {
            messageConverters.add(new JsonbHttpMessageConverter());
        }

        if (jackson2SmilePresent) {
            builder = Jackson2ObjectMapperBuilder.smile();
            if (this.applicationContext != null) {
                builder.applicationContext(this.applicationContext);
            }

            messageConverters.add(new MappingJackson2SmileHttpMessageConverter(builder.build()));
        }

        if (jackson2CborPresent) {
            builder = Jackson2ObjectMapperBuilder.cbor();
            if (this.applicationContext != null) {
                builder.applicationContext(this.applicationContext);
            }

            messageConverters.add(new MappingJackson2CborHttpMessageConverter(builder.build()));
        }

    }

当我们配置了自己的 MessageConverter, SpringMVC 启动过程就不会调用 addDefaultHttpMessageConverters 方法,且看下面代码 if 条件,这样做也是为了定制化我们自己的 MessageConverter:

    protected final List<HttpMessageConverter<?>> getMessageConverters() {
        if (this.messageConverters == null) {
            this.messageConverters = new ArrayList();
            this.configureMessageConverters(this.messageConverters);
            if (this.messageConverters.isEmpty()) {
                this.addDefaultHttpMessageConverters(this.messageConverters);
            }

            this.extendMessageConverters(this.messageConverters);
        }

        return this.messageConverters;
    }

HttpMessageConverter 的子类支持的MediaType如下:

类名 支持的JavaType 支持的MediaType
ByteArrayHttpMessageConverter byte[] application/octet-stream, * / *
StringHttpMessageConverter String text/plain, * / *
ResourceHttpMessageConverter Resource * / *
SourceHttpMessageConverter Source application/xml, text/xml, application/*+xml
AllEncompassingFormHttpMessageConverter Map<K, List<?>> application/x-www-form-urlencoded, multipart/form-data
MappingJackson2HttpMessageConverter Object application/json, application/*+json
Jaxb2RootElementHttpMessageConverter Object application/xml, text/xml, application/*+xml
JavaSerializationConverter Serializable x-java-serialization;charset=UTF-8
FastJsonHttpMessageConverter Object * / *

这里只列出重要的两个属性,详细的可以去看org.springframework.http.converter包中的代码。 另外,基本类型都视为对应的包装类的类型来算。还有,基本类型的json序列化就只有字面值,没有key,不属于规范的json序列化,但是基本上所有json框架都支持基本类型直接序列化。

三、MappingJackson2HttpMessageConverter介绍

此处专门讲解MappingJackson2HttpMessageConverter 的流程,因为该类是处理application/json的,咱们一般实际上都会在这里对数据进行自定义的处理:

下图为MappingJackson2HttpMessageConverter的类图关系:

咱们查看MappingJackson2HttpMessageConverter 的父类AbstractJackson2HttpMessageConverter,通过idea对该类 alt+7 查看该类的所有方法,如下图可以看到这个抽象类的方法:

其中关键的方法:

  • read(): 该方法会读取请求的body里的数据,并根据当前的controller入参定义的对象,进行反序列;
  • write():该方法将接口从service返回的数据进行序列化处理;

当然还有一些其他的方法,比如canRead()和canWrite()是做一些辅助处理的,具体流程可参照该图:

客户端 Request Header 中设置好 Content-Type(传入的数据格式)和Accept(接收的数据格式),根据配置好的MessageConverter来判断是否 canRead 或 canWrite,然后决定 response.body 的 Content-Type 的第一要素是对应的request.headers.Accept 属性的值,又叫做 MediaType。如果服务端支持这个 Accept,那么应该按照这个 Accept 来确定返回response.body 对应的格式,同时把 response.headers.Content-Type 设置成自己支持的符合那个 Accept 的 MediaType。

下面debug调试源码:

在接受请求转发后会到达 AbstractJackson2HttpMessageConverter 的readWithMessageConverters 方法,该方法会遍历当前加载的Converters信息与当前的请求的的信息进行判断,是否可以canRead,可以的话就会执行后续的读取操作:

 label94: {
                message = new AbstractMessageConverterMethodArgumentResolver.EmptyBodyCheckingHttpInputMessage(inputMessage);
                Iterator var11 = this.messageConverters.iterator();

                HttpMessageConverter converter;
                Class converterType;
                GenericHttpMessageConverter genericConverter;
                while(true) {
                    if (!var11.hasNext()) {
                        break label94;
                    }

                    converter = (HttpMessageConverter)var11.next();
                    converterType = converter.getClass();
                    genericConverter = converter instanceof GenericHttpMessageConverter ? (GenericHttpMessageConverter)converter : null;
                    if (genericConverter != null) {
                        if (genericConverter.canRead(targetType, contextClass, contentType)) {
                            break;
                        }
                    } else if (targetClass != null && converter.canRead(targetClass, contentType)) {
                        break;
                    }
                }

                if (message.hasBody()) {
                    HttpInputMessage msgToUse = this.getAdvice().beforeBodyRead(message, parameter, targetType, converterType);
                    body = genericConverter != null ? genericConverter.read(targetType, contextClass, msgToUse) : converter.read(targetClass, msgToUse);
                    body = this.getAdvice().afterBodyRead(body, msgToUse, parameter, targetType, converterType);
                } else {
                    body = this.getAdvice().handleEmptyBody((Object)null, message, parameter, targetType, converterType);
                }
            }

当前的MappingJackson2HttpMessageConverter匹配上,进入了如下的界面,此时会根据当前的VO类进行处理:

继续深入debug,会进入到ObjectMapper类的_readMapAndClose方法将内容反序列出来:

接口在从db获取数据经过业务处理返回给客户端的时候按照上述的操作将数据write进行序列化。

发现了吗?Converter也是通过ObjectMapper 类进行序列化和反序列操作的,如果我们想要定制化可以通过他进行处理:

如下代码是对属性值为null的不序列化以及时间格式的处理(此处处理是全局设置),具体的还包含哪些处理可在网上寻找更多例子:

@Bean
    public ObjectMapper getObjectMapper() {
        ObjectMapper mapper = new ObjectMapper();
        mapper.setSerializationInclusion(Include.NON_NULL); // 不序列化null的属性
        mapper.setDateFormat(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss")); // 默认的时间序列化格式
        return mapper;
    }

到此位置,大家应该对这块的流程大概了解了一些,建议大家也手动debug走走流程。

四、结尾:

文章中讲解的ObjectMapper,我们可以通过对这块进行参数调整,来满足我们的需求,具体可参照这篇文章 Jackson详解