springboot学习(三)——http序列化/反序列化之HttpMessageConverter

以下内容,如有问题,烦请指出,谢谢!

上一篇说掉了点内容,这里补上,那就是springmvc的http的序列化/反序列化,这里简单说下如何在springboot中使用这个功能。

使用过原生netty http的人可能对http序列化比较熟悉,springmvc中的意思跟netty中的意思一样。http序列化(或者叫作http报文编码),就是将Java类转化为二进制流输出给http body;http反序列化,就是将http报文转换为程序内部的Java类。有了http反序列化,就不用再去一个个request.getParam("xxx")来获取参数,有了http序列化,就不用直接通过response.getWriter.write来输出结果。后面为了简单,http序列化/反序列化统一称作http序列化。

http序列化是一个合格的controller框架都应该具备的功能,有了它,在很多场景下,controller的方法只用写调用service的那一行代码,大大简化了业务代码逻辑的复杂性。

在springmvc中可以在controller方法的代码中直接写Java类作为参数,这样默认是通过参数名和属性名的配对,使用request.getParam("xxx")来完成的。在前后端分离时,有时候需要处理一些很层次很深、很复杂的参数,这时候通过普通的form-data并不是一个很好的选择,它们可读性差,难以解析。这种场景直接使用一些文本型格式(json/xml)的序列化/反序列化是个比较好的选择,编码非常简单,也便于统一入口处理。在springmvc中简单的说就是通过@RequestBody和@ResponseBody来注解请求参数和返回值,这个相信使用过springmvc的都知道怎么用。

那么不使用json格式来传输数据行不行,当然可以,http虽然名字是叫文本,但是一样可以用来传输二进制数据,也就是所有格式的数据。这样看起来可能很奇怪,但是在一些与非web前端进行http通信的地方,自定义http的数据格式就很常见,比如基于http的rpc服务。rpc为了满足通用性、低消耗性,一般会选择跨语言、时间性能好、压缩比高的序列化格式,比如protobuf。

springmvc自己就提供了protobuf的http序列化,在spring-web包中有个类叫做org.springframework.http.converter.protobuf.ProtobufHttpMessageConverter,就是用来处理protobuf格式的数据的,有兴趣可以去试试。

参考ProtobufHttpMessageConverter,我们可以写一个自己的http序列化,使用Java原生序列化读写对象,代码如下。

package pr.study.springboot.configure.mvc.converter;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.nio.charset.Charset;
import java.util.Base64;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
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 pr.study.springboot.bean.BaseBean;

public class JavaSerializationConverter extends AbstractHttpMessageConverter<Object> {
    private Logger LOGGER = LoggerFactory.getLogger(JavaSerializationConverter.class);

    public JavaSerializationConverter() {
        // 构造方法中指明consumes(req)和produces(resp)的类型,指明这个类型才会使用这个converter
        super(new MediaType("application", "x-java-serialization", Charset.forName("UTF-8")));
    }

    @Override
    protected boolean supports(Class<?> clazz) {
        return BaseBean.class.isAssignableFrom(clazz);
    }

    @Override
    protected Object readInternal(Class<? extends Object> clazz, HttpInputMessage inputMessage)
            throws IOException, HttpMessageNotReadableException {
        byte[] bytes = StreamUtils.copyToByteArray(inputMessage.getBody());
        // base64使得二进制数据可视化,便于测试
        ByteArrayInputStream bytesInput = new ByteArrayInputStream(Base64.getDecoder().decode(bytes));
        ObjectInputStream objectInput = new ObjectInputStream(bytesInput);
        try {
            return objectInput.readObject();
        } catch (ClassNotFoundException e) {
            LOGGER.error("exception when java deserialize, the input is:{}", new String(bytes, "UTF-8"), e);
            return null;
        }
    }

