太強了!Spring Boot 3.4 的 @JsonView 注解,讓接口返回靈活到飛起!
在實際開發中,前后端分離的系統通常會用 JSON 來傳遞數據。但不同行為場景下,前端所需的數據范圍是不同的。舉個例子,產品列表只需要展示商品的基本信息,如商品名、價格等;而商品詳情頁就得展示完整信息,比如庫存、供應商、采購價等等。
傳統做法是定義多個 DTO 分別應對不同的展示需求,但這樣做容易產生大量重復代碼,維護起來也會相當頭疼。
幸好,Spring Boot 提供了對 Jackson 的 @JsonView 注解的支持,它就像一個靈活的“數據可見性開關”——可以幫我們按需返回不同的數據字段,既保持了數據結構統一,又能減輕維護負擔。
原理淺析
@JsonView 來源于 Jackson,它的工作方式非常巧妙:
- 定義幾個“視圖接口”(通常是空接口)。
- 在實體類的字段上用 @JsonView 標明字段在哪些視圖中可見。
- 控制器里用 @JsonView(View.class) 指定序列化用哪個視圖。
這樣就可以根據場景有選擇性地返回數據字段。
實戰演練:用 @JsonView 玩轉返回字段
我們以一個商品 Product 實體作為例子進行講解。
定義實體類
package com.icoderoad.model;
import com.fasterxml.jackson.annotation.JsonView;
public class Product {
// 定義視圖接口
public interface BasicInfo {}
public interface DetailedInfo extends BasicInfo {}
@JsonView(BasicInfo.class)
private Long id;
@JsonView(BasicInfo.class)
private String name;
@JsonView(BasicInfo.class)
private Double price;
@JsonView(DetailedInfo.class)
private Integer stock;
@JsonView(DetailedInfo.class)
private String supplier;
@JsonView(BasicInfo.class)
private String category;
// 構造函數
public Product(Long id, String name, Double price, Integer stock, String supplier, String category) {
this.id = id;
this.name = name;
this.price = price;
this.stock = stock;
this.supplier = supplier;
this.category = category;
}
// getter & setter 省略
}
控制器用法示例
package com.icoderoad.controller;
import com.fasterxml.jackson.annotation.JsonView;
import com.icoderoad.model.Product;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/products")
public class ProductController {
// 只返回基礎信息
@JsonView(Product.BasicInfo.class)
@GetMapping("/basic")
public Product getBasicProduct() {
return new Product(1L, "iPhone 15", 8999.0, 200, "Apple", "Electronics");
}
// 返回所有字段
@JsonView(Product.DetailedInfo.class)
@GetMapping("/detail")
public Product getDetailedProduct() {
return new Product(1L, "iPhone 15", 8999.0, 200, "Apple", "Electronics");
}
}
不同接口就像不同“窗口”,只展示自己該展示的數據。
編程式控制返回字段
如果你希望動態指定視圖而不是用注解,也可以用 MappingJacksonValue:
@GetMapping("/dynamic")
public MappingJacksonValue dynamicView() {
Product product = new Product(1L, "iPhone 15", 8999.0, 200, "Apple", "Electronics");
MappingJacksonValue wrapper = new MappingJacksonValue(product);
wrapper.setSerializationView(Product.BasicInfo.class);
return wrapper;
}
配合 Thymeleaf 或 MVC 頁面展示
你也可以在 MVC 頁面中返回 JSON 數據,并用視圖控制字段:
@Controller
public class ProductViewController {
@GetMapping("/product/view")
public ModelAndView viewProduct(Model model) {
Product product = new Product(1L, "iPhone 15", 8999.0, 200, "Apple", "Electronics");
model.addAttribute("product", product);
model.addAttribute(JsonView.class.getName(), Product.BasicInfo.class);
return new ModelAndView(new MappingJackson2JsonView());
}
}
這里的關鍵點是:把 JsonView 的類全限定名作為 key 放入 model 中,然后使用 Jackson 的 JSON View 進行渲染。
控制反序列化(限制字段反解析)
@JsonView 不僅可以控制輸出,也可以限制 JSON 字段的反序列化行為:
@PostMapping("/deserialize")
publicProductdeserialize(@RequestBodyString json)throwsException{
ObjectMapper mapper =newObjectMapper();
return mapper
.readerWithView(Product.BasicInfo.class)
.forType(Product.class)
.readValue(json);
}
像上面的代碼,如果 JSON 中包含 supplier 或 stock,也不會被反序列化出來。
設置默認行為(全量輸出未注解字段)
默認情況下,未打 @JsonView 注解的字段不會被返回。
不過,我們可以通過如下配置調整:
spring:
jackson:
mapper:
default-view-inclusion: true
如果你加上這個配置,就算某個字段沒有標注 @JsonView,也會被默認輸出(前提是你仍然在接口上標了某個視圖)。
總結
@JsonView 是一個非常強大而靈活的工具,特別適合處理字段輸出差異化需求:
- 不需要定義多個冗余 DTO;
- 數據結構統一,維護性高;
- 支持注解和編程式兩種方式;
- 輸出、反序列化都可以控制;
- 配合 MVC 視圖同樣適用!
在 Spring Boot 3.4 的加持下,搭配 Jackson 的 @JsonView,你可以優雅地應對各種接口返回需求,真正實現“靈活到飛起”的數據控制!