在使用SrpingMVC进行开发时,如何使用JSONVIEW更好的控制字段的输出虽然不难。但总感觉找不到一种相对使用简单、理解简单的方法。本文在历史项目的实践基础上,尝试找到了一种更佳的实践方法。
项目源码地址: https://github.com/mengyunzhi/springBootSampleCode/tree/master/jsonview
我们当前遇到的最大的问题,就是在 实体中
使用了大量的外部 JSONVEIW
。
例:我们输出 Student
实体时,需要进行以下两步操作:
class StudentController { public Student getById(Long id) { }
JsonView
类或是接口,比如 class StudentJsonView { public interface GetById{} }
@JsonView
注解,并将刚刚定义的 StudentJsonView.GetById.class
加入其中。比如: @JsonView(StudentJsonView.GetById.class)
Stduent
实体,并将需要输出的字段,加入 @JsonView(StudentJsonView.GetById.class)
注解。 存在问题也很明显:
Student
实体的同一字段上,我们使用了大量的 JsonView
,后期我们进行维护时,只能增加新的,不敢删除老的(因为我们不知道谁会用这个JsonView)。不利于维护。 对修改关闭
的原则。比如:A是负责实体类的,B是负责触发器的。那么B在进行触发器的开发时,需要修改A负责的实体类。这并不是我们想要的。
既然实体并不想并修改,哪怕是添加 JsonView
这样并不影响实体结构的操作。那么实体就要对扩展开放。使其它调用者,可以顺利的定义输出字段。
那么,我们尝试做如下修改:
JsonView
的定义移至实体类中,并在实体类中,使用实体内部定义的 JsonView
来进行修饰。 JsonView
JsonView
继承关联方实体内部的 JsonView
pom
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.1.2.RELEASE</version> <relativePath/> <!-- lookup parent from repository --> </parent> <groupId>com.mengyunzhi.springBootSampleCode</groupId> <artifactId>jsonview</artifactId> <version>0.0.1-SNAPSHOT</version> <name>jsonview</name> <description>Demo project for Spring Boot</description> <properties> <java.version>1.8</java.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>com.h2database</groupId> <artifactId>h2</artifactId> <scope>runtime</scope> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> <version>1.2.54</version> <scope>test</scope> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> <repositories> <repository> <id>alimaven</id> <name>aliyun maven</name> <url>http://maven.aliyun.com/nexus/content/groups/public/</url> <releases> <enabled>true</enabled> </releases> <snapshots> <enabled>false</enabled> </snapshots> </repository> </repositories> </project>
实体依然采用我们熟悉的: Student学生
, Klass 班级
两个实体举例,关系如下:
学生
@Entity public class Student { public Student() { } public Student(String name) { this.name = name; } /** * 定义JSON输出 */ static abstract class Json { interface base {} // 基本字段 interface klass extends Klass.Json.base {} // 对应klass字段 } @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @JsonView(Json.base.class) private Long id; @JsonView(Json.base.class) private String name; @JsonView(Json.klass.class) @ManyToOne private Klass klass; // 省略set与get }
班级:
@Entity public class Klass { public Klass() { } public Klass(String name) { this.name = name; } /** * 定义JSON输出 */ static abstract class Json { interface base {} // 基本字段 interface students extends Student.Json.base {}// 对应students字段 } @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; @JsonView(Json.base.class) private String name; @JsonView(Json.students.class) @OneToMany(mappedBy = "klass") private List<Student> students = new ArrayList<>(); // 省略set与get }
我们在上述代码中,主要做了两件事:1. 在内部定义了JsonView. 2. 为关联字段单独定义了JsonView,并做了相应的继承,以使其显示关联实体的基本字段信息。
班级
package com.mengyunzhi.springBootSampleCode.jsonview; import com.fasterxml.jackson.annotation.JsonView; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @RestController @RequestMapping("klass") public class KlassController { static abstract class Json { interface getById extends Klass.Json.base, Klass.Json.students {} } @Autowired private KlassRepository klassRepository; @GetMapping("{id}") @JsonView(Json.getById.class) public Klass getById(@PathVariable Long id) { return klassRepository.findById(id).get(); } }
学生
package com.mengyunzhi.springBootSampleCode.jsonview; import com.fasterxml.jackson.annotation.JsonView; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @RestController @RequestMapping("student") public class StudentController { static abstract class Json { interface getById extends Student.Json.base, Student.Json.klass {} } @Autowired private StudentRepository studentRepository; @GetMapping("{id}") @JsonView(Json.getById.class) public Student getById(@PathVariable Long id) { return studentRepository.findById(id).get(); } }
如代码所示,我们进行输出时,并没有对实体进行任何的操作,却仍然达到了个性化输出字段的目的。
班级:
package com.mengyunzhi.springBootSampleCode.jsonview; import com.alibaba.fastjson.JSON; import org.assertj.core.api.Assertions; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.http.MediaType; import org.springframework.test.context.junit4.SpringRunner; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; @AutoConfigureMockMvc @RunWith(SpringRunner.class) @SpringBootTest public class KlassControllerTest { @Autowired private KlassRepository klassRepository; @Autowired private StudentRepository studentRepository; @Autowired private MockMvc mockMvc; @Test public void getById() throws Exception { // 数据准备 Klass klass = new Klass("测试班级"); klassRepository.save(klass); Student student = new Student("测试学生"); student.setKlass(klass); studentRepository.save(student); klass.getStudents().add(student); klassRepository.save(klass); // 模拟请求,将结果转化为字符化 String result = this.mockMvc.perform( MockMvcRequestBuilders.get("/klass/" + klass.getId().toString()) .contentType(MediaType.APPLICATION_JSON_UTF8)) .andReturn().getResponse().getContentAsString(); // 将字符串转换为实体,并断言 Klass resultKlass = JSON.parseObject(result, Klass.class); Assertions.assertThat(resultKlass.getName()).isEqualTo("测试班级"); Assertions.assertThat(resultKlass.getStudents().size()).isEqualTo(1); Assertions.assertThat(resultKlass.getStudents().get(0).getName()).isEqualTo("测试学生"); } }
学生:
package com.mengyunzhi.springBootSampleCode.jsonview; import com.alibaba.fastjson.JSON; import org.assertj.core.api.Assertions; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.http.MediaType; import org.springframework.test.context.junit4.SpringRunner; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; @AutoConfigureMockMvc @RunWith(SpringRunner.class) @SpringBootTest public class StudentControllerTest { @Autowired private KlassRepository klassRepository; @Autowired private StudentRepository studentRepository; @Autowired private MockMvc mockMvc; @Test public void getById() throws Exception { // 数据准备 Klass klass = new Klass("测试班级"); klassRepository.save(klass); Student student = new Student("测试学生"); student.setKlass(klass); studentRepository.save(student); // 模拟请求,将结果转化为字符化 String result = this.mockMvc.perform( MockMvcRequestBuilders.get("/student/" + student.getId().toString()) .contentType(MediaType.APPLICATION_JSON_UTF8)) .andReturn().getResponse().getContentAsString(); // 将字符串转换为实体,并断言 Student resultStudent = JSON.parseObject(result, Student.class); Assertions.assertThat(resultStudent.getName()).isEqualTo("测试学生"); Assertions.assertThat(resultStudent.getKlass().getName()).isEqualTo("测试班级"); } }
我们将 JosnView
定义到相关的实体中,并使其与特定的字段进行关联。在进行输出时,采用继承的方法,来自定义输出字段。即达到了“对扩展开放,对修改关闭”的目标,也有效的防止了JSON输出时的死循环问题。当前来看,不失为一种更佳的实践。
骐骥一跃,不能十步;驽马十驾,功在不舍。