    @Override
    protected void writeInternal(Object t, HttpOutputMessage outputMessage)
            throws IOException, HttpMessageNotWritableException {
        ByteArrayOutputStream bytesOutput = new ByteArrayOutputStream();
        ObjectOutputStream objectOutput = new ObjectOutputStream(bytesOutput);
        objectOutput.writeObject(t);
        // base64使得二进制数据可视化,便于测试
        outputMessage.getBody().write(Base64.getEncoder().encode(bytesOutput.toByteArray()));
    }

}

可以使用下面的代码来,配置这个converter

    @Override
    public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
        // 仅仅添加一种新的converter,不删除默认添加的
        // 如果要删除可以使用 converters.clear()
        // 仅仅只有一种converter时,代表请求和响应默认都是这个converter代表的mediatype
        // 推荐使用这个方法添加converter
        converters.add(new JavaSerializationConverter());
    }

//    // 添加converter的第二种方式,会删除原来的converter
//    @Bean
//    public HttpMessageConverter<Object> javaSerializationConverter() {
//        return new JavaSerializationConverter();
//    }

//    // 添加converter的第三种方式,会删除原来的converter
//    @Override
//    public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
//        converters.add(new JavaSerializationConverter());
//    }

上面的代码要放在我们写的WebMvcConfigurerAdapter的子类中,如果使用我的代码,那就是在pr.study.springboot.configure.mvc.SpringMvcConfigure中。推荐使用第一种方式,如果你的业务确定只有一种http序列化方式,可以使用下面的几种,提升一些效率。

converter的代码里面注意一点,构造方法中千万要指明mediaType的类型(使用父类的构造方法是个很好的选择),指明这个类型才有机会使用这个converter。具体就是:

  • 通过http请求中的Headers.Content-Type指定的mediaType来决定使用哪个converter(controller方法要支持consumes这种mediaType)来处理这个req的body的序列化;
  • 通过http请求中的Headers.Accept指定的mediaType来决定使用哪个converter(controller方法要支持produces这种mediaType)来处理这个req的对应的resp的body的序列化,处理成功时对应的resp返回一个属于Accept子集的Content-Type;

json序列化使用下面的json

{"id":123,"name":"helloworld","email":"[email protected]","createTime":"2017-12-17 15:22:55"}

java序列化使用下面的数据

rO0ABXNyAB1wci5zdHVkeS5zcHJpbmdib290LmJlYW4uVXNlcrt1879rvWjlAgAESgACaWRMAApjcmVhdGVUaW1ldAAQTGphdmEvdXRpbC9EYXRlO0wABWVtYWlsdAASTGphdmEvbGFuZy9TdHJpbmc7TAAEbmFtZXEAfgACeHIAIXByLnN0dWR5LnNwcmluZ2Jvb3QuYmVhbi5CYXNlQmVhbklx6Fsr8RKpAgAAeHAAAAAAAAAAe3NyAA5qYXZhLnV0aWwuRGF0ZWhqgQFLWXQZAwAAeHB3CAAAAWBjWqyYeHQAEGhlbGxvd29ybGRAZy5jb210AApoZWxsb3dvcmxk

用来测试的代码如下:

@RestController
@RequestMapping("/users")
public class UserController {
    @Autowired
    private UserService userService;

    @GetMapping("/{id}")
    public User getUserById(@PathVariable long id) {
        return userService.getUserById(id);
    }

    @PostMapping
    public User createUser(@RequestBody User user) {
        System.err.println("create an user: " + user);
        return user;
    }
}

@Service
public class UserServiceImpl implements UserService {

    @Override
    public User getUserById(long id) {
        User user = new User();
        user.setId(123L);
        user.setName("helloworld");
        user.setEmail("[email protected]");
        user.setCreateTime(new Date());
        return user;
    }

}

下面的是一些运行结果图,对比下可以看出Accept和Content-Type对序列化反序列化的影响。

  • GET + Accept: application/x-java-serialization,resp使用java序列化返回

  • GET + Accept: application/json,resp使用json序列化返回

  • POST + Content-Type: application/x-java-serialization + Accept: application/x-java-serialization,req和resp都使用java序列化

  • POST + Content-Type: application/x-java-serialization + Accept: application/json,req使用java序列化,resp使用json序列化

  • POST + Content-Type: application/json + Accept: application/json,req和resp都使用json序列化

  • POST + Content-Type: application/json + Accept: application/x-java-serialization,req使用json序列化,resp使用java序列化

