在使用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輸出時的死循環問題。當前來看,不失爲一種更佳的實踐。
騏驥一躍,不能十步;駑馬十駕,功在不捨。