HTTP 教程:https://www.w3cschool.cn/http/a96bxfml.html
HTTP请求返回接口类型控制(json/xml):https://www.cnblogs.com/dayou123123/p/8298917.html
HttpMessageConverter:https://www.jianshu.com/p/3e1de3d02dd8
本章演示代码:https://gitee.com/tysite-web/tysite-service/tree/master/src/main/java/org/tysite/tyservice/example/jsonview
在前后端分离的项目中,服务端接口不需要关注WEB页面的渲染工作,只需要专注于业务数据的回传和状态信息反馈,然后由前端同学完成页面渲染。所以在日常开发中,我们会通过HTTP状态码
标记响应状态,通过响应正文传递接口回传信息。
为了更好的格式化响应信息,当前主流的方式是采用JSON
格式返回。spring mvc 为我们提供了@ResponseBody
注解,可以根据请求头的Accept
参数值,调用对应的响应转换器HttpMessageConverter
将请求结果对象以对应格式写入响应体(jackson默认情况下不支持xml格式,会以json格式返回)
在日常开发的工作中,通常遇到不同业务场景下,相同结果对象所需要返回的字段信息不同。
例如:
1、面对用户信息接口,管理端使用时,需要返回用户的手机号、身份证号码等敏感信息,以便管理员查看、修改;而用户端使用时则仅需要呈现姓名、头像等非敏感信息。
2、管理列表呈现的字段信息与数据编辑界面呈现的数据也有不同。
Spring MVC 为我们提供了@JsonView注解,实现对结果对象的回显字段过滤功能,本文将详细讲解@JsonView的使用及推荐用法。
在我们的tysite-service
项目搭建时,依赖使用的是org.springframework.boot:spring-boot-starter-web
,该依赖集中已经包含com.fasterxml.jackson.core:jackson-databind
相关依赖,无需额外添加。
注意:tysite-service
项目以返回json
格式数据为目标,故不能添加com.fasterxml.jackson.dataformat:jackson-dataformat-xml
依赖,否则chrome浏览器默认会以xml
格式返回。
@JsonView
通过类标识
标注不同的属性过滤方案,并在Controller
方法上使用对应过滤方案的类标识
,得到预期的请求体JSON数据。
注意: 上面说的类标识
可以是class
,也可以是interface
。作者建议采用POJO
的内部接口
来定义过滤方案的类标识
。
具体实现方案如下:
第一步,在作为结果集返回对象的POJO类
中,根据业务需求定义 内部接口
作为类标识。
……
public interface ListView {}
public interface DetailView extends ListView{}
……
注意: 类标识支持继承关系,如果属性注释了子类,则相当于其父类也生效。
第二步,根据业务需求,通过@JsonView注解注释相关属性
……
/** 数字字段 */
@JsonView({ListView.class})
private Integer id;
/** 名称字段 */
@JsonView({ListView.class})
private String name;
/** 密码字段 */
@JsonView({DetailView.class})
private String password;
……
演示对象源码地址:JsonViewInfoDTO
第三步,完成Controller接口注释
……
@GetMapping("/list")
@JsonView(JsonViewInfoDTO.ListView.class)
public JsonViewInfoDTO demoList() {
return jsonViewService.getJsonViewInfo();
}
@GetMapping("/detail")
@JsonView(JsonViewInfoDTO.DetailView.class)
public JsonViewInfoDTO demoDetail() {
return jsonViewService.getJsonViewInfo();
}
……
演示Controller源码地址:JsonViewController
第四步,启动项目,分别调用列表和详情接口,查看响应体的属性过滤效果。
调用:/api/example/json-view/list
接口
调用:/api/example/json-view/detail
接口
综上所述:JsonViewInfoDTO.DetailView.class
类标识的响应体比JsonViewInfoDTO.ListView.class
类标识的响应体多返回了password
属性。以此方案实现以相同POJO
类作为响应对象时的属性过滤。
细心的读者会发现,上面两张截图中,typs
和attribute
属性没能正常返回数据,而是空对象{}
。这是因为@JsonView
默认只能对基本类型
的对象属性进行JSON
序列化。而对象类型
的对象属性,我们需要通过@JsonSerialize
注解指定序列化类
。
这里作者分别以 JsonViewTypeDTO
和 List
两个对象类型为例,讲解@JsonSerialize
的用法。
第一步,我们在JsonViewTypeDTO.java
类所在的dto
包下,创建 serializer
包,并在该包中,创建JsonViewTypeDTO
的Json序列化处理类:JsonViewTypeSerializer
public class JsonViewTypeSerializer extends JsonSerializer<JsonViewTypeDTO> { (1)
@Override
public void serialize(JsonViewTypeDTO value, JsonGenerator gen, SerializerProvider serializers) throws IOException { (2)
gen.writeStartObject();
gen.writeNumberField("id", value.getId());
gen.writeStringField("name", value.getName());
gen.writeEndObject();
}
}
(1) 序列化类JsonViewTypeSerializer
继承泛型类JsonSerializer
,并且以待序列化的POJO类JsonViewTypeDTO
作为类型变量。
(2) 覆盖serialize()
方法,实现对JsonViewTypeDTO
的序列化。
value
:对象实例,包含待序列化对象的所有当前属性值。
gen
:序列化后的结果集,我们通过gen.write***
方法组装序列化后的JSON数据。
writeStartObject() …… writeEndObject()
:用于组建json对象,相当于json数据中的 {}
。
上例中序列化后的json
数据,将包含id
和name
两个属性,我们可以根据自己的业务需要构建属性集合。
第二步,在对象属性上使用@JsonSerialize
注解,且using值采用JsonViewTypeSerializer.class
。
……
/** 对象字段 */
@JsonView({ListView.class})
@JsonSerialize(using = JsonViewTypeSerializer.class)
private JsonViewTypeDTO type;
……
第一步,我们在serializer
包下创建List
的序列化类:JsonViewAttributeListSerializer
public class JsonViewAttributeListSerializer extends JsonSerializer<List<JsonViewAttributeDTO>> {
@Override
public void serialize(List<JsonViewAttributeDTO> value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
if (null != value && value.size() > 0) {
gen.writeStartArray();
for (JsonViewAttributeDTO attributeDTO : value) {
gen.writeStartObject();
gen.writeNumberField("id", attributeDTO.getId());
gen.writeStringField("name", attributeDTO.getName());
gen.writeEndObject();
}
gen.writeEndArray();
}
}
}
从上面的示例代码中,我们可以发现JsonSerializer
的类型变量是我们要序列化的对象类型List
,并且在serialize()
方法中,我们先判断List
值是否为空,当非空状态下,对属性对象进行循环处理。
注意
:writeStartArray() / writeEndArray()
相当于json数据中的 []
,writeStartObject() / writeEndObject()
相当于json数据中的 {}
第二步,在对象属性上使用@JsonSerialize
注解,且using值采用JsonViewAttributeListSerializer.class
。
……
/** 对象列表字段 */
@JsonView({ListView.class})
@JsonSerialize(using = JsonViewAttributeListSerializer.class)
private List<JsonViewAttributeDTO> attribute;
……
在@JsonView
的使用中,作者给出如下建议:
1、为了便于管理类标识
,所有类标识
均采用需要使用@JsonView过滤属性的POJO
类(entity或dto)的内部接口
实现。
2、类标识视图
支持继承,建议所有存在字段包含关系的内部接口
采用继承方案。
3、@JsonView({A.class, B.class})
支持数组,可将多个必要且不包含继承关系的类标识视图
放在同一个@JsonView注解中。
4、强烈建议,所有Controller接口,结果对象必须进行@JsonView
过滤。
5、POJO
类的序列化处理类,建议存放在该类所在包的serializer
包下,便于后期维护。
|- controller
|- entity // 存放持久层对象
|- serializer //序列化处理类(无序列化处理类可不创建该包)
|- dto // 存放数据传输对象
|- serializer //序列化处理类(无序列化处理类可不创建该包)
|- service