Spring Cloud构建RESTful API文档

Posted by Kriz on 2017-12-19

本文基于Spring Cloud,通过SwaggerSpring REST docs两种方式分别构建RESTful documents。二者都是为Spring项目提供方法文档的框架,可以自动进行RESTful接口文档的生成或更新。

RESTful APIs规范

以上两种方式都基于REST,这里首先梳理一下REST相关的内容。

REST(Representational State Transfer)是一种互联网软件的架构原则,可以简单总结为:

  • 每一个URI代表一个资源
  • 客户端和服务器之间,传递这种资源的是某种表现层(即资源呈现的形式,如HTML/XML/JSON/PNG)
  • 客户端通过HTTP动词对服务器端的资源进行操作

一些要注意的地方:

  • URI不能包含动词。因为资源是一种实体(名词),动词应该放在HTTP协议中。

    例:

    √:PUT /zoos/ID
    ×:POST /zoos/ID/change
  • 一般来说,常用动词对应的动作:

    • GET:SELECT
    • POST:CREATE
    • PUT:UPDATE(客户端提供改变后的完整资源)
    • PATCH:UPDATE(客户端提供改变的属性)
    • DELETE:DELETE
  • API的身份认证应该使用OAuth 2.0框架。

  • 服务器返回的数据格式应该尽量使用JSON。

Demo:通过Spring REST docs生成文档

首先建立一个Spring Boot工程,在pom文件中引入依赖:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.restdocs</groupId>
<artifactId>spring-restdocs-mockmvc</artifactId>
<scope>test</scope>
</dependency>
</dependencies>

在Application中写好需要测试的mapping逻辑。作为范例,这里测试三个方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
@SpringBootApplication(exclude={DataSourceAutoConfiguration.class,HibernateJpaAutoConfiguration.class})
@RestController
public class TestApplication {
public static void main(String[] args) {
SpringApplication.run(TestApplication.class, args);
}
@Value("${server.port}")
String port;
@RequestMapping(value="/user", method=RequestMethod.GET)
public String getAllUser() { return "This is user list."; }
@RequestMapping(value="/user/{name}", method=RequestMethod.GET)
public String getUser(@PathVariable("name") String name) throws JSONException {
JSONObject obj = new JSONObject();
obj.put("name", name);
obj.put("message", "This is user " + name);
return obj.toString();
}
@RequestMapping(value="/user/{name}", method=RequestMethod.POST)
public String postUser(@PathVariable("name") String name) {
return "I've pushed user " + name + " into list.";
}
}

启动工程,访问localhost:8080,可以看到返回值信息被打印出来,运行成功。接下来进行测试和API文档的生成,建立一个单元测试类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
@RunWith(SpringRunner.class)
@WebMvcTest(TestApplication.class)
@AutoConfigureRestDocs(outputDir = "target/snippets")
public class TestApplicationTests {
@Autowired
private MockMvc mockMvc;
@Test
public void testGetUserList() throws Exception {
this.mockMvc.perform(get("/user")).andDo(print()).andExpect(status().isOk())
.andExpect(content().string(containsString("This is user list.")))
.andDo(document("getUserList"));
}
@Test
public void testGetUser() throws Exception {
this.mockMvc.perform(get("/user/kevinz")).andDo(print()).andExpect(status().isOk())
.andExpect(content().string(containsString("This is user kevinz")))
.andDo(document("getUser"));
}
@Test
public void testCreateUser() throws Exception {
this.mockMvc.perform(post("/user/kevinz")).andDo(print()).andExpect(status().isOk())
.andExpect(content().string(containsString("I've pushed user kevinz into list.")))
.andDo(document("createUser"));
}
}

这里的@AutoConfigureRestDocs指定了生成的snippets的存放位置,我们之后要通过snippets来生成html格式的API文档。

启动单元测试并通过后,可以看到目录下多出了targets/snippets文件夹,里面存放了:

产生的snippets是*.adoc格式(即Asciidoctor格式)文件,记录了http格式的request和response,以及其他两种格式的request。

到目前为止我们只生成了snippets文件,需要将它转换成html文档。这里需要用到asciidoctor-maven-plugin这个插件,在pom中添加:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<plugin>
<groupId>org.asciidoctor</groupId>
<artifactId>asciidoctor-maven-plugin</artifactId>
<executions>
<execution>
<id>generate-docs</id>
<phase>prepare-package</phase>
<goals>
<goal>process-asciidoc</goal>
</goals>
<configuration>
<sourceDocumentName>index.adoc</sourceDocumentName>
<backend>html</backend>
<attributes>
<snippets>${project.build.directory}/snippets</snippets>
</attributes>
</configuration>
</execution>
</executions>
</plugin>

