Jackson视图神技:一个DTO干掉N个DTO,告别DTO爆炸问题

大模型向量数据库微服务
前言

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

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

于是你开始创建各种DTO:


 
 
 
 
   
UserSummaryDTO、UserDetailDTO、UserAdminDTO...

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

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

最近建了一些工作内推群,各大城市都有,欢迎各位HR和找工作的小伙伴进群交流,群里目前已经收集了不少的工作内推岗位。

扫码下发二维码加苏三的微信,备注:所在城市,即可进群。

picture.image

痛点场景

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

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爆炸的困扰。

最后欢迎加入苏三的星球,你将获得:智能天气播报AI Agent、SaaS点餐系统(DDD+多租户)、100万QPS短链系统(超过并发)、复杂的商城微服务系统(分布式)、苏三商城系统、苏三AI项目、刷题吧小程序、秒杀系统、码猿简历网站、代码生成工具等10个项目的源代码、开发教程和技术答疑。 系统设计、性能优化、技术选型、底层原理、Spring源码解读、工作经验分享、痛点问题、面试八股文等多个优质专栏。

还有1V1免费修改简历、技术答疑、职业规划、送书活动、技术交流。

扫描下方二维码,可以加入星球:

picture.image

数量有限,先到先得。 目前星球已经更新了6100+篇优质内容,还在持续爆肝中.....

星球已经被官方推荐了3次,收到了小伙伴们的一致好评。戳我加入学习,已有2100+小伙伴加入学习

0
0
0
0
关于作者
关于作者

文章

0

获赞

0

收藏

0

相关资源
字节跳动 XR 技术的探索与实践
火山引擎开发者社区技术大讲堂第二期邀请到了火山引擎 XR 技术负责人和火山引擎创作 CV 技术负责人,为大家分享字节跳动积累的前沿视觉技术及内外部的应用实践,揭秘现代炫酷的视觉效果背后的技术实现。
相关产品
评论
未登录
看完啦,登录分享一下感受吧~
暂无评论