这里为了测试,post返回的是User对象,直接返回基本类型(包装类/BigDecimal)以及String的话,不会走普通对象序列化,会处理成直接使用它们的通用格式返回。

实际中根据应用的req/resp应用场景,来决定controller方法的consumes和produces,忽略这两个属性通常情况下不会有问题,springmvc内部的mediaType匹配机制还是比较好的。



再说点其他的小内容。

springmvc默认使用的是jackson框架来处理json。jackson在实际使用中还需要进行一些配置,比如关闭null值的属性的序列化,以及时间的序列化格式等等。要配置jackson,只需要自己配置注入ObjectMapper即可。如下:

package pr.study.springboot.configure.mvc.json;

import java.text.SimpleDateFormat;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import com.fasterxml.jackson.annotation.JsonInclude.Include;
import com.fasterxml.jackson.databind.ObjectMapper;

/**
 * jackson的核心是ObjectMapper,在这里配置ObjectMapper来控制springboot使用的jackson的某些功能
 */
@Configuration
public class MyObjectMpper {

    @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;
    }
}

如果要使用fastJson怎么办(有些公司会要求这些基础组件都使用相同的jar包,便于扩展维护)?跟在springmvc中方式差不多,自己配置一个FastJsonHttpMessageConverter就行。在springboot中的配置方式和上面我们写的java序列化差不多,mvc配置类中添加下列代码即可:

    @Override
    public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
        // 仅仅添加一种新的converter,不删除默认添加的
        // 如果要删除可以使用 converters.clear()
        // 仅仅只有一种converter时,代表请求和响应默认都是这个converter代表的mediatype
        // 推荐使用这个方法添加converter
        converters.add(new JavaSerializationConverter());

        // 使用fastJson代替jackson
        FastJsonHttpMessageConverter fastJsonConverter = new FastJsonHttpMessageConverter();

        FastJsonConfig fastJsonConfig = new FastJsonConfig();
        fastJsonConfig.setSerializerFeatures(SerializerFeature.WriteMapNullValue); // 序列化null属性
        fastJsonConfig.setDateFormat("yyyy-MM-dd HH:mm:ss"); // 默认的时间序列化格式
        fastJsonConverter.setFastJsonConfig(fastJsonConfig);

        converters.add(fastJsonConverter);
        System.err.println(converters);
    }

其他的再说一点,官方文档上面有说一个自定义JsonSerializer,这个有什么用呢?简单说就是你可以自己指定任何对象的json序列化格式,比如时间你可以序列化成中文的一些格式,rgb颜色一般都是三个byte存储,你可以序列化成css通用的格式。

这个功能我没用过,不过在网上找了个例子,大家可以参考下,说的是如何在json中将rgb颜色序列化成css格式

代码相关:

https://gitee.com/page12/study-springboot/tree/springboot-3

https://github.com/page12/study-springboot/tree/springboot-3

时间: 2024-10-29 00:26:09

springboot学习(三)——http序列化/反序列化之HttpMessageConverter的相关文章

springboot学习(三)————使用HttpMessageConverter进行http序列化和反序列化

