目录
前言
JSON的可读性比XML强几条长安街,解析规则也简单许多。XML解析的时候规则太多了,动不动就非法字符,动不动就抛异常。这对追求高开发速度和低开发门槛的企业来说是个致命伤。
在一个项目的各接口间、前后端之间数据的传输多使用 JSON格式的数据进行传输交互。而Spring Boot框架项目接口返回 JSON格式的数据比较简单:在 Controller 类中使用@RestController注解即可返回 JSON格式的数据。
@RestController是SpringBoot 新增的一个注解,OK,看下这个注解类:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Controller
@ResponseBody
public @interface RestController {
/**
* The value may indicate a suggestion for a logical component name,
* to be turned into a Spring bean in case of an autodetected component.
* @return the suggested component name, if any (or empty String otherwise)
* @since 4.0.1
*/
@AliasFor(annotation = Controller.class)
String value() default "";
}
可以看出, @RestController 注解包含了:@Controller和@ResponseBody两个核心注解。
- @Controller:当一个类引入这个注解,表明当前的这个类是一个controller控制器类;
- @ResponseBody:表示方法返回值的注释应该绑定到web响应的消息体中。支持带注释的处理程序方法。从4.0版本开始,这个注释也可以添加到类型级别,在这种情况下,它是继承的,不需要添加到方法级别。
1. SpringBoot默认的JSON依赖
@ResponseBody注解是将返回的数据结构转换为 Json 格式。所以,默认情况下,使用了`@RestController 注解,即可将返回的数据结构转换成 JSON格式。
在Spring Boot 中默认使用的 JSON解析技术框架是 jackson。这里,我们怎么去看SpringBoot默认的JSON依赖呢,OK,直接去找pom.xml文件中的spring-boot-starter-web这个web启步依赖:
进入spring-boot-starter-web依赖,查看一下这个包它的JSON依赖关系:
这里,我们知道,SpringBoot的web起步依赖,已经封装了spring-boot-starter-json依赖,也可以看到很多spring-boot-starter-xxx系列的依赖,这是 Spring Boot 的特点之一,不需要去引入很多相关的依赖了,"spring-boot-starter-xxx "系列直接都包含了所必要的依赖。所以,再次点上面这个 "spring-boot-starter-json"依赖,一探究竟:
看上图jar包依赖,原来SpringBoot中默认使用的JSON解析框架是 jackson。下面我们看一下默认的 jackson 框架对常用数据类型的转 JSON处理。
2. SpringBoot默认对JSON的处理
在项目开发过程中,我们常用的数据结构大致有:类对象、List对象、Map对象。
OK,来看一下SpringBoot默认的jackson框架对这三种常用的数据结构转成 JHSON后的格式如何。
2.1 实体类
测试需要,先来一个实体类:SysUser.java
**
* 系统用户实体类
*/
public class SysUser{
private String id;
private String userName;
private String password;
/**此处省略构造方法、setr和getr方法**/
}
2.2 Controller控制器
有了实体类,还得一个controller把它的信息返回给前端。
controller控制器:SysUserController.java
@RestController
@RequestMapping("/jsonFormat1")
public class SystemUser1Controller {
@GetMapping("/user")
public SystemUser getUser() {
return new SystemUser("1", "小明", "xiaoming123");
}
@GetMapping("/list")
public List<SystemUser> getUserList() {
List<SystemUser> list= new ArrayList<>();
SystemUser xiaoming= new SystemUser("1", "小明", "xiaoming123");
SystemUser xiaoli = new SystemUser("2", "小丽", "xiaoli123");
list.add(xiaoming);
list.add(xiaoli);
return list;
}
@GetMapping("/map")
public Map<String, Object> getMap() {
Map<String, Object> map = new HashMap<>();
SystemUser user = new SystemUser("1", "xiaoming", "xiaomign123");
map.put("用户信息", user);
map.put("在干啥呢", "working");
map.put("备注说明", "hello world");
map.put("一串数字",123456789);
return map;
}
}
2.3 测试返回的JSON数据
OK,准备工作已完成,分别返回:一个实体类对象、一个List 集合和一个Map 集合。其中 Map 集合中的 value 存的是不同的数据类型。接下来,我们依次来测试一下各自返回的JSON数据。
【1】返回实体类对象
访问地址:
127.0.0.1:8080/jsonFormat1/user
返回结果:
{"id":"1","userName":"小明","password":"xiaoming123"}
【2】返回List集合
访问地址:
127.0.0.1:8080/jsonFormat1/list
返回结果:
[{"id":"1","userName":"小明","password":"xiaoming123"},{"id":"2","userName":"小丽","password":"xiaoli123"}]
【3】返回Map集合
访问地址:
127.0.0.1:8080/jsonFormat1/map
返回结果:
{"用户信息":{"id":"1","userName":"xiaoming","password":"xiaomign123"},"备注说明":"hello world","在干啥呢":"working","一串数字":123456789}
可以看出,map 中不管是什么数据类型,都可以转成相应的 json 格式,这样就非常方便。
2.4 jackson中对null的处理
在项目开发中,难免会遇到null 值出现。数据封装为JSON格式时,实际上是不希望数据出现null。如果我们期望所有的null 在转换为JSON格式时都变成 "" 这种空字符串,那怎么做呢?
在SpringBoot 中,做以下配置即可,新建一个 jackson 的配置类:
/**
* jackson数据格式处理类:null值处理
*/
@Configuration
public class JacksonConfig {
@Bean
@Primary
@ConditionalOnMissingBean(ObjectMapper.class)
public ObjectMapper jacksonObjectMapper(Jackson2ObjectMapperBuilder builder) {
ObjectMapper objectMapper = builder.createXmlMapper(false).build();
objectMapper.getSerializerProvider().setNullValueSerializer(new JsonSerializer<Object>() {
@Override
public void serialize(Object o, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException {
jsonGenerator.writeString("");
}
});
return objectMapper;
}
}
OK,修改一下上面返回map的接口中的部分传值,将其改为null测试:
修改程序:
@GetMapping("/map")
public Map<String, Object> getMap() {
Map<String, Object> map = new HashMap<>();
SystemUser user = new SystemUser("1", "xiaoming", "xiaomign123");
map.put("用户信息", user);
map.put("在干啥呢", null);
map.put("备注说明", "hello world");
map.put("一串数字", null);
return map;
}
浏览器访问:localhost:8080/jsonFormat2/map
返回结果:
{"用户信息":{"id":"1","userName":"xiaoming","password":"xiaomign123"},"备注说明":"hello world","在干啥呢":"","一串数字":""}
OK,JacksonConfig配置类已将所有null值字段转换为空字符串了。
3. SpringBoot配置阿里巴巴fastjson
3.1 jackson和fastjson对比
【1】开源jsckson
- Jackson所依赖的jar包较少,简单易用并且性能也要相对高些。
- 而且Jackson社区相对比较活跃,更新速度也比较快。
- Jackson对于复杂类型的json转换bean会出现问题,一些集合Map,List的转换出现问题。
- Jackson对于复杂类型的bean转换Json,转换的json格式不是标准的Json格式
【2】阿里巴巴fastjson
- fastjson是一个Java语言编写的高性能的JSON处理器,由阿里巴巴公司开发。
- 无依赖,不需要例外额外的jar,能够直接跑在JDK上。
- fastJson在复杂类型的Bean转换Json上会出现一些问题,可能会出现引用的类型,导致Json转换出错,需要制定引用。
- fastJson采用独创的算法,将parse的速度提升到极致,超过所有json库。
指标 | jsckson | fastjson |
难易度 | 容易 | 中等 |
高级特性支持 | 中等 | 丰富 |
官方文档、Example支持 | 中文 | 英文 |
处理json速度 | 略快 | 快 |
从扩展上来看,fastJson没有 jackson 灵活,从速度或者上手难度来看,fastJson 可以考虑。目前项目中使用的是阿里的 fastJson,挺方便的。
3.2 pom.xml文件配置fastjson依赖
SpringBoot项目配置fastjson较为简单,直观。在pom.xml文件的<dependencies></dependencies>标签中配置依赖信息即可。
这里,我们配置的fastjson版本:1.2.60。配置如下:
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.62</version>
</dependency>
3.3 使用fastjson处理null值
使用fastJson时,对null的处理和jackson有些不同。需要继承WebMvcConfigurationSupport类,然后覆盖configureMessageConverters方法,在方法中,我们可以选择对要实现 null 转换的场景,配置好即可。
4. 封装统一返回的数据结构
以上是SpringBoot 返回 JSON的几个代表的例子,但是在实际项目中,除了要封装数据之外,我们往往需要在返回的 JSON中添加一些其他信息,比如返回一些状态码code ,返回一些msg给调用者,这样调用者可以根据 code 或者 msg 做一些逻辑判断。所以在实际项目中,我们需要封装一个统一的 json 返回结构存储返回信息。
4.1 定义统一的 json 结构
由于封装的 json 数据的类型不确定,所以在定义统一的 json 结构时,我们需要用到泛型。统一的 json 结构中属性包括数据、状态码、提示信息即可,构造方法可以根据实际业务需求做相应的添加即可,一般来说,应该有默认的返回结构,也应该有用户指定的返回结构。
【1】控制器响应实体类,返回接口调用信息描述
/**
* 控制器响应结果实体类,返回接口调用信息描述
*/
public class ResponseMessage extends BaseModel {
private static final long serialVersionUID = -7127875856370230011L;
/**
* 状态
*/
private int status = 20000;
/**
* 消息说明
*/
private String message;
/**
* 数据信息
*/
private Object data;
public ResponseMessage() {
}
public ResponseMessage(int status, String message) {
this.status = status;
this.message = message;
}
public ResponseMessage(int status, String message, Object data) {
this.status = status;
this.message = message;
this.data = data;
}
/**此处省略变量的set和get方法**/
}
【2】信息状态
public enum ResponseStatus {
/**
* 操作成功
*/
SUCCESS(20000, "操作成功"),
/**
* 操作失败
*/
FAILURE(50000, "操作失败"),
/**
* 请求错误
*/
REQUEST_ERROR(100, "请求错误"),
/**
* 服务端程序错误
*/
SERVER_INTERNAL_ERROR(500, "服务端内部错误"),
/**
* 服务类错误
*/
SERVICE_EXCEPTION(600, "业务逻辑错误");
/**
* 响应码
*/
private int status;
/**
* 描述信息
*/
private String message;
ResponseStatus() {
}
ResponseStatus(int status, String message) {
this.status = status;
this.message = message;
}
public int getStatus() {
return status;
}
public String getMessage() {
return message;
}
}
4.2 改造Controller控制器
定义了返回结果的消息响应实体和消息状态后,所有的接口方法均可以返回消息响应实体对象,而返回的JSON数据可以统一作为入参传递给实体对象。
在具体的接口场景中,将只需将不同的返回数据替换成具体的数据类型即可,非常方便,也便于维护。在实际项目中,还可以继续封装,比如状态码和提示信息还封装为一个枚举类型,这样只需要维护这个枚举类型中的数据即可(暂不赘述)。
根据以上统一定义JSON结构,我们改造一下Controller控制器:
@RestController
@RequestMapping("/jsonFormat3")
public class SystemUser3Controller {
@GetMapping("/user")
public ResponseMessage getUser() {
SystemUser systemUser = new SystemUser("1", "小明", "xiaoming123");
return new ResponseMessage(ResponseStatus.SUCCESS.getStatus(), ResponseStatus.SUCCESS.getMessage(), systemUser);
}
@GetMapping("/list")
public ResponseMessage getUserList() {
List<SystemUser> list = new ArrayList<>();
SystemUser xiaoming = new SystemUser("1", "小明", "xiaoming123");
SystemUser xiaoli = new SystemUser("2", "小丽", "xiaoli123");
list.add(xiaoming);
list.add(xiaoli);
return new ResponseMessage(ResponseStatus.SUCCESS.getStatus(), ResponseStatus.SUCCESS.getMessage(), list);
}
@GetMapping("/map")
public ResponseMessage getMap() {
Map<String, Object> map = new HashMap<>();
SystemUser user = new SystemUser("1", "xiaoming", "xiaomign123");
map.put("用户信息", user);
map.put("在干啥呢", "working");
map.put("备注说明", "hello world");
map.put("一串数字", 123456789);
return new ResponseMessage(ResponseStatus.SUCCESS.getStatus(), ResponseStatus.SUCCESS.getMessage(), map);
}
}
4.3 测试统一返回的JSON数据格式
OK,准备工作已完成。接下来,我们依次来测试一下定义的统一各自返回的JSON数据。
【1】返回实体类对象
访问地址:
127.0.0.1:8080/jsonFormat3/user
返回结果:
{"data":{"id":"1","password":"xiaoming123","userName":"小明"},"message":"操作成功","status":20000}
【2】返回List集合
访问地址:
127.0.0.1:8080/jsonFormat3/list
返回结果:
{"data":[{"id":"1","password":"xiaoming123","userName":"小明"},{"id":"2","password":"xiaoli123","userName":"小丽"}],"message":"操作成功","status":20000}
【3】返回Map集合
访问地址:
127.0.0.1:8080/jsonFormat3/map
返回结果:
{"data":{"用户信息":{"id":"1","password":"xiaomign123","userName":"xiaoming"},"备注说明":"hello world","在干啥呢":"working","一串数字":123456789},"message":"操作成功","status":20000}
通过定义统一返回的JSON数据格式,并对其重新封装封装,将JSON格式数据传给了前端或者其他接口,返回的消息响应体带上了消息状态码和操作提示信息,这在实际项目场景中应用非常广泛。
5. 小结
本节内容主要对SpringBoot中JSON数据的返回、数据格式处理及响应信息结构体封装做了详细的分析与总结。
从Spring Boot 默认的 jackson 框架到阿里巴巴的fastJson 框架,分别对它们的配置做了相应的学习与总结。结合实际项目情况,总结了项目开发过程中使用的 JSON封装结构体,加入了状态码和提示信息,使返回的JSON数据信息更加完整。
项目名称:spring-boot-json
源码地址:https://github.com/JohnnyHL/SpringBoot-Item