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

作者:风象南日期:2025/11/6

前言

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

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

于是你开始创建各种DTO:

1UserSummaryDTOUserDetailDTOUserAdminDTO...
2

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

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

痛点场景

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

1. 用户实体类

1@Entity
2public class User {
3    private Long id;
4    private String username;
5    private String email;
6    private String phone;
7    private String address;
8    private String avatar;
9    private LocalDateTime createTime;
10    private LocalDateTime updateTime;
11    // getter/setter省略...
12}
13

2. 多种API需求

列表页API:只需要id和username

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

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

1GET /api/users/{id}
2// 期望返回:{id: 1, username: "张三", email: "[email protected]", phone: "13800138000", ...}
3

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

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

3. 传统解决方案的弊端

很多开发者会这样做:

1// 摘要DTO
2public class UserSummaryDTO {
3    private Long id;
4    private String username;
5}
6
7// 详情DTO
8public class UserDetailDTO {
9    private Long id;
10    private String username;
11    private String email;
12    private String phone;
13    private String address;
14    private String avatar;
15    private LocalDateTime createTime;
16}
17
18// 管理员DTO
19public class UserAdminDTO {
20    private Long id;
21    private String username;
22    private String email;
23    private String phone;
24    private String address;
25    private String avatar;
26    private LocalDateTime createTime;
27    private LocalDateTime updateTime;
28}
29

问题分析

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

Jackson Views解决方案

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

1. 定义视图接口

1public class Views {
2    // 公共基础视图
3    public interface Public {}
4
5    // 摘要视图(继承Public)
6    public interface Summary extends Public {}
7
8    // 详情视图(继承Summary)
9    public interface Detail extends Summary {}
10
11    // 管理员视图(继承Detail)
12    public interface Admin extends Detail {}
13}
14

2. 在DTO中使用@JsonView注解

1public class UserDTO {
2    @JsonView(Views.Public.class)
3    private Long id;
4
5    @JsonView(Views.Summary.class)
6    private String username;
7
8    @JsonView(Views.Detail.class)
9    private String email;
10
11    @JsonView(Views.Detail.class)
12    private String phone;
13
14    @JsonView(Views.Detail.class)
15    private String address;
16
17    @JsonView(Views.Detail.class)
18    private String avatar;
19
20    @JsonView(Views.Admin.class)
21    private LocalDateTime updateTime;
22
23    @JsonView(Views.Admin.class)
24    private String internalNote; // 管理员专用字段
25
26    // getter/setter省略...
27}
28

3. 在Controller中指定视图

1@RestController
2@RequestMapping("/api")
3public class UserController {
4
5    @Autowired
6    private UserService userService;
7
8    // 列表页 - 只返回基础信息
9    @GetMapping("/users")
10    @JsonView(Views.Summary.class)
11    public List<UserDTO> getUserList() {
12        return userService.getAllUsers();
13    }
14
15    // 详情页 - 返回详细信息
16    @GetMapping("/users/{id}")
17    @JsonView(Views.Detail.class)
18    public UserDTO getUserDetail(@PathVariable Long id) {
19        return userService.getUserById(id);
20    }
21
22    // 管理员接口 - 返回所有信息
23    @GetMapping("/admin/users/{id}")
24    @JsonView(Views.Admin.class)
25    public UserDTO getUserForAdmin(@PathVariable Long id) {
26        return userService.getUserById(id);
27    }
28}
29

4. 效果演示

调用列表页接口

1GET /api/users
2

响应结果

1[
2    {
3        "id": 1,
4        "username": "张三"
5    },
6    {
7        "id": 2,
8        "username": "李四"
9    }
10]
11

调用详情页接口

1GET /api/users/1
2

响应结果

1{
2    "id": 1,
3    "username": "张三",
4    "email": "[email protected]",
5    "phone": "13800138000",
6    "address": "北京市朝阳区",
7    "avatar": "http://example.com/avatar1.jpg"
8}
9

调用管理员接口

1GET /api/admin/users/1
2

响应结果

1{
2    "id": 1,
3    "username": "张三",
4    "email": "[email protected]",
5    "phone": "13800138000",
6    "address": "北京市朝阳区",
7    "avatar": "http://example.com/avatar1.jpg",
8    "updateTime": "2024-01-15T10:30:00",
9    "internalNote": "VIP用户,需要重点关注"
10}
11

高级用法

1. 多字段组合视图

1public class UserDTO {
2    // 基础信息
3    @JsonView(Views.Basic.class)
4    private Long id;
5
6    @JsonView(Views.Basic.class)
7    private String username;
8
9    // 联系信息
10    @JsonView(Views.Contact.class)
11    private String email;
12
13    @JsonView(Views.Contact.class)
14    private String phone;
15
16    // 统计信息
17    @JsonView(Views.Statistics.class)
18    private Integer loginCount;
19
20    @JsonView(Views.Statistics.class)
21    private LocalDateTime lastLoginTime;
22
23    // 敏感信息
24    @JsonView(Views.Sensitive.class)
25    private String realName;
26
27    @JsonView(Views.Sensitive.class)
28    private String idCard;
29}
30

