⭐⭐⭐ Spring Boot 项目实战 ⭐⭐⭐ Spring Cloud 项目实战
《Dubbo 实现原理与源码解析 —— 精品合集》 《Netty 实现原理与源码解析 —— 精品合集》
《Spring 实现原理与源码解析 —— 精品合集》 《MyBatis 实现原理与源码解析 —— 精品合集》
《Spring MVC 实现原理与源码解析 —— 精品合集》 《数据库实体设计合集》
《Spring Boot 实现原理与源码解析 —— 精品合集》 《Java 面试题 + Java 学习指南》

摘要: 原创出处 风象南 「风象南」欢迎转载,保留摘要,谢谢!


🙂🙂🙂关注微信公众号:【芋道源码】有福利:

  1. RocketMQ / MyCAT / Sharding-JDBC 所有源码分析文章列表
  2. RocketMQ / MyCAT / Sharding-JDBC 中文注释源码 GitHub 地址
  3. 您对于源码的疑问每条留言都将得到认真回复。甚至不知道如何读源码也可以请教噢。
  4. 新的源码解析文章实时收到通知。每周更新一篇左右。
  5. 认真的源码交流微信群。

前言

在API开发中,你是否遇到过这样的困扰:

  • 列表页只需要用户的id和name
  • 详情页需要显示用户的所有字段
  • 管理员页面需要看到敏感信息

于是你开始创建各种DTO:

UserSummaryDTO、UserDetailDTO、UserAdminDTO...

最终导致DTO类”爆炸”,代码维护成本激增。

今天分享一个被90%开发者忽略的Jackson”神技”——Jackson Views,用1个DTO + 注解,优雅解决API响应数据的多场景展示问题。

痛点场景

让我们先看一个典型的业务场景:

1. 用户实体类

@Entity
public class User {
private Long id;
private String username;
private String email;
private String phone;
private String address;
private String avatar;
private LocalDateTime createTime;
private LocalDateTime updateTime;
// getter/setter省略...
}

2. 多种API需求

列表页API:只需要id和username

GET /api/users
// 期望返回:[{id: 1, username: "张三"}, {id: 2, username: "李四"}]

详情页API:需要完整信息(除了敏感字段)

GET /api/users/{id}
// 期望返回:{id: 1, username: "张三", email: "zhang@qq.com", phone: "13800138000", ...}

管理员API:需要所有字段包括敏感信息

GET /api/admin/users/{id}
// 期望返回:所有字段信息

3. 传统解决方案的弊端

很多开发者会这样做:

// 摘要DTO
public class UserSummaryDTO {
private Long id;
private String username;
}

// 详情DTO
public class UserDetailDTO {
private Long id;
private String username;
private String email;
private String phone;
private String address;
private String avatar;
private LocalDateTime createTime;
}

// 管理员DTO
public class UserAdminDTO {
private Long id;
private String username;
private String email;
private String phone;
private String address;
private String avatar;
private LocalDateTime createTime;
private LocalDateTime updateTime;
}

问题分析:

  • DTO类数量爆炸式增长
  • 代码重复率高,维护成本大
  • 字段变更时需要同步修改多个DTO
  • 项目结构臃肿,可读性下降

Jackson Views解决方案

Jackson Views提供了一种优雅的解决方案:通过视图接口和注解,控制JSON序列化时包含哪些字段。

1. 定义视图接口

public class Views {
// 公共基础视图
public interface Public {}

// 摘要视图(继承Public)
public interface Summary extends Public {}

// 详情视图(继承Summary)
public interface Detail extends Summary {}

// 管理员视图(继承Detail)
public interface Admin extends Detail {}
}

2. 在DTO中使用@JsonView注解

public class UserDTO {
@JsonView(Views.Public.class)
private Long id;

@JsonView(Views.Summary.class)
private String username;

@JsonView(Views.Detail.class)
private String email;

@JsonView(Views.Detail.class)
private String phone;

@JsonView(Views.Detail.class)
private String address;

@JsonView(Views.Detail.class)
private String avatar;

@JsonView(Views.Admin.class)
private LocalDateTime updateTime;

@JsonView(Views.Admin.class)
private String internalNote; // 管理员专用字段

// getter/setter省略...
}

3. 在Controller中指定视图

@RestController
@RequestMapping("/api")
public class UserController {

@Autowired
private UserService userService;

// 列表页 - 只返回基础信息
@GetMapping("/users")
@JsonView(Views.Summary.class)
public List<UserDTO> getUserList() {
return userService.getAllUsers();
}

// 详情页 - 返回详细信息
@GetMapping("/users/{id}")
@JsonView(Views.Detail.class)
public UserDTO getUserDetail(@PathVariable Long id) {
return userService.getUserById(id);
}

// 管理员接口 - 返回所有信息
@GetMapping("/admin/users/{id}")
@JsonView(Views.Admin.class)
public UserDTO getUserForAdmin(@PathVariable Long id) {
return userService.getUserById(id);
}
}

4. 效果演示

调用列表页接口:

GET /api/users

响应结果:

[
{
"id": 1,
"username": "张三"
},
{
"id": 2,
"username": "李四"
}
]

调用详情页接口:

GET /api/users/1

