这个博文可以分为两部分:第一部分我将编写一个Spring Boot RESTful API,第二部分将介绍如何使用JSONDoc来记录创建的API。做这两个部分最多需要15分钟,因为使用Spring Boot创建一个API非常简单快捷,并且使用JSONDoc Spring Boot启动器和UI webjar进行记录也是如此。我将跳过这个例子的测试创建,因为主要目标是如何记录API而不是编写和测试它。
我们首先根据快速入门的原型创建Maven项目
并声明API所需的依赖关系:
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">
4.0.0
org.example
jsondoc-shelf
0.0.1-SNAPSHOT
jar
jsondoc-shelf
http://maven.apache.org
UTF-8
org.springframework.boot
spring-boot-starter-web
1.2.0.RELEASE
org.springframework.boot
spring-boot-starter-data-jpa
1.2.0.RELEASE
com.h2database
h2
1.3.176
org.projectlombok
lombok
1.14.8
junit
junit
4.11
test
这个应用程序将是一个管理简单货架的服务的集合。 将有两个实体:
为此,我将创建通常的组件来管理持久层和控制器层:
model
将包含Book
和Author
repository
将包含BookRepository
和AuthorRepository
controller
将包含BookController
和AuthorController
DatabasePopulator
类,实现 CommandLineRunner
,以便在启动时将在内存数据库中存在一些数据。我们来看看实体,存储库和控制器的代码:
package org.example.shelf.model;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import lombok.Data;
import lombok.EqualsAndHashCode;
@Entity
@Data
@EqualsAndHashCode(exclude = "id")
public class Book {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
@Column(name = "title")
private String title;
@ManyToOne
@JoinColumn(name = "author_id")
private Author author;
}
package org.example.shelf.model;
import java.util.ArrayList;
import java.util.List;
import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.OneToMany;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
import lombok.ToString;
import com.fasterxml.jackson.annotation.JsonIgnore;
@Entity
@Data
@NoArgsConstructor
@ToString(exclude = "books")
@EqualsAndHashCode(of = "name")
public class Author {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
@Column(name = "name")
private String name;
@JsonIgnore
@OneToMany(mappedBy = "author", fetch = FetchType.LAZY, cascade = CascadeType.ALL)
private List<Book> books = new ArrayList<Book>();
}
package org.example.shelf.repository;
import org.example.shelf.model.Book;
import org.springframework.data.jpa.repository.JpaRepository;
public interface BookRepository extends JpaRepository<Book, Long> {
}
package org.example.shelf.repository;
import org.example.shelf.model.Author;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
public interface AuthorRepository extends JpaRepository<Author, Long> {
}
package org.example.shelf.controller;
import java.util.List;
import org.example.shelf.flow.ShelfFlowConstants;
import org.example.shelf.model.Book;
import org.example.shelf.repository.BookRepository;
import org.jsondoc.core.annotation.Api;
import org.jsondoc.core.annotation.ApiBodyObject;
import org.jsondoc.core.annotation.ApiMethod;
import org.jsondoc.core.annotation.ApiPathParam;
import org.jsondoc.core.annotation.ApiResponseObject;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.util.UriComponentsBuilder;
@RestController
@RequestMapping(value = "/books", produces = MediaType.APPLICATION_JSON_VALUE)
public class BookController {
@Autowired
private BookRepository bookRepository;
@RequestMapping(value = "/{id}", method = RequestMethod.GET)
public Book findOne(@PathVariable Long id) {
return bookRepository.findOne(id);
}
@RequestMapping(method = RequestMethod.GET)
public List<Book> findAll() {
return bookRepository.findAll();
}
@RequestMapping(method = RequestMethod.POST, consumes = MediaType.APPLICATION_JSON_VALUE)
@ResponseStatus(value = HttpStatus.CREATED)
public ResponseEntity<Void> save(@RequestBody Book book, UriComponentsBuilder uriComponentsBuilder) {
bookRepository.save(book);
HttpHeaders headers = new HttpHeaders();
headers.setLocation(uriComponentsBuilder.path("/books/{id}").buildAndExpand(book.getId()).toUri());
return new ResponseEntity<Void>(headers, HttpStatus.CREATED);
}
@RequestMapping(value = "/{id}", method = RequestMethod.DELETE)
@ResponseStatus(value = HttpStatus.OK)
public void delete(@PathVariable Long id) {
Book book = bookRepository.findOne(id);
bookRepository.delete(book);
}
}
package org.example.shelf.controller;
import java.util.List;
import org.example.shelf.flow.ShelfFlowConstants;
import org.example.shelf.model.Author;
import org.example.shelf.repository.AuthorRepository;
import org.jsondoc.core.annotation.Api;
import org.jsondoc.core.annotation.ApiBodyObject;
import org.jsondoc.core.annotation.ApiMethod;
import org.jsondoc.core.annotation.ApiPathParam;
import org.jsondoc.core.annotation.ApiResponseObject;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.util.UriComponentsBuilder;
@RestController
@RequestMapping(value = "/authors", produces = MediaType.APPLICATION_JSON_VALUE)
public class AuthorController {
@Autowired
private AuthorRepository authorRepository;
@RequestMapping(value = "/{id}", method = RequestMethod.GET)
public Author findOne(@PathVariable Long id) {
return authorRepository.findOne(id);
}
@RequestMapping(method = RequestMethod.GET)
public List<Author> findAll() {
return authorRepository.findAll();
}
@RequestMapping(method = RequestMethod.POST, consumes = MediaType.APPLICATION_JSON_VALUE)
@ResponseStatus(value = HttpStatus.CREATED)
public ResponseEntity<Void> save(@RequestBody Author author, UriComponentsBuilder uriComponentsBuilder) {
authorRepository.save(author);
HttpHeaders headers = new HttpHeaders();
headers.setLocation(uriComponentsBuilder.path("/authors/{id}").buildAndExpand(author.getId()).toUri());
return new ResponseEntity<Void>(headers, HttpStatus.CREATED);
}
@RequestMapping(value = "/{id}", method = RequestMethod.DELETE)
@ResponseStatus(value = HttpStatus.OK)
public void delete(@PathVariable Long id) {
Author author = authorRepository.findOne(id);
authorRepository.delete(author);
}
}
package org.example.shelf;
import org.example.shelf.model.Author;
import org.example.shelf.model.Book;
import org.example.shelf.repository.AuthorRepository;
import org.example.shelf.repository.BookRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.context.annotation.Configuration;
@Configuration
public class DatabasePopulator implements CommandLineRunner {
@Autowired
private AuthorRepository authorRepository;
@Autowired
private BookRepository bookRepository;
public void run(String... arg0) throws Exception {
Author horbny = new Author();
horbny.setId(1L);
horbny.setName("Nick Horby");
Author smith = new Author();
smith.setId(2L);
smith.setName("Wilbur Smith");
authorRepository.save(horbny);
authorRepository.save(smith);
Book highFidelty = new Book();
highFidelty.setId(1L);
highFidelty.setTitle("High fidelty");
highFidelty.setAuthor(horbny);
Book aLongWayDown = new Book();
aLongWayDown.setId(2L);
aLongWayDown.setTitle("A long way down");
aLongWayDown.setAuthor(horbny);
Book desertGod = new Book();
desertGod.setId(3L);
desertGod.setTitle("Desert god");
desertGod.setAuthor(smith);
bookRepository.save(highFidelty);
bookRepository.save(aLongWayDown);
bookRepository.save(desertGod);
}
}
现在是编写主类来运行应用程序的时候了。 Shelf
在这种情况下,我会称之为Spring Boot,这很简单:
package org.example.shelf;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
@EnableAutoConfiguration
@EnableJpaRepositories
@ComponentScan
public class Shelf {
public static void main(String[] args) {
SpringApplication.run(Shelf.class, args);
}
}
通过运行这个类,我们可以实际验证应用程序是否响应请求。 您可以通过使用 curl 轻松测试 API 的工作:
curl -i http://localhost:8080/books/1
curl -i http://localhost:8080/books
curl -i http://localhost:8080/authors/1
curl -i http://localhost:8080/authors
这是有趣的和新的部分,即使用JSONDoc库来注释代码并自动生成其文档。要做到这一点,你必须声明JSONDoc依赖关系,并在你的类中插入一些代码。让我们看看如何做到这一点:
只需添加两个依赖关系到pom文件:
org.jsondoc
spring-boot-starter-jsondoc
1.1.3
org.jsondoc
jsondoc-ui-webjar
1.1.3
使用JSONDoc启动器,您可以通过添加@EnableJSONDoc
到Shelf
类中来启用文档生成,如下所示:
package org.example.shelf;
import org.jsondoc.spring.boot.starter.EnableJSONDoc;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
@EnableAutoConfiguration
@EnableJpaRepositories
@EnableJSONDoc
@ComponentScan
public class Shelf {
public static void main(String[] args) {
SpringApplication.run(Shelf.class, args);
}
}
接下来要做的是配置JSONDoc来扫描您的控制器,对象和流类。要做到这一点,只需添加一些条目到application.properties
文件(src/main/resources
如果你没有它创建它)
jsondoc.version=1.0
jsondoc.basePath=http://localhost:8080
jsondoc.packages[0]=org.example.shelf.model
jsondoc.packages[1]=org.example.shelf.controller
JSONDoc可以从Spring注释中获取几个信息来构建文档。无论如何,它是一个选择加入的过程,这意味着JSONDoc将仅在使用自己的注释注释时才扫描类和方法。例如,要正确记录BookController
,这里是如何使用JSONDoc注释:
package org.example.shelf.controller;
import java.util.List;
import org.example.shelf.flow.ShelfFlowConstants;
import org.example.shelf.model.Book;
import org.example.shelf.repository.BookRepository;
import org.jsondoc.core.annotation.Api;
import org.jsondoc.core.annotation.ApiBodyObject;
import org.jsondoc.core.annotation.ApiMethod;
import org.jsondoc.core.annotation.ApiPathParam;
import org.jsondoc.core.annotation.ApiResponseObject;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.util.UriComponentsBuilder;
@RestController
@RequestMapping(value = "/books", produces = MediaType.APPLICATION_JSON_VALUE)
@Api(description = "The books controller", name = "Books services")
public class BookController {
@Autowired
private BookRepository bookRepository;
@ApiMethod
@RequestMapping(value = "/{id}", method = RequestMethod.GET)
public @ApiResponseObject Book findOne(@ApiPathParam(name = "id") @PathVariable Long id) {
return bookRepository.findOne(id);
}
@ApiMethod
@RequestMapping(method = RequestMethod.GET)
public @ApiResponseObject List<Book> findAll() {
return bookRepository.findAll();
}
@ApiMethod
@RequestMapping(method = RequestMethod.POST, consumes = MediaType.APPLICATION_JSON_VALUE)
@ResponseStatus(value = HttpStatus.CREATED)
public @ApiResponseObject ResponseEntity<Void> save(@ApiBodyObject @RequestBody Book book, UriComponentsBuilder uriComponentsBuilder) {
bookRepository.save(book);
HttpHeaders headers = new HttpHeaders();
headers.setLocation(uriComponentsBuilder.path("/books/{id}").buildAndExpand(book.getId()).toUri());
return new ResponseEntity<Void>(headers, HttpStatus.CREATED);
}
@ApiMethod
@RequestMapping(value = "/{id}", method = RequestMethod.DELETE)
@ResponseStatus(value = HttpStatus.OK)
public void delete(@ApiPathParam(name = "id") @PathVariable Long id) {
Book book = bookRepository.findOne(id);
bookRepository.delete(book);
}
}
同样的 AuthorController
。
接下来要做的就是把一些JSONDoc注释也需要被记录在案,在这种情况下,对象Book
和Author
。这是Book
类:
package org.example.shelf.model;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import org.jsondoc.core.annotation.ApiObject;
import org.jsondoc.core.annotation.ApiObjectField;
import lombok.Data;
import lombok.EqualsAndHashCode;
@Entity
@Data
@EqualsAndHashCode(exclude = "id")
@ApiObject
public class Book {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
@ApiObjectField(description = "The book's ID")
private Long id;
@Column(name = "title")
@ApiObjectField(description = "The book's title")
private String title;
@ManyToOne
@JoinColumn(name = "author_id")
@ApiObjectField(description = "The book's author")
private Author author;
}
而且在这种情况下 Author
也是如此
。
在开始记录流程之前,让我们启动应用程序,看看会发生什么:
http://localhost:8080/jsondoc
你会看到一个json,这是由JSONDoc生成的,它代表了基于控制器方法和模型对象上的注释的文档http://localhost:8080/jsondoc-ui.html
你会看到JSONDoc UI。只需复制并粘贴http://localhost:8080/jsondoc
到输入字段中,并在清晰的用户界面中获取文档这是一个很好的时机,需要一些时间来探索界面,并在界面上玩API。
按照流程我的意思是一些API方法的后续执行,旨在实现一个目标,即可以购买一本书,或浏览目录并获取图书详细信息。在这种情况下,流程可能涉及几种方法,API用户可能需要知道哪个是正确的调用方法序列来实现目标。在这个例子中,我不能想到有意义的流程,但是让我们假设我想要记录浏览框架的方法顺序,并通过我选择的一本书获取作者的细节,所以这个用例的结果流是就像是:
要记录此流程,您只需按照以下步骤操作:
@ApiFlowSet
,这使得JSONDoc了解在构建文档时应该考虑到这个类。@ApiFlow
。方法的正文以及它的返回类型和参数可以是void,因为方法签名服务器只是作为@ApiFlow
注释的钩子findAll
方法的BookController
可有一个像IDBOOK_FIND_ALL
@ApiMethod
注释和内部 api methodid 的@ApiFlowStep
注解application.properties
使用该值更新该文件我们来看看我是怎么做到的 这是持有应用程序流程的类:
package org.example.shelf.flow;
import org.jsondoc.core.annotation.ApiFlow;
import org.jsondoc.core.annotation.ApiFlowSet;
import org.jsondoc.core.annotation.ApiFlowStep;
@ApiFlowSet
public class ShelfFlows {
@ApiFlow(
name = "Author detail flow",
description = "Gets an author's details starting from the book's list",
steps = {
@ApiFlowStep(apimethodid = ShelfFlowConstants.BOOK_FIND_ALL),
@ApiFlowStep(apimethodid = ShelfFlowConstants.BOOK_FIND_ONE),
@ApiFlowStep(apimethodid = ShelfFlowConstants.AUTHOR_FIND_ONE)
}
)
public void authorDetailFlow() {
}
}
这是包含注释中要引用的方法ID的类:
package org.example.shelf.flow;
public class ShelfFlowConstants {
// Book IDs
public final static String BOOK_FIND_ALL = "BOOK_FIND_ALL";
public final static String BOOK_FIND_ONE = "BOOK_FIND_ONE";
public final static String BOOK_SAVE = "BOOK_SAVE";
public final static String BOOK_DELETE = "BOOK_DELETE";
// Author IDs
public final static String AUTHOR_FIND_ALL = "AUTHOR_FIND_ALL";
public final static String AUTHOR_FIND_ONE = "AUTHOR_FIND_ONE";
public final static String AUTHOR_SAVE = "AUTHOR_SAVE";
public final static String AUTHOR_DELETE = "AUTHOR_DELETE";
}
这是 BookController
,指定了 id 属性后:
package org.example.shelf.controller;
import java.util.List;
import org.example.shelf.flow.ShelfFlowConstants;
import org.example.shelf.model.Book;
import org.example.shelf.repository.BookRepository;
import org.jsondoc.core.annotation.Api;
import org.jsondoc.core.annotation.ApiBodyObject;
import org.jsondoc.core.annotation.ApiMethod;
import org.jsondoc.core.annotation.ApiPathParam;
import org.jsondoc.core.annotation.ApiResponseObject;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.util.UriComponentsBuilder;
@RestController
@RequestMapping(value = "/books", produces = MediaType.APPLICATION_JSON_VALUE)
@Api(description = "The books controller", name = "Books services")
public class BookController {
@Autowired
private BookRepository bookRepository;
@ApiMethod(id = ShelfFlowConstants.BOOK_FIND_ONE)
@RequestMapping(value = "/{id}", method = RequestMethod.GET)
public @ApiResponseObject Book findOne(@ApiPathParam(name = "id") @PathVariable Long id) {
return bookRepository.findOne(id);
}
@ApiMethod(id = ShelfFlowConstants.BOOK_FIND_ALL)
@RequestMapping(method = RequestMethod.GET)
public @ApiResponseObject List<Book> findAll() {
return bookRepository.findAll();
}
@ApiMethod(id = ShelfFlowConstants.BOOK_SAVE)
@RequestMapping(method = RequestMethod.POST, consumes = MediaType.APPLICATION_JSON_VALUE)
@ResponseStatus(value = HttpStatus.CREATED)
public @ApiResponseObject ResponseEntity<Void> save(@ApiBodyObject @RequestBody Book book, UriComponentsBuilder uriComponentsBuilder) {
bookRepository.save(book);
HttpHeaders headers = new HttpHeaders();
headers.setLocation(uriComponentsBuilder.path("/books/{id}").buildAndExpand(book.getId()).toUri());
return new ResponseEntity<Void>(headers, HttpStatus.CREATED);
}
@ApiMethod(id = ShelfFlowConstants.BOOK_DELETE)
@RequestMapping(value = "/{id}", method = RequestMethod.DELETE)
@ResponseStatus(value = HttpStatus.OK)
public void delete(@ApiPathParam(name = "id") @PathVariable Long id) {
Book book = bookRepository.findOne(id);
bookRepository.delete(book);
}
}
最后的 application.properties
文件,用新的包:
jsondoc.version=1.0
jsondoc.basePath=http://localhost:8080
jsondoc.packages[0]=org.example.shelf.model
jsondoc.packages[1]=org.example.shelf.controller
jsondoc.packages[2]=org.example.shelf.flow
现在是再次启动应用程序的时候,转到 http://localhost:8080/jsondoc-ui.html
,插入 http://localhost:8080/jsondoc
输入框并获取文档。请享用!