在使用SpringMVC进行开发时,使用JSONVIEW控制字段输出虽然不难。但总感受应该有一种相对使用简单、理解简单的方法。本文在历史项目实践基础上,尝试找出一种更佳的实践方法。java
项目源码地址: https://github.com/mengyunzhi/springBootSampleCode/tree/master/jsonview
咱们当前遇到的最大的问题是在实体中
使用了大量的外部JSONVEIW
。
例:咱们输出Student
实体时,须要进行如下两步操做:git
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)
注解。存在问题也很明显:github
Student
实体的同一字段上,咱们使用了大量的JsonView
,后期咱们进行维护时,只能增长新的,不敢删除老的(由于咱们不知道谁会用这个JsonView)。不利于维护。对修改关闭
的原则。好比:A是负责实体类的,B是负责触发器的。那么B在进行触发器开发时,须要修改A负责的实体类。而这并非咱们想要的。既然实体并不想并修改(哪怕是添加JsonView
这样并不影响实体结构的操做),那么实体就要对扩展开放,以使其它调用者能够顺利的定义输出字段。web
咱们尝试作以下修改:spring
JsonView
的定义移至实体类中,并在实体类中,使用实体内部定义的JsonView
来进行修饰。JsonView
JsonView
继承关联方实体内部的JsonView
pomapache
<?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 班级
两个实体举例,关系以下:json
学生api
@Entity public class Student { public Student() { } public Student(String name) { this.name = name; } interface base { } // 基本字段 interface klass extends Klass.base { } // 对应klass字段 @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @JsonView(base.class) private Long id; @JsonView(base.class) private String name; @JsonView(klass.class) @ManyToOne private Klass klass; // 省略set与get }
班级:app
@Entity public class Klass { public Klass() { } public Klass(String name) { this.name = name; } interface base { } // 基本字段 interface students extends Student.base { }// 对应students字段 @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; @JsonView(base.class) private String name; @JsonView(students.class) @OneToMany(mappedBy = "klass") private List<Student> students = new ArrayList<>(); // 省略set与get }
咱们在上述代码中,主要作了两件事:maven
班级
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 { // 这是关键!继承了两个interface,即显示这两个interface对应的字段。 interface getById extends Klass.base, Klass.students { } @Autowired private KlassRepository klassRepository; @GetMapping("{id}") @JsonView(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 { // 这是关键!继承了两个interface,即显示这两个interface对应的字段。 interface getById extends Student.base, Student.klass { } @Autowired private StudentRepository studentRepository; @GetMapping("{id}") @JsonView(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("测试班级"); } }
咱们将JsonView
定义到相关的实体中,并使其与特定的字段进行关联。在进行输出时,采用继承的方法,来自定义输出字段。即达到了“对扩展开放,对修改关闭”的目标,也有效的防止了JSON输出时的死循环问题。当前来看,不失为一种更佳的实践。
骐骥一跃,不能十步;驽马十驾,功在不舍。