然后在src/main目录下建立名为asciidoc的文件夹,写入文件index.adoc,依据此来生成最终文档。例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
= Spring REST Docs
Kevinz demo <kevinz.zhao@sap.com>
== getUserList
This is an example output for a getUserList service.
.request
include::{snippets}/getUserList/http-request.adoc[]
.response
include::{snippets}/getUserList/http-response.adoc[]
== getUser
This is an example output for a getUser service. It will return a json format string, includes:
*name
*a mysterious message
.request
include::{snippets}/getUser/http-request.adoc[]
.response
include::{snippets}/getUser/http-response.adoc[]
== createUser
This is an example output for a createUser service.
.request
include::{snippets}/createUser/http-request.adoc[]
.response
include::{snippets}/createUser/http-response.adoc[]

adoc文档的写作形式类似于markdown,可以在这里参考

完成后,只需要通过命令

1
mvnw package

就可以在测试后生成文档了。生成的文档默认存放位置是target/generated-docs,会提供基本的API信息,像下图这样:

(图中的8762端口是我在测试其他组件时避免冲突使用的,不设定的话默认是8080,这里不需要在意)

Demo:通过Swagger生成文档

首先在pom中引入依赖:

1
2
3
4
5
6
7
8
9
10
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
<version>2.2.2</version>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
<version>2.2.2</version>
</dependency>

然后在Application的同级目录下创建Swagger类。通过@Configuration注解令Spring加载该配置,通过@EnableSwagger2开启Swagger。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
@Configuration
@EnableSwagger2
public class Swagger2 {
@Bean
public Docket createRestApi() {
return new Docket(DocumentationType.SWAGGER_2)
.apiInfo(apiInfo())
.select()
.apis(RequestHandlerSelectors.basePackage("cn.kevinz.swagger"))
.paths(PathSelectors.any())
.build();
}
private ApiInfo apiInfo() {
return new ApiInfoBuilder()
.title("Swagger demo")
.description("Use Swagger2 to create APIs document.")
.contact("Kevinz")
.version("1.0")
.build();
}
}

运行程序后进入http://localhost:8080/swagger-ui.html即可看到Swagger自动生成的API文档,类似这样:

默认状态下提供请求本身的API信息,也可以进行简单的调试。但这样的文档不易理解,因此Swagger允许以注解的方式为API和参数增加说明。

为API增添说明使用@ApiOperation来完成,例如:

1
2
3
4
5
@ApiOperation(value="Get User List", notes="Get all users and put them into a list")
@RequestMapping(value="/user", method=RequestMethod.GET)
public String getAllUser() {
return "This is user list.";
}

为参数增加说明使用@ApiImplicitParam@ApiImplicitParams

1
2
3
4
5
6
7
8
9
@ApiOperation(value="Get User Information", notes="Get detailed information of specific user.")
@ApiImplicitParam(name = "name", value = "This is name of user", required = true, dataType = "String")
@RequestMapping(value="/user/{name}", method=RequestMethod.GET)
public String getUser(@PathVariable("name") String name) throws JSONException {
JSONObject obj = new JSONObject();
obj.put("name", name);
obj.put("message", "This is user " + name);
return obj.toString();
}
1
2
3
4
5
6
7
8
9
@ApiOperation(value="Create New User", notes="Create a new user who does not exist.")
@ApiImplicitParams({
@ApiImplicitParam(name = "id", value = "This is ID of user to specify user.", required = true, dataType = "Long"),
@ApiImplicitParam(name = "name", value = "This is name of user.", required = true, dataType = "String")
})
@RequestMapping(value="/user/{name}", method=RequestMethod.POST)
public String postUser(@PathVariable("name") String name) {
return "I've pushed user " + name + " into list.";
}

完成后的文档效果:

总结

Spring REST docs和Swagger都可以自动生成高质量的API文档,可以凭借喜好选用。

二者在使用上的主要区别是:

  • REST docs是测试驱动的,会对测试成功的接口进行文档的生成;Swagger的文档生成不涉及测试部分。
  • REST docs以adoc格式单独生成文档;Swagger将API的注释以annotation注入方法中。

更多差异可以参考这里来了解。