以下内容,如有问题,烦请指出,谢谢! 对象的序列化/反序列化大家应该都比较熟悉:序列化就是将object转化为可以传输的二进制,反序列化就是将二进制转化为程序内部的对象.序列化/反序列化主要体现在程序I/O这个过程中,包括网络I/O和磁盘I/O. 那么什么是http序列化和反序列化呢? 在使用springmvc时,我们经常会这样写: @RestController @RequestMapping("/users") public class UserController { @Auto

C#三十一 序列化与反序列化

序列化又称串行化,是.NET运行时环境用来支持用户定义类型的流化的机制.其目的是以某种存储形成使自定义对象持久化,或者将这种对象从一个地方传输到另一个地方. .NET框架提供了两种串行化的方式:1.是使用BinaryFormatter进行串行化:2.使用SoapFormatter进行串行化:3.使用XmlSerializer进行串行化.第一种方式提供了一个简单的二进制数据流以及某些附加的类型信息,而第二种将数据流格式化为XML存储:第三种其实和第二种差不多也是XML的格式存储,只不过比第二种的X

一个C#版的序列化/反序列化器-SharpSerialization

项目地址:https://sourceforge.net/projects/sharpserialization/ 我们的实际工作中经常会遇到跨语言的交互,如Java-C#-C++等,大部分情况下通过Xml.Json数据交换等协议可以很好的处理对象的交互,但有时我们希望数据流变得更小,性能变得更好,但同时又不想对现有程序做过多修改或者不想对每个协议对象编写特定的代码,这时,一个良好的二进制序列化器是自然的一个考虑,对于.Net语言间的交互,Framework自带的序列化器是自然也很好的选择,但当

SpringBoot学习-SpringMVC自动配置

SpringBoot学习-SpringMVC自动配置 前言 在SpringBoot官网对于SpringMVCde 自动配置介绍 1-原文介绍如下: Spring MVC Auto-configuration Spring Boot provides auto-configuration for Spring MVC that works well with most applications. The auto-configuration adds the following features

DjangoRestFramework学习二之序列化组件、视图组件

目录 DjangoRestFramework学习二之序列化组件.视图组件 一 序列化组件 二 视图组件(Mixin混合类) DjangoRestFramework学习二之序列化组件.视图组件 本节目录 一 序列化组件 首先按照restful规范咱们创建一些api接口,按照下面这些形式写吧: Courses --- GET ---> 查看数据----->返回所有数据列表[{},{},] Courses--- POST --->添加数据 -----> 返回添加的数据{ } course

常用json序列化/反序列化技术对比测试

目前常用的json工具有:1.json-lib:2.jakson-mapper:3.fastjson. 下面对这三种工具的性能进行简单对比测试. 测试样本:一个126K的json文件,内容为json数组. 测试方法:反序列化,读取文件中的json转化为java对象. 测试代码如下: 1 @Test 2 public void testDeserialize() throws Exception { 3 String dealer = "d:\\auto\\json\\100016109.js&q

序列化反序列化api(入门级)

定义: java序列化是指把Java对象转换为字节序列的过程:而Java反序列化是指把字节序列恢复为Java对象的过程. 为什么字符串通常也会进行序列化? 对象需要进行序列化的原因:保证对象的状态不变(比如一个studunet): 字符串通常也进行序列化的原因:为了保证解析不出意外(比如编码可能不一致)(虽然字符串不序列化一般也不会报错). 同时以下原因也是一个很重要的因素: 对象.文件.数据,有许多不同的格式,很难统一传输和保存, 序列化以后就都是字节流了,无论原来是什么东西,都能变成一样的东

PHP5.5三种序列化性能对比

json_encode,serialize,igbinary三种序列化方式,在之前已经有过相关的测试,PHP5.5这方面的测试暂时没有,这次测试基于PHP5.5,并且测试用例,http://blog.csdn.net/hguisu/article/details/7651730的测试用例是一样的,只是从这个测试上家里igbinary serialize的测试,作为对比,可以参考http://www.ooso.net/archives/538 运行环境        PHP5.5 内存 16G 8

java序列化反序列化深入探究

When---什么时候需要序列化和反序列化: 简单的写一个hello world程序,用不到序列化和反序列化.写一个排序算法也用不到序列化和反序列化.但是当你想要将一个对象进行持久化写入文件,或者你想将一个对象从一个网络地址通过网络协议发送到另一个网络地址时,这时候就需要考虑序列化和反序列化了.另外如果你想对一个对象实例进行深度拷贝,也可以通过序列化和反序列化的方式进行. What---什么是序列化和反序列化: Serialization-序列化:可以看做是将一个对象转化为二进制流的过程 Des