在前后端分离盛兴的时代,REST API是前后端通信的必要途径。一个好的REST API,不仅符合RESTful规范,还需要有一个高质量的API服务文档。对于API服务文档来讲,难点不是创建文档,而是如何更高效的维护文档,让文档和代码的变化保持同步。错误的API服务文档,会让使用者走入歧途,还不如没有文档的好。
Spring REST Docs
值得开心的是,在Spring全家桶中就用这样的工具来满足我们的要求。Spring REST Docs采用手写文档和自动生成片段相结合的方式来创建API文档。Asciidoc是手写文档的格式,这种文档格式在Spring全家桶中广广泛使用,Spring Reference document就是使用它生成的。
Spring REST Docs依据单元测试时所需的HTTP请求及其响应自动生成单元测试的文档。
配置
我们都知道现在流行的Java项目管理工具有Maven和Gradle两种。在Spring Framework的高级版本,将项目管理工具从Maven切换到了Gradle。因此,我们也采用Gradle来管理我们的项目。
build.gradle配置
buildscript {
repositories {
mavenCentral()
}
dependencies {
classpath 'org.springframework.boot:spring-boot-gradle-plugin:2.1.8.RELEASE'
}
}
plugins {
id "org.asciidoctor.convert" version "1.5.9.2"
}
apply plugin: 'java'
apply plugin: 'org.springframework.boot'
apply plugin: 'eclipse'
apply plugin: 'io.spring.dependency-management'
repositories {
mavenLocal()
maven { url 'https://repo.spring.io/libs-snapshot' }
mavenCentral()
}
sourceCompatibility = 1.8
targetCompatibility = 1.8
ext {
snippetsDir = file('build/generated-snippets')
}
ext['spring-restdocs.version'] = '2.0.5.BUILD-SNAPSHOT'
dependencies {
asciidoctor 'org.springframework.restdocs:spring-restdocs-asciidoctor'
compile 'org.springframework.boot:spring-boot-starter-web'
testCompile('org.springframework.boot:spring-boot-starter-test') {
exclude group: 'junit', module: 'junit;'
}
testCompile 'org.springframework.restdocs:spring-restdocs-mockmvc'
testCompile 'org.junit.jupiter:junit-jupiter-api'
testRuntime 'org.junit.jupiter:junit-jupiter-engine'
}
test {
outputs.dir snippetsDir
useJUnitPlatform()
}
asciidoctor {
inputs.dir snippetsDir
dependsOn test
}
bootJar {
dependsOn asciidoctor
from("${asciidoctor.outputDir}/html5") {
into 'static/docs'
}
}
eclipseJdt.onlyIf { false }
cleanEclipseJdt.onlyIf { false }
在这里,我们的单元测试JUnit使用JUnit 5版本,它在Spring 5中得到了很好的支持。
像其他的Spring Boot项目一样,我们需要创建一个Application。
package io.github.tinyking.spotlighs.restdocs;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
/**
* RestdocsApplication.
*
* @author tinyking
* @version 1.0
* @since 2019/10/15
*/
@SpringBootApplication
public class RestdocsApplication {
public static void main(String[] args) {
SpringApplication.run(RestdocsApplication.class, args);
}
}
创建一个简单的Controller,用来做的HTTP API测试
package io.github.tinyking.spotlighs.restdocs;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* SampleController.
*
* @author tinyking
* @version 1.0
* @since 2019/10/15
*/
@RestController
public class SampleController {
@GetMapping("/")
public String index() {
return "Hello, World";
}
}
接下来,我们需要为Application创建一个使用JUnit 5单元测试
package io.github.tinyking.spotlighs.restdocs;
import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.document;
import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.documentationConfiguration;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.restdocs.AutoConfigureRestDocs;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.restdocs.RestDocumentationContextProvider;
import org.springframework.restdocs.RestDocumentationExtension;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.web.context.WebApplicationContext;
/**
* RestdocsApplicationTest.
*
* @author tinyking
* @version 1.0
* @since 2019/10/15
*/
@SpringBootTest
@ExtendWith({RestDocumentationExtension.class, SpringExtension.class})
public class RestdocsApplicationTest {
@Autowired
private WebApplicationContext wac;
private MockMvc mockMvc;
@BeforeEach
public void setup(RestDocumentationContextProvider restDocumentationContextProvider) {
this.mockMvc = MockMvcBuilders.webAppContextSetup(wac)
.apply(documentationConfiguration(restDocumentationContextProvider)).build();
}
@Test
public void sample() throws Exception {
this.mockMvc.perform(get("/"))
.andExpect(status().isOk())
.andDo(document("sample"));
}
}
手写文档
就像一开始说的,我们需要手写文档+自动生成来实现REST API服务文档。上面所有的操作都是为了自动生成片段,下面我们来创建一个手写文档。
spotlight-spring-restdocs/src/docs/asciidoc/index.adoc
= 基于JUnit5的Spring restdocs实例
tinyking;
:doctype: book
:icons: font
:source-highlighter: highlightjs
Http curl请求示例:
include::{snippets}/sample/curl-request.adoc[]
Http请求:
include::{snippets}/sample/http-request.adoc[]
Http响应:
include::{snippets}/sample/http-response.adoc[]
这就是一个简单的手写+自动的文档,里面的curl-request.adoc
、http-request.adoc、
http-response.adoc都是根据单元测试自动生成的。
生成API文档
接下来我们通过命名来生成API文档
gradle bootJar
执行完成后,可以找到build/asciidoc/html5/index.html
,直接在浏览器中打开,如下:
总结
通过使用Spring REST docs,可以让我们快速的生成API服务文档,基于单元测试可以确保API文档和代码保持一致。并且Spring REST Docs的方式不像Swagger,它不会入侵我们的业务代码,保证我们代码的整洁性。