@JsonView 对比场景
数据库按需查询【推荐】
Dao查询列表的时候,仅仅查询基础信息,不包含密码信息;查询详情的时候,就把更详细的详细查询并返回;
定义不同的前端视图对象
查询的时候,都把详细的查询出来,定义不同的响应对象并赋值返回,List<UserSimpleInfo>和UserDetailsInfo
定义特定的对象转换工具
业务对象到响应的视图对象转换时,定义一个特殊的转换工具类,根据需要,忽略掉对应的值,比如之前介绍的:【还用 BeanUtils 拷贝对象?MapStruct 才是王者!】就可以实现
@JsonView【推荐】
同一个响应对象,通过指定不同的Json视图,来达到响应不同数据结构的目的
基础实现
创建一个用户对象
第一步:定义不同的视图对象
第二步:在属性的get方法上面指定不同的视图@JsonView(xxx.class)
;由于这里使用了Lombok,所以@JsonView注解直接添加在属性之上
package com.example.demo.pojo;
import com.example.demo.vo.BaseVO;
import com.fasterxml.jackson.annotation.JsonView;
import lombok.AllArgsConstructor;
import lombok.Data;
@Data
@AllArgsConstructor
public class User {
// 名字
@JsonView(UserSimpleView.class)
private String userName;
// 年龄
@JsonView(UserSimpleView.class)
private Integer age;
// 密码
@JsonView(UserDetailsView.class)
private String pwd;
// 基础的简单详细视图
public interface UserSimpleView extends BaseVO.BaseResponceView {
}
// 详情视图
// 详情视图继承自基础视图,意味着详情视图中包含了所有的基础视图数据
public interface UserDetailsView extends UserSimpleView {
}
}
package com.example.demo.vo;
import com.fasterxml.jackson.annotation.JsonView;
import lombok.AllArgsConstructor;
import lombok.Data;
@Data
@AllArgsConstructor
public class BaseVO<T> {
/**
* 状态码
*/
@JsonView(BaseResponceView.class)
private Integer code;
/**
* 状态描述
*/
@JsonView(BaseResponceView.class)
private String msg;
/**
* 数据
*/
@JsonView(BaseResponceView.class)
private T data;
// 基础的视图对象
public interface BaseResponceView {
}
}
测试用例
package com.example.demo;
import lombok.extern.slf4j.Slf4j;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.http.MediaType;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.context.web.WebAppConfiguration;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
import org.springframework.test.web.servlet.result.MockMvcResultMatchers;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.web.context.WebApplicationContext;
@RunWith(SpringRunner.class)
@SpringBootTest
@Slf4j
@WebAppConfiguration
public class UserTests {
@Autowired
private WebApplicationContext webApplicationContext;
private MockMvc mockMvc;
@Before
public void setup(){
mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext).build();
}
@Test
public void list() throws Exception {
String contentAsString = mockMvc.perform(MockMvcRequestBuilders.get("/user/list")
.accept(MediaType.APPLICATION_JSON_UTF8))
.andExpect(MockMvcResultMatchers.status().isOk())
.andReturn().getResponse().getContentAsString();
log.info(contentAsString);
}
@Test
public void userDetails() throws Exception{
String contentAsString = mockMvc.perform(MockMvcRequestBuilders.get("/user/1")
.accept(MediaType.APPLICATION_JSON_UTF8))
.andExpect(MockMvcResultMatchers.status().isOk())
.andReturn().getResponse().getContentAsString();
log.info(contentAsString);
}
}
运行结果
2021-08-05 11:20:53.751 INFO 4404 --- [ main] o.s.b.t.m.w.SpringBootMockServletContext : Initializing Spring TestDispatcherServlet ''
2021-08-05 11:20:53.751 INFO 4404 --- [ main] o.s.t.web.servlet.TestDispatcherServlet : Initializing Servlet ''
2021-08-05 11:20:53.752 INFO 4404 --- [ main] o.s.t.web.servlet.TestDispatcherServlet : Completed initialization in 1 ms
2021-08-05 11:20:53.817 INFO 4404 --- [ main] com.example.demo.UserTests : {"code":0,"msg":"成功","data":[{"userName":"a","age":10},{"userName":"b","age":20},{"userName":"c","age":70}]}
2021-08-05 11:31:40.974 INFO 5780 --- [ main] o.s.b.t.m.w.SpringBootMockServletContext : Initializing Spring TestDispatcherServlet ''
2021-08-05 11:31:40.974 INFO 5780 --- [ main] o.s.t.web.servlet.TestDispatcherServlet : Initializing Servlet ''
2021-08-05 11:31:40.975 INFO 5780 --- [ main] o.s.t.web.servlet.TestDispatcherServlet : Completed initialization in 1 ms
2021-08-05 11:31:41.044 INFO 5780 --- [ main] com.example.demo.UserTests : {"code":0,"msg":"成功","data":{"userName":"a","age":10,"pwd":"123"}}
业务对象的嵌套
上面列举了基础结构嵌套业务数据的示例,实际的开发中同样存在多个业务对象间的嵌套,不同的场景,返回的嵌套对象不同,对于
JsonView的配置也上面展示的基础结构配置没啥差异,举一反三即可实现了。
开头列举了几种不同的方式,来满足不同场景下返回不同数据结构的问题,并没有说哪一种就是最优的解决方案;需要根据不同的业务场景,来针对性选择;如果说单表的操作,可能直接通Dao层按需求查询对应的字段就能好了;如果业务逻辑比较复杂,最终数据来源于多个地方,通过数据库的方式会导致Dao越来越庞大,使用JsonView的方式可能很轻松就满足了需求;
我们最终目的是让结构更清晰,代码更合理,维护更容易,所以合适才是最重要。
JsonView仅支持jackson框架;SpringBoot默认使用的框架就是jackson;如果你将Http的消息转换对象由jackson配置成了FastJson,那么所有的@JsonView配置将全部失效
,所以务必注意;替换的具体配置如下:
@Bean
public HttpMessageConverters fastJsonHttpMessageConverters(){
FastJsonHttpMessageConverter fastJsonHttpMessageConverter = new FastJsonHttpMessageConverter();
FastJsonConfig fastJsonConfig = new FastJsonConfig();
fastJsonConfig.setSerializerFeatures(SerializerFeature.WriteMapNullValue,SerializerFeature.WriteNullStringAsEmpty,SerializerFeature.PrettyFormat);
fastJsonConfig.setDateFormat("yyyy-MM-dd HH:mm:ss");
List<MediaType> fastMediaTypes = new ArrayList<>();
fastMediaTypes.add(MediaType.APPLICATION_JSON_UTF8);
fastJsonHttpMessageConverter.setSupportedMediaTypes(fastMediaTypes);
fastJsonHttpMessageConverter.setFastJsonConfig(fastJsonConfig);
HttpMessageConverter<?> converter = fastJsonHttpMessageConverter;
return new HttpMessageConverters(converter);
}