2. 组合视图使用

1// 基础信息 + 联系信息
2public interface BasicContact extends Views.Basic, Views.Contact {}
3
4// 统计信息 + 敏感信息
5public interface FullStats extends Views.Statistics, Views.Sensitive {}
6
7@GetMapping("/users/contact")
8@JsonView(Views.BasicContact.class)
9public UserDTO getUserWithContact(@PathVariable Long id) {
10    return userService.getUserById(id);
11}
12

3. 动态视图选择

1@GetMapping("/users/{id}")
2public ResponseEntity<UserDTO> getUser(
3    @PathVariable Long id,
4    @RequestParam(defaultValue = "summary") String view) {
5
6    UserDTO user = userService.getUserById(id);
7
8    // 根据参数动态选择视图
9    Class<?> viewClass = switch (view.toLowerCase()) {
10        case "detail" -> Views.Detail.class;
11        case "admin" -> Views.Admin.class;
12        default -> Views.Summary.class;
13    };
14
15    return ResponseEntity.ok().body(user);
16}
17

最佳实践

1. 视图设计原则

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

2. 常用视图模板

1public class CommonViews {
2    // 公共接口
3    public interface Public {}
4
5    // 内部接口
6    public interface Internal extends Public {}
7
8    // 管理员接口
9    public interface Admin extends Internal {}
10
11    // 摘要信息
12    public interface Summary extends Public {}
13
14    // 详情信息
15    public interface Detail extends Summary {}
16
17    // 完整信息
18    public interface Full extends Detail {}
19
20    // 导出数据
21    public interface Export extends Full {}
22}
23

3. 避免常见陷阱

❌ 错误做法

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

✅ 正确做法

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

4. 与其他注解的配合

1public class UserDTO {
2    @JsonView(Views.Summary.class)
3    @JsonProperty("user_id")  // 自定义JSON字段名
4    private Long id;
5
6    @JsonView(Views.Detail.class)
7    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")  // 日期格式化
8    private LocalDateTime createTime;
9
10    @JsonView(Views.Admin.class)
11    @JsonIgnore  // 在某些视图中忽略字段
12    private String sensitiveData;
13}
14

总结

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

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

适用场景

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

不适用场景

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

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


Jackson视图神技:一个DTO干掉N个DTO,告别DTO爆炸问题》 是转载文章,点击查看原文


相关推荐


🚀 2025 年 10 月 GitHub 十大热门项目排行榜 🔥
一点一木2025/10/31

欢迎来到 2025 年 10 月 GitHub 热门开源项目排行榜!本期榜单精选了十个备受关注的仓库,横跨从提示工程教程、桌面代理框架、Claude 实用指南到金融 AI 分析、知识记忆引擎与云存储的多样化场景。这些项目不仅强化了 AI 学习路径、代理自主性与隐私保护,还为开发者提供了高效的集成工具与实验平台,推动开源从理论探索向生产级应用加速转型。 prompt-eng-interactive-tutorial 🌟 Star 数:25.3K+ 📚 Anthropic 开源的交互式提示工


【EI复现】基于深度强化学习的微能源网能量管理与优化策略研究(Python代码实现)
荔枝科研社2025/10/29

💥💥💞💞欢迎来到本博客❤️❤️💥💥 🏆博主优势:🌞🌞🌞博客内容尽量做到思维缜密,逻辑清晰,为了方便读者。 ⛳️座右铭:行百里者,半于九十。 📋📋📋本文目录如下:🎁🎁🎁 💥1 概述 文献来源: 根据微电网或微能源网是否与主电网相连接,可将其分为并网型和独立型 2 种。本文以并网型微 能源网为研究对象,研究其并网运行的能量管理与优化问题。目前,针对微能源网的能量管理,从算法上来讲,多结合最优化算法或者启发


Python 的内置函数 dir
IMPYLH2025/10/26

Python 内建函数列表 > Python 的内置函数 dir Python 的内置函数 dir() 是一个非常有用的工具函数,主要用于获取对象的属性和方法列表。该函数在不同使用场景下会返回不同类型的信息: def dir(obj): ''' 返回对象的成员列表 :param obj: 一个对象 :return: 对象的成员列表 ''' 不带参数使用时会返回当前作用域中的名称列表带参数使用时会返回指定对象的有效属性列表 典型应用场景包括:


JMeter对接口返回值进行AES解密
一半烟火以谋生2025/10/24

