本週須要完成一項工做:在單元測試中完成後端返回字段的斷言。
換句話說,須要斷言後端向前端返回了哪些字段。前端
因爲對JsonView的瞭解不足,在找字段的時候花費了較多時間。
所以本文將從實踐的角度,闡述JsonView的做用和用法。git
在說做用以前,咱們已經知道:先後端分離的項目中,使用Json字符串來完成先後端之間的通訊。
在默認狀況下,只要前端發起請求,就會返回對象的全部字段。但有的時候,後端不能把全部字段所有返回。github
一方面,是由於有些字段前端不須要,返回過多的數據會佔用網絡帶寬;另外一方面是出於安全性考慮,好比,不能夠將密碼返回給前端,不然,網站攻擊者能夠用REST工具直接獲取密碼。segmentfault
而JsonView的做用,就是用來控制C層返回哪些字段的。後端
經過如下幾個實例的對比,來展現JsonView的效果。安全
如下的代碼,咱們經過一個小Demo來演示:網絡
在這個小小的教務系統中,有三種實體——教師、學生、班級,app
爲了清晰的展現實體關係,提供簡單的E-R圖:
前後端分離
由圖可知:
班級和教師是多對一的關係,
學生和班級是多對一的關係
所以,若是查詢學生,班級會包含在學生的字段中,教師會包含在班級的字段中。
這是典型的「對象套對象套對象」的例子。工具
下面實體的代碼供參考(可略過):
/** * 班級實體 * 包含字段 id、 name、 * 外鍵 teacher */ @Entity public class Klass { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; @ManyToOne private Teacher teacher; private String name; }
/** * 學生實體 * 包含字段 id、 name、 sno * 外鍵 klass */ @Entity public class Student { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; @Column(nullable = false) private String name; @Column(nullable = false, unique = true) private String sno; @ManyToOne @JoinColumn(nullable = false) private Klass klass;
/** * 教師實體 * 包含字段 id、 name、 sex、 username、 email */ @Entity public class Teacher { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; private String name; private Boolean sex; private String username; private String email;
在不使用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時,返回全部字段,包括外鍵關聯對象的全部字段
在原來的基礎上,對姓名和學號字段分別使用JsonView,同時,定義對應的接口:
public class Student { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; // 姓名字段使用JsonView @Column(nullable = false) @JsonView(NameJsonView.class) private String name; // 學號字段使用JsonView @Column(nullable = false, unique = true) @JsonView(SnoJsonView.class) private String sno; // 班級外鍵使用JsonView @ManyToOne @JsonView(KlassJsonView.class) @JoinColumn(nullable = false) private Klass klass; ... // 姓名字段的接口 public interface NameJsonView {} // 學號字段的接口 public interface SnoJsonView {} // 班級外鍵對應的實體的接口 public interface KlassJsonView {} }
在C層控制器中加入NameJsowView:
(注意:此時只有姓名,並無加入學號和班級)
/** * 經過ID查詢學生 * @param id 學生ID * @return 學生 */ @GetMapping("{id}") @JsonView(Student.NameJsonView.class) public Student getById(@PathVariable Long id) { return this.studentService.findById(id); }
返回結果以下:
{ "name":"學生1" }
因爲咱們在C層只使用了姓名的字段,除了姓名,其餘字段均不返回。
所以能夠得出結論二:
對於已經定義JsowView的對象,C層只返回註解中的JsonView接口裏麪包含的字段,其餘字段一律不返回。
前一節的基礎上,把C層的註解由 @JsonView(Student.NameJsonView.class)
改成
@JsonView(Student.KlassJsonView.class)
來返回學生對象關聯的班級。
/** * 經過ID查詢學生 * @param id 學生ID * @return 學生 */ @GetMapping("{id}") @JsonView(Student.KlassJsonView.class) public Student getById(@PathVariable Long id) { return this.studentService.findById(id); }
此次的返回結果比較有意思,只返回了空的Klass,裏面一個字段也沒有:
{ "klass": { } }
因此,結論三:
經過外鍵關聯的對象,使用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的字段,只須要在接口中繼續繼承便可。
接下來講具體如何在Spring的項目中應用JsonView。
須要記住接口名稱
// 定義了一個接口,用於JsonView控制返回字段 public interface SnoJsonView {}
找到一個字段,加入@JsonView(XXXJsonView.class),名稱與剛纔寫的接口名稱相同。
@JsonView(SnoJsonView.class) private String sno;
實際的項目中,不可能只返回一個字段,若是返回多個字段,那就在C層再定一個接口,繼承因此要返回字段的接口便可。
原則上,每一個控制器方法,都必須有惟一的JsonView接口,接口名與方法名相同,不能混用。
定義一個與C層方法名相同的接口,繼承業務邏輯中須要返回的全部字段:
public interface GetByIdJsonView extends Student.KlassJsonView, Student.NameJsonView, Student.SnoJsonView {}
最後一步,就是把剛纔的接口,加到要控制字段的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接口能夠經過繼承,來實現返回不一樣字段的組合。
本文做者: 河北工業大學夢雲智開發團隊 - 劉宇軒