JOSNVIEW更佳实践
在使用SpringMVC进行开发时,使用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
来进行修饰。 - 为了防止在json输出时造成的死循环,凡事涉及到关联的,单独定义
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 班级
两个实体举例,关系如下:
- 学生:班级 = n:1
学生
@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 }
班级:
@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 }
我们在上述代码中,主要做了两件事:
- 在内部定义了JsonView.
- 为关联字段单独定义了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 { // 这是关键!继承了两个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输出时的死循环问题。当前来看,不失为一种更佳的实践。
相关推荐
yuzhu 2020-11-16
somebodyoneday 2020-01-29
qianqianxiao 2019-07-01
cfh00 2019-06-30
cfh00 2019-06-28
xusong 2011-04-26
千古悠悠何所愁 2010-01-06
SlaughterDevil 2017-08-23
ghjcduhvfyjhbf 2018-05-26
mdjxy 2007-03-09