@JsonView + 單一 DTO:如何實現多場景 JSON 字段動態渲染
- @JsonView + 單一 DTO:如何實現多場景 JSON 字段動態渲染
- 1、@JsonView 注解產生的背景
- 2、為了滿足不同場景下返回對應的屬性的做法有哪些?
- 2.1 最快速的實現則是針對不同場景新建不同的 DTO 對象
- 2.2 使用 @JsonView 注解實現不同DTO對象的返回
- 2.2.1 定義同一個 DTO 對象
- 2.2.2 區分不同的響應視圖
- 2.2.3 Controller 層的調用
- 2.2.4 簡要信息 視圖 DTO
- 2.2.5 詳情信息 視圖 DTO
- 2.3 問題已解決(引入原理實現篇)
- 3、Debug調試篇
- 3.1 SpringMvc 切入點 對應的核心代碼片段
- 3.2 字段屬性序列化核心邏輯
- 3.3 多種 DTO 視圖 Demo 驗證
- 3.3.1 ObjectMapper 配置視圖 View
- 3.3.2 ObjectWriter 配置視圖 View
- 3.4 小結
- 4、擴展點
- 4.1 ResponseBodyAdvice接口
- 4.2 RequestBodyAdvice接口
@JsonView + 單一 DTO:如何實現多場景 JSON 字段動態渲染
1、@JsonView 注解產生的背景
@JsonView 是 Jackson 庫提供的一個注解,用于控制 Java 對象序列化為 JSON 時的字段可見性。通過定義不同的“視圖”(View),可以靈活地決定哪些字段在特定場景下被序列化,從而避免為不同接口編寫多個相似的 DTO 類。
2、為了滿足不同場景下返回對應的屬性的做法有哪些?
2.1 最快速的實現則是針對不同場景新建不同的 DTO 對象
細心的童鞋可以發現:雖然是不同的 DTO,但是存在共同的屬性,而且比如后面再來一個需求,這個接口僅返回基本信息的字段(外加一個手機號字段),那么我們是不是還需要創建一個新的 DTO 對象呢?如果針對每一個接口返回都定義一個 DTO 對象的話,對于后端代碼的維護也是相當的冗余操作,鑒于這種需求,有沒有一種對應后端的 其他 解決方案呢?答案是有的。即就是(@JsonView注解)。
2.2 使用 @JsonView 注解實現不同DTO對象的返回
2.2.1 定義同一個 DTO 對象
/*** @Description 用戶DTO* @Author Mr.Gao* @Date 2025/4/16 23:43*/
@Getter
@Setter
public class User {/*** 用戶姓名*/@JsonView(SimpleInfoView.class)private String username;/*** 用戶年齡*/@JsonView(SimpleInfoView.class)private Integer age;/*** 用戶性別*/@JsonView(SimpleInfoView.class)private String sex;/*** =================以下信息為用戶敏感信息不能給用戶返回================*//*** 手機號碼*/@JsonView(SensitiveInfoView.class)private String mobileNo;/*** 登錄密碼*/@JsonView(SensitiveInfoView.class)private String loginPwd;/*** 支付密碼*/@JsonView(SensitiveInfoView.class)private String payPwd;
}
2.2.2 區分不同的響應視圖
/*** @Description 簡單視圖展示View信息* @Author Mr.Gao*/
public interface SimpleInfoView {
}------ 視圖與視圖之間是可以繼承的,那么也就實現既包含基礎信息字段又包含需要的字段,進而實現不同DTO的返回 ---
/*** @Description 敏感信息* @Author Mr.Gao*/
public interface SensitiveInfoView extends SimpleInfoView {
}
2.2.3 Controller 層的調用
/*** @Description 用戶控制層* @Author Mr.Gao* @Date 2025/4/16 23:50*/
@RestController
public class UserController {/*** 模擬從數據庫獲取用戶信息** @return*/public static User getUserInfoFromDB() {User user = new User();user.setUsername("Mr.Gao");user.setAge(18);user.setSex("男");user.setMobileNo("12345678901");user.setLoginPwd("123456");user.setPayPwd("123456");return user;}/*** 獲取用戶簡要信息** @return*/@JsonView(SimpleInfoView.class) // 返回簡要信息的視圖DTO@GetMapping("/user/getUserSimpleInfo")public User getUserSimpleInfo() {return getUserInfoFromDB();}/*** 獲取用戶詳細信息** @return*/@JsonView(SensitiveInfoView.class)// 返回詳情信息的視圖DTO@GetMapping("/user/getUserDetailInfo")public User getUserDetailInfo() {return getUserInfoFromDB();}}
2.2.4 簡要信息 視圖 DTO
2.2.5 詳情信息 視圖 DTO
2.3 問題已解決(引入原理實現篇)
經過上述代碼案例操作,確實是可以解決我們后端程序猿的新建多個 DTO 對象的冗余問題,童鞋們可以都試試,但是出于好奇,為了什么在 一個實體對象 DTO 和 controller 層的方法 增加 @JsonView 注解之后就能實現不同視圖的效果呢?其中究竟是使用了什么魔法呢?接下來我們繼續進入 Debug 調試篇,繼續 gank 它。
3、Debug調試篇
3.1 SpringMvc 切入點 對應的核心代碼片段
3.2 字段屬性序列化核心邏輯
經過上述分析可得,設置視圖的核心代碼為 objectMapper.writerWithView(serializationView) ,然后調用 objectMapper.writeValueAsString 方法是否可以實現不同視圖的 DTO 輸出呢?
3.3 多種 DTO 視圖 Demo 驗證
3.3.1 ObjectMapper 配置視圖 View
@Test
public void testJsonViewAnnotationConvertMutiDTOByObjectMapper() throws JsonProcessingException {User user = UserController.getUserInfoFromDB();// @1: 設置序列化視圖為SimpleInfoView(輸出簡要信息)objectMapper.setConfig(objectMapper.getSerializationConfig().withView(SimpleInfoView.class));// @2: 設置序列化視圖為SensitiveInfoView(輸出詳細信息)//objectMapper.setConfig(objectMapper.getSerializationConfig()// .withView(SensitiveInfoView.class));System.out.println("采用Object序列化視圖:" + objectMapper.getSerializationConfig().getActiveView());String JsonResult = objectMapper.writeValueAsString(user);System.out.println(JsonResult);
}
3.3.2 ObjectWriter 配置視圖 View
@Test
public void testJsonViewAnnotationConvertMutiDTOByObjectWriter() throws JsonProcessingException {User user = UserController.getUserInfoFromDB();// @1: 設置序列化視圖為SimpleInfoView(輸出簡要信息)//ObjectWriter objectWriter = objectMapper.writerWithView(SimpleInfoView.class);// @2: 設置序列化視圖為SensitiveInfoView(輸出詳細信息)ObjectWriter objectWriter = objectMapper.writerWithView(SensitiveInfoView.class);System.out.println("ObjectWriter中的序列化視圖為: " + objectWriter.getConfig().getActiveView());String JsonResult = objectWriter.writeValueAsString(user);System.out.println(JsonResult);
}
3.4 小結
最后,我本地的項目 SpringBoot 版本是 2.6.13,而我的 Pom 文件依賴中僅僅引入了spring-boot-starter-web,我可以確定的是沒有引入任何 jackson 包的依賴的,而@JsonView 注解是 jackson 包下的注解,那么只有一種可能,那就是對應的 springboot 的 web 依賴集成了對應 jackson 相關 jar 包,出于好奇的我還是點開了 spring-boot-starter-web 依賴,結果發現確實是這樣。
4、擴展點
4.1 ResponseBodyAdvice接口
對響應的內容可以進行二次包裝處理,例如對響應參數內容統一進行簽名、加密等邏輯處理。
4.2 RequestBodyAdvice接口
用來對請求的內容進行請求參數重寫處理,例如對接收到請求參數統一進行驗簽、解密等邏輯處理。
綜上所述,相信我們已經掌握了 如何通過一個 DTO 對象來渲染不同需求場景下的 DTO 對象,不過存在唯一的缺點,經過 Debug 調試篇我們可以發現,只有在 序列化的時候才會過濾對應的字段,那么如果一個 DTO 對象的屬性太多,根據類的單一設計原則還是建議使用新建新的 DTO 對象來完成。所以我覺得可以視情況而定,想要代碼的邏輯清晰一些就新建 DTO 實體,想要減少的代碼的編碼量(即減少 DTO 實體對象)那么就用@JsonView注解實現。