從實踐中理解JsonView的做用和用法

零、問題的產生

本週須要完成一項工做:在單元測試中完成後端返回字段的斷言
換句話說,須要斷言後端向前端返回了哪些字段。前端

因爲對JsonView的瞭解不足,在找字段的時候花費了較多時間。
所以本文將從實踐的角度,闡述JsonView的做用和用法。git

1、JsonView的做用

在說做用以前,咱們已經知道:先後端分離的項目中,使用Json字符串來完成先後端之間的通訊。
在默認狀況下,只要前端發起請求,就會返回對象的全部字段。但有的時候,後端不能把全部字段所有返回。github

一方面,是由於有些字段前端不須要,返回過多的數據會佔用網絡帶寬;另外一方面是出於安全性考慮,好比,不能夠將密碼返回給前端,不然,網站攻擊者能夠用REST工具直接獲取密碼。segmentfault

而JsonView的做用,就是用來控制C層返回哪些字段的。後端

2、JsonView的效果

經過如下幾個實例的對比,來展現JsonView的效果。安全

如下的代碼,咱們經過一個小Demo來演示:網絡

在這個小小的教務系統中,有三種實體——教師、學生、班級app

爲了清晰的展現實體關係,提供簡單的E-R圖:
image.png前後端分離

由圖可知:
班級和教師是多對一的關係,
學生和班級是多對一的關係
所以,若是查詢學生,班級會包含在學生的字段中,教師會包含在班級的字段中。
這是典型的「對象套對象套對象」的例子。工具

下面實體的代碼供參考(可略過):

/**
 * 班級實體
 * 包含字段 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

在不使用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

在原來的基礎上,對姓名和學號字段分別使用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接口裏麪包含的字段,其餘字段一律不返回。

三、在實體的關聯對象中使用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

爲了解決結論三的問題,咱們須要像上文同樣,在學生關聯的班級實體中也啓用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的字段,只須要在接口中繼續繼承便可。

3、JsonView的使用方法

接下來講具體如何在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層方法上加入註解

最後一步,就是把剛纔的接口,加到要控制字段的C層方法上:

@GetMapping("{id}")
    @JsonView(GetByIdJsonView.class)
    public Student getById(@PathVariable Long id) {
        return this.studentService.findById(id);
    }

到此,就能夠實現用JsonView控制返回字段了。
這種作法的優勢在於:
實體層中,接口名字段名一致,到C層引用時,就能夠根據名稱知道這個接口控制哪一個字段;
控制器中,接口名方法名一致,經過接口名能夠知道是這個方法返回哪些字段。

4、總結

先後端分離的項目中,使用Json字符串來完成先後端之間的通訊,但有的時候,後端不能把全部字段所有返回,所以可使用JsonView,來控制C層返回哪些字段

若是不使用JsonView,默認返回全部字段,包括外鍵關聯對象的全部信息;
若是使用JsonView,只返回接口中聲明的全部字段,若是出現關聯對象,只返回關聯對象自己,而不返回其中的字段。
JsonView接口能夠經過繼承,來實現返回不一樣字段的組合。

版權聲明

本文做者: 河北工業大學夢雲智開發團隊 - 劉宇軒
相關文章
相關標籤/搜索