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":"helloworld@g.com","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("helloworld@g.com"); 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即可。如下:
```java
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配置类中添加下列代码即可:java
@Override
public void extendMessageConverters(List
springboot学习(三)——http序列化/反序列化之HttpMessageConverter的更多相关文章
- SpringBoot学习(三)-->Spring的Java配置方式之读取外部的资源配置文件并配置数据库连接池
三.读取外部的资源配置文件并配置数据库连接池 1.读取外部的资源配置文件 通过@PropertySource可以指定读取的配置文件,通过@Value注解获取值,具体用法: @Configuration ...
- 学习C# XmlSerializer 序列化反序列化XML
类.变量常用头: [XmlRootAttribute]:对根节点的描述,在类声明中使用 如:下例的Html类 [XmlType]:对节点描述,在类声明中使用 如:下例的Head类 [X ...
- springboot学习三:整合jsp
在pom.xml加入jstl <!--springboot tomcat jsp 支持开启--> <dependency> <groupId>org.apache. ...
- SpringBoot学习(三):日志
1.日志框架 小张:开发一个大型系统: 1.System.out.println(""):将关键数据打印在控制台:去掉?写在一个文件? 2.框架来记录系统的一些运行时信息: ...
- springboot学习(三)————使用HttpMessageConverter进行http序列化和反序列化
以下内容,如有问题,烦请指出,谢谢! 对象的序列化/反序列化大家应该都比较熟悉:序列化就是将object转化为可以传输的二进制,反序列化就是将二进制转化为程序内部的对象.序列化/反序列化主要体现在程序 ...
- springboot学习(三)——使用HttpMessageConverter进行http序列化和反序列化
以下内容,如有问题,烦请指出,谢谢! 对象的序列化/反序列化大家应该都比较熟悉:序列化就是将object转化为可以传输的二进制,反序列化就是将二进制转化为程序内部的对象.序列化/反序列化主要体现在程序 ...
- Java开发学习(三十六)----SpringBoot三种配置文件解析
一. 配置文件格式 我们现在启动服务器默认的端口号是 8080,访问路径可以书写为 http://localhost:8080/books/1 在线上环境我们还是希望将端口号改为 80,这样在访问的时 ...
- 大型分布式C++框架《三:序列化与反序列化》
一.前言 个人感觉序列化简单来说就是按一定规则组包.反序列化就是按组包时的规则来接包.正常来说.序列化不会很难.不会很复杂.因为过于复杂的序列化协议会导致较长的解析时间,这可能会使得序列化和反序列化 ...
- Java开发学习(三十七)----SpringBoot多环境配置及配置文件分类
一.多环境配置 在工作中,对于开发环境.测试环境.生产环境的配置肯定都不相同,比如我们开发阶段会在自己的电脑上安装 mysql ,连接自己电脑上的 mysql 即可,但是项目开发完毕后要上线就需要该配 ...
随机推荐
- Linux学习(十六)VIM
一.简介 VIM是vi的增强版.VIM是Linux平台上的主要编辑器.基本上所有的文档的新增,修改,保存都需要用到它.所以,掌握VIM是很有必要的. vim的安装非常简单,一条命令就可以了: yum ...
- 用cmd命令创建oracle 数据库、用户和表空间
Win+R调查运行窗口,输入cmd回车 连接到本地oracle库 创建名为"SXSJ"的表空间,其初始大小为512M,支持自动扩展,每次增量为32M: create tablesp ...
- Gmail,QMail,163邮箱的 IMAP/SMTP/POP3 地址
我们在客户端设置邮箱或者使用 PHPMailer 发送邮件的时候,我们都会去查找这些邮箱的 IMAP/SMTP/POP3 地址,这里就列出 Gmail, QMail, 163邮箱这三个常用邮箱的这些地 ...
- ANDROID基础ACTIVITY篇之Activity的生命周期(一)
首先我们先来看一下官方的Android的生命周期图: 根据这个流程图我们可以看到Activity的生命周期一共有7个方法,那么接下来我们就来聊聊这些方法执行过程. 首先在两个Activity(Main ...
- 你应该学会的Postman用法
postman这个神器相信大家都用过,程序员作为非专业的测试人员,非常需要这么一款简单轻量级的restful测试工具,但是不知道你是否知道,postman的强大之处不只是测试一下接口,还有其他非常赞的 ...
- Android 之旅开始了!先自我了解下Android与Linux之间的关系
Android是在Linux2.6的内核基础之上运行的,提供核心系统服务:安全.内存管理.进程管理.网络组.驱动模型.内核部分还相当于一个介于硬件层和系统中其他软件组之间的一个抽象层次.但是严格来说它 ...
- websocket做手机页面聊天与PC页面聊天一对一的即时通讯
当时要写这个需求的时候,很头痛,手机端页面的客服功能,相当于QQ这样一个一对一聊天室功能了,瞬间蒙蔽的我也不知道用什么去写这个东西,一开始用ajax,定时器去写,写着写着发现这尼玛不在同一个页面怎么做 ...
- Spring学习笔记(二)之装配Bean
一,介绍Bean的装配机制 在Spring中,容器负责对象的创建并通过DI来协调对象之间的关系.但是我们要告诉Spring创建哪些Bean并且如何将其装配在一起.,装配wiring就是DI依赖注入的本 ...
- 使用vue2.x+webpack+vuex+sass+axios+elementUI等快速搭建前端项目框架
一.本文将分享如何快速搭起基于webpack+vue的前端项目框架,利用vue的自己的脚手架工具vue-cli搭建起基本的环境配置,再通过npm包管理工具引入相应的依赖来完善项目的各种依赖框架.下面是 ...
- OC面向对象的三大特性
一.面向对象的三大特性:封装(成员变量).继承和多态 1. set方法和get方法 1. set方法和get方法的使用场合 @public的成员可以被随意赋值,应该使用set方法和get方法来管理成员 ...