乐趣区

关于json:从实践中真正理解JsonView

零、问题的产生

本周须要实现一项工作:在单元测试中实现后端返回字段的 断言
换句话说,须要断言后端向前端返回了哪些字段。

因为对 JsonView 的理解有余,在找字段的时候破费了较多工夫。
因而本文将从实际的角度,论述 JsonView 的作用和用法。

一、JsonView 的作用

在说作用之前,咱们曾经晓得:前后端拆散的我的项目中,应用 Json 字符串 来实现前后端之间的通信。
在默认状况下,只有前端发动申请,就会返回对象的 所有 字段。但有的时候,后端不能把所有字段全副返回。

一方面,是因为有些字段前端不须要,返回过多的数据会占用网络带宽;另一方面是出于 安全性 思考,比方,不能够将明码返回给前端,否则,网站攻击者能够用 REST 工具间接获取明码。

而 JsonView 的作用,就是用来 管制 C 层返回哪些字段 的。

二、JsonView 的成果

通过以下几个实例的比照,来展现 JsonView 的成果。

以下的代码,咱们通过一个小 Demo 来演示:

在这个小小的教务零碎中,有三种实体——老师、学生、班级

为了清晰的展现实体关系,提供简略的 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;

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,同时,定义对应的接口:

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 接口外面蕴含的字段,其余字段一律不返回。

3、在实体的关联对象中应用 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 只会返回关联空对象自身,而不返回关联对象的任何字段。

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 接口能够通过 继承,来实现返回不同字段的组合。

版权申明

本文作者:河北工业大学梦云智开发团队 – 刘宇轩

退出移动版