Spring Boot+原生注解@JsonView 輕松過濾字段,真的優雅!
前后端分離的項目中,使用Json字符串來完成前后端之間的通信。在默認情況下,只要前端發起請求,就會返回對象的所有字段。但有的時候,后端不能把所有字段全部返回。
一方面,是因為有些字段前端不需要,返回過多的數據會占用網絡帶寬;
另一方面是出于安全性考慮,比如,不可以將密碼返回給前端,否則,網站攻擊者可以用REST工具直接獲取密碼。
而JsonView的作用,就是用來控制C層返回哪些字段的。
@JsonView 它是 Jackson 庫中的一個強大注解。通過定義不同的視圖類(通常為接口或類),并在實體類的字段上使用 @JsonView 注解標記該字段在哪個視圖中可見,同時在控制器方法中通過 @JsonView 注解指定返回的視圖,就可以靈活控制返回的字段內容,實現了根據不同場景動態選擇序列化字段的目的,大大提高了代碼的簡潔性和可維護性。
JsonView的效果
通過以下幾個實例的對比,來展示JsonView的效果。
以下的代碼,我們通過一個小Demo來演示:
在這個小小的教務系統中,有三種實體——教師、學生、班級,
為了清晰的展示實體關系,提供簡單的E-R圖:
圖片
由圖可知: 班級和教師是多對一的關系, 學生和班級是多對一的關系 因此,如果查詢學生,班級會包含在學生的字段中,教師會包含在班級的字段中。 這是典型的“對象套對象套對象”的例子。
下面實體的代碼供參考(可略過):
圖片
1、不使用JsonView
在不使用JsonView的情況下,使用REST工具直接取出一個學生,返回了學生的所有字段,也包含關聯查詢得到的對象:
{
"id":1,
"name":"學生1",
"sno":"123456",
"klass":{
"id":1,
"teacher":{
"id":1,
"name":"張三",
"sex":false,
"username":"zhangsan",
"email":"123@123.com"
},
"name":"班級1"
}
}
因此,很容易得到結論一:
不使用JsonView時,返回所有字段,包括外鍵關聯對象的所有字段
2、在實體的字段上使用JsonView
在原來的基礎上,對姓名和學號字段分別使用JsonView,同時,定義對應的接口:
圖片
在C層控制器中加入NameJsowView: (注意:此時只有姓名,并沒有加入學號和班級)
圖片
返回結果如下:
{
"name":"學生1"
}
由于我們在C層只使用了姓名的字段,除了姓名,其他字段均不返回。關注公眾號:碼猿技術專欄,回復關鍵詞:1111 獲取阿里內部Java性能調優手冊!
因此可以得出結論二:
對于已經定義JsowView的對象,C層只返回注解中的JsonView接口里面包含的字段,其他字段一概不返回。
3、在實體的關聯對象中使用JsonView
前一節的基礎上,把C層的注解由 **@JsonView(Student.NameJsonView.class)改為@JsonView(Student.KlassJsonView.class)**來返回學生對象關聯的班級。
圖片
這次的返回結果比較有意思,只返回了空的Klass,里面一個字段也沒有:
{
"klass":
{
}
}
所以,結論三:通過外鍵關聯的對象,使用JsonView只會返回關聯空對象本身,而不返回關聯對象的任何字段。
4、在關聯對象的字段中使用JsonView
為了解決結論三的問題,我們需要像上文一樣,在學生關聯的班級實體中也啟用JsonView。
然后新建一個接口,分別繼承班級字段以及班級實體中的姓名、教師等其他字段:
public interface GetByIdJsonView extends Student.KlassJsonView, Klass.NameJsonView, Klass.TeacherJsonView {}
把這個新接口寫到C層方法的注解上:
@GetMapping("{id}")
@JsonView(GetByIdJsonView.class)
public Student getById(@PathVariable Long id) {
return this.studentService.findById(id);
}
再次運行,查看返回結果:
{
"klass":
{
"teacher":{ },
"name":"班級1"
}
}
符合預期,因此,結論四:
如果想返回關聯對象中的字段,只需要繼承這個實體中,相關字段的JsonView接口即可。
細心的你可以發現,Teacher中依然沒有字段,如果也想返回Teacher的字段,只需要在接口中繼續繼承即可。
JsonView的使用方法
接下來說具體如何在Spring的項目中應用JsonView。
1、在實體類中定義接口
需要記住接口名稱
// 定義了一個接口,用于JsonView控制返回字段
public interface SnoJsonView {}
2、在實體中的字段中加入注解
找到一個字段,加入**@JsonView(XXXJsonView.class)**,名稱與剛才寫的接口名稱相同。
@JsonView(SnoJsonView.class)
private String sno;
3、繼承接口
實際的項目中,不可能只返回一個字段,如果返回多個字段,那就在C層再定一個接口,繼承所以要返回字段的接口即可。 原則上,每個控制器方法,都必須有唯一的JsonView接口,接口名與方法名相同,不能混用。
定義一個與C層方法名相同的接口,繼承業務邏輯中需要返回的所有字段:
public interface GetByIdJsonView extends Student.KlassJsonView, Student.NameJsonView, Student.SnoJsonView {}
4、在C層方法上加入注解
最后一步,就是把剛才的接口,加到要控制字段的C層方法上:
@GetMapping("{id}")
@JsonView(GetByIdJsonView.class)
public Student getById(@PathVariable Long id) {
return this.studentService.findById(id);
}
到此,就可以實現用JsonView控制返回字段了。 這種做法的優點在于: 實體層中,接口名和字段名一致,到C層引用時,就可以根據名稱知道這個接口控制哪個字段; 控制器中,接口名與方法名一致,通過接口名可以知道是這個方法返回哪些字段。
總結
前后端分離的項目中,使用Json字符串來完成前后端之間的通信,但有的時候,后端不能把所有字段全部返回,因此可以使用JsonView,來控制C層返回哪些字段。
如果不使用JsonView,默認返回所有字段,包括外鍵關聯對象的所有信息; 如果使用JsonView,只返回接口中聲明的所有字段,如果出現關聯對象,只返回關聯對象本身,而不返回其中的字段。 JsonView接口可以通過繼承,來實現返回不同字段的組合。