在API开发中,你是否遇到过这样的困扰:
- • 列表页只需要用户的id和name
- • 详情页需要显示用户的所有字段
- • 管理员页面需要看到敏感信息
于是你开始创建各种DTO:
UserSummaryDTO、UserDetailDTO、UserAdminDTO...
最终导致DTO类"爆炸",代码维护成本激增。
今天分享一个被90%开发者忽略的Jackson"神技"——Jackson Views ,用1个DTO + 注解,优雅解决API响应数据的多场景展示问题。
最近建了一些工作内推群,各大城市都有,欢迎各位HR和找工作的小伙伴进群交流,群里目前已经收集了不少的工作内推岗位。
扫码下发二维码加苏三的微信,备注:所在城市,即可进群。
让我们先看一个典型的业务场景:
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提供了一种优雅的解决方案:通过视图接口和注解,控制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爆炸的困扰。
最后欢迎加入苏三的星球,你将获得:智能天气播报AI Agent、SaaS点餐系统(DDD+多租户)、100万QPS短链系统(超过并发)、复杂的商城微服务系统(分布式)、苏三商城系统、苏三AI项目、刷题吧小程序、秒杀系统、码猿简历网站、代码生成工具等10个项目的源代码、开发教程和技术答疑。 系统设计、性能优化、技术选型、底层原理、Spring源码解读、工作经验分享、痛点问题、面试八股文等多个优质专栏。
还有1V1免费修改简历、技术答疑、职业规划、送书活动、技术交流。
扫描下方二维码,可以加入星球:
数量有限,先到先得。 目前星球已经更新了6100+篇优质内容,还在持续爆肝中.....