响应结果:

{
"id": 1,
"username": "张三",
"email": "zhang@example.com",
"phone": "13800138000",
"address": "北京市朝阳区",
"avatar": "http://example.com/avatar1.jpg"
}

调用管理员接口:

GET /api/admin/users/1

响应结果:

{
"id": 1,
"username": "张三",
"email": "zhang@example.com",
"phone": "13800138000",
"address": "北京市朝阳区",
"avatar": "http://example.com/avatar1.jpg",
"updateTime": "2024-01-15T10:30:00",
"internalNote": "VIP用户,需要重点关注"
}

高级用法

1. 多字段组合视图

public class UserDTO {
// 基础信息
@JsonView(Views.Basic.class)
private Long id;

@JsonView(Views.Basic.class)
private String username;

// 联系信息
@JsonView(Views.Contact.class)
private String email;

@JsonView(Views.Contact.class)
private String phone;

// 统计信息
@JsonView(Views.Statistics.class)
private Integer loginCount;

@JsonView(Views.Statistics.class)
private LocalDateTime lastLoginTime;

// 敏感信息
@JsonView(Views.Sensitive.class)
private String realName;

@JsonView(Views.Sensitive.class)
private String idCard;
}

2. 组合视图使用

// 基础信息 + 联系信息
public interface BasicContact extends Views.Basic, Views.Contact {}

// 统计信息 + 敏感信息
public interface FullStats extends Views.Statistics, Views.Sensitive {}

@GetMapping("/users/contact")
@JsonView(Views.BasicContact.class)
public UserDTO getUserWithContact(@PathVariable Long id) {
return userService.getUserById(id);
}

3. 动态视图选择

@GetMapping("/users/{id}")
public ResponseEntity<UserDTO> getUser(
@PathVariable Long id,
@RequestParam(defaultValue = "summary") String view) {

UserDTO user = userService.getUserById(id);

// 根据参数动态选择视图
Class<?> viewClass = switch (view.toLowerCase()) {
case "detail" -> Views.Detail.class;
case "admin" -> Views.Admin.class;
default -> Views.Summary.class;
};

return ResponseEntity.ok().body(user);
}

最佳实践

1. 视图设计原则

继承优于平级:使用视图继承关系,避免重复定义
粒度适中:视图粒度既不能太细(导致过多视图类),也不能太粗(失去灵活性)
命名清晰:视图名称要能清晰表达其用途

2. 常用视图模板

public class CommonViews {
// 公共接口
public interface Public {}

// 内部接口
public interface Internal extends Public {}

// 管理员接口
public interface Admin extends Internal {}

// 摘要信息
public interface Summary extends Public {}

// 详情信息
public interface Detail extends Summary {}

// 完整信息
public interface Full extends Detail {}

// 导出数据
public interface Export extends Full {}
}

3. 避免常见陷阱

错误做法

// 视图层级过深,增加维护复杂度
public interface A extends B {}
public interface B extends C {}
public interface C extends D {}
public interface D extends E {}

正确做法

// 视图层级保持在3层以内
public interface Public {}
public interface Summary extends Public {}
public interface Detail extends Summary {}

4. 与其他注解的配合

public class UserDTO {
@JsonView(Views.Summary.class)
@JsonProperty("user_id") // 自定义JSON字段名
private Long id;

@JsonView(Views.Detail.class)
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") // 日期格式化
private LocalDateTime createTime;

@JsonView(Views.Admin.class)
@JsonIgnore // 在某些视图中忽略字段
private String sensitiveData;
}

总结

Jackson Views是一个强大但被低估的功能,它能够:

减少DTO类数量:从N个DTO合并为1个DTO
降低维护成本:字段变更时只需修改一处
提高代码可读性:视图名称直观,用途明确
保持灵活性:通过视图组合满足复杂业务需求

适用场景

  • 同一实体在不同接口中需要返回不同字段
  • 需要区分用户权限看到不同数据
  • API版本升级时需要渐进式暴露字段

不适用场景

  • 字段差异极大,无法通过视图合理组织
  • 需要复杂的字段转换逻辑(此时建议使用专门的DTO)

通过合理使用Jackson Views,我们可以构建出更加简洁、高效、易维护的API接口,告别DTO爆炸的困扰。

文章目录
  1. 1. 前言
  2. 2. 痛点场景
    1. 2.0.1. 1. 用户实体类
    2. 2.0.2. 2. 多种API需求
    3. 2.0.3. 3. 传统解决方案的弊端
  • 3. Jackson Views解决方案
    1. 3.0.1. 1. 定义视图接口
    2. 3.0.2. 2. 在DTO中使用@JsonView注解
    3. 3.0.3. 3. 在Controller中指定视图
    4. 3.0.4. 4. 效果演示
  • 4. 高级用法
    1. 4.0.1. 1. 多字段组合视图
    2. 4.0.2. 2. 组合视图使用
    3. 4.0.3. 3. 动态视图选择
  • 5. 最佳实践
    1. 5.0.1. 1. 视图设计原则
    2. 5.0.2. 2. 常用视图模板
    3. 5.0.3. 3. 避免常见陷阱
    4. 5.0.4. 4. 与其他注解的配合
  • 6. 总结