本文将从零开始,详细演示如何用JMeter实现接口返回值的AES解密,涵盖环境准备、脚本编写到结果验证的全流程。 1. 环境准备 安装JMeter:从官网下载最新版(建议5.4+)安装插件: 安装Custom JAR Functions插件(用于AES解密)将Apache Commons Codec库的JAR文件放入JMeter的/lib目录(提供Base64解码支持) 接口信息(示例): 接口地址:https://api.example.com/encrypted-data


AIGC-Fooocus部署实践:从本地手动配置到云端一键启用的深度剖析
Undoom2025/10/22

摘要: 本文旨在为人工智能生成内容(AIGC)领域的爱好者和开发者提供一份详尽的Fooocus部署指南。Fooocus作为一款基于Gradio的开源图像生成软件,凭借其简化的操作和高质量的输出,受到了广泛关注。我们将通过两种截然不同的部署路径——传统的本地手动环境配置与现代化的云平台一键部署——来全面探索Fooocus的落地过程。本文将深入剖析手动部署中的每一个步骤、每一条命令及其背后的技术逻辑,详细记录可能遇到的环境冲突与解决方案,并将其与云端部署的流畅体验进行客观对比,为读者在不同场景下选择


Orcad capture 原理图的电信号连接和符号
终身学习的暖男2025/10/21

电气连接相关的操作 绘制导线:Place -> Wire,快捷键【W】。 结束导线绘制:快捷键【Esc】,鼠标右键【End Wire】。 添加连接结点:Place -> Junction,快捷键【J】。 放置网络标号:Place -> Net Alias,快捷键【N】。 放置NA不连接点:Place -> No Connect,快捷键【X】。 总线:Place -> Bus,快捷键【B】(总线名称格式为 名称 + [],例如 BUS[0:5]、BUS[0-5]、BUS[0…5])。 总线分支:


告别数据混乱!掌握JSON与内置对象,让你的JS代码更专业
良山有风来2025/10/20

你是不是也有过类似的经历? 后端返回的数据看起来正常,但JS就是解析不了 日期显示成一串看不懂的数字,还得手动转换 处理复杂数据时写了无数循环,代码又长又难维护 本地存储数据后再读取,发现格式全乱了 别担心,今天我就带你彻底掌握JSON和JS内置对象的使用技巧。学完这篇文章,你不仅能轻松解决这些问题,还能写出更优雅、更专业的数据处理代码! JSON:数据交换的"普通话" 想象一下,你要跟一个外国朋友交流,但他说法语,你说中文,完全没法沟通怎么办?这时候就需要一种"通用语言"。 在编程世界里,


Java 怎么学习Kubernetes
自由的疯2025/10/19

学习 Kubernetes 可以是一项挑战,但也非常值得投入时间和精力,因为它在现代软件开发和运维中扮演着至关重要的角色。以下是一条结构化的学习路径,帮助你从零开始学习 Kubernetes,直至掌握其核心概念和实践技能。 1. 基础知识准备 1.1 了解容器技术 Docker:学习 Docker 是开始 Kubernetes 学习之旅的第一步。了解如何创建和管理容器,熟悉 Dockerfile 和 Docker Compose 的使用。 容器基础概念:理解容器与虚拟机的区别,了解容器的生命周


量子纠错(Quantum Error Correction, QEC)
deepdata_cn2025/10/17

量子纠错(QEC)是突破量子计算实用化瓶颈的核心技术,其本质是通过特殊编码和算法抵消量子比特的固有不稳定性,为容错量子计算奠定基础。从实验室的原理验证到特定领域的原型应用,量子纠错正逐步从理论走向实践,成为连接量子硬件与实用场景的关键桥梁。 一、量子纠错的核心价值 量子比特与经典比特最大的区别在于其脆弱性。经典比特的0和1状态稳定,而量子比特依赖叠加态(如|ψ⟩=α|0⟩+β|1⟩)和纠缠态存在,极易受环境干扰(如温度波动、电磁辐射、控制噪声)出现错误,即“退相干”——量子态会在微秒至毫秒级


嵌入式硬件——基于IMX6ULL的I2C实现
眰恦ゞLYF2025/10/16

一、I2C 基础概念与硬件特性 1.1 I2C 总线核心定义 I2C(Inter-Integrated Circuit)是飞利浦提出的串行半双工通信总线,核心特点是两根信号线实现多设备互联: SDA(Serial Data):双向数据线,用于传输数据;SCL(Serial Clock):双向时钟线,由主设备产生,同步数据传输;上拉电阻:SDA 和 SCL 需外接(或引脚内部配置)上拉电阻(通常 4.7KΩ),空闲时保持高电平;主从架构:同一总线中仅 1 个主设备(如 I.MX6ULL),可

首页编辑器站点地图

Copyright © 2025 聚合阅读

License: CC BY-SA 4.0