目前来讲,在 Java 领域使用 Springboot
构建微服务是比较流行的,在构建微服务时,咱们大多数会选择暴漏一个 REST API
以供调用。又或者公司采用先后端分离的开发模式,让前端和后端的工做由彻底不一样的工程师进行开发完成。不论是微服务仍是这种先后端分离开发,维持一份完整的及时更新的 REST API
文档,会极大的提升咱们的工做效率。而传统的文档更新方式(如手动编写),很难保证文档的及时性,常常会年久失修,失去应有的意义。所以选择一种新的 API 文档维护方式颇有必要,这也是这篇文章要介绍的内容。html
OpenAPI Specification
简称 OAS,中文也称 OpenAPI
描述规范,使用 OpenAPI
文件能够描述整个 API,它制定了一套的适合通用的与语言无关的 REST API
描述规范,如 API 路径规范、请求方法规范、请求参数规范、返回格式规范等各类相关信息,令人类和计算机均可以不须要访问源代码就能够理解和使用服务的功能。前端
下面是 OpenAPI
规范中建议的 API 设计规范,基本路径设计规范。java
https://api.example.com/v1/users?role=admin&status=active
\________________________/\____/ \______________________/
server URL endpoint query parameters
path
复制代码
对于传参的设计也有规范,能够像下面这样:git
/users/{id}
/users?role=未读代码
X-MyHeader: Value
Cookie: debug=0; csrftoken=BUSe35dohU3O1MZvDCU
OpenAPI
规范的东西远远不止这些,目前 OpenAPI
规范最新版本是 3.0.2,若是你想了解更多的 OpenAPI
规范,能够访问下面的连接。 OpenAPI Specification (https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.2.md)github
不少人都觉得 Swagger
只是一个接口文档生成框架,其实并非。 Swagger 是一个围绕着 OpenAPI Specification
(OAS,中文也称 OpenAPI规范)构建的一组开源工具。能够帮助你从 API 的设计到 API 文档的输出再到 API 的测试,直至最后的 API 部署等整个 API 的开发周期提供相应的解决方案,是一个庞大的项目。 Swagger 不只免费,并且开源,无论你是企业用户仍是我的玩家,均可以使用 Swagger 提供的方案构建使人惊艳的 REST API
。web
Swagger 有几个主要的产品。面试
若是你想了解更多信息,能够访问 Swagger 官方网站 swagger.io。spring
源于 Java 中 Spring 框架的流行,让一个叫作 Marrty Pitt 的老外有了为 SpringMVC 添加接口描述的想法,所以他建立了一个遵照 OpenAPI 规范(OAS)的项目,取名为 swagger-springmvc,这个项目可让 Spring 项目自动生成 JSON 格式的 OpenAPI 文档。这个框架也仿照了 Spring 项目的开发习惯,使用注解来进行信息配置。shell
后来这个项目发展成为 Springfox
,再后来扩展出 springfox-swagger2
,为了让 JSON 格式的 API 文档更好的呈现,又出现了 springfox-swagger-ui
用来展现和测试生成的 OpenAPI 。这里的 springfox-swagger-ui 其实就是上面介绍的 Swagger-ui,只是它被经过 webjar 的方式打包到 jar 包内,并经过 maven 的方式引入进来。json
上面提到了 Springfox-swagger2 也是经过注解进行信息配置的,那么是怎么使用的呢?下面列举经常使用的一些注解,这些注解在下面的 Springboot 整合 Swagger 中会用到。
注解 | 示例 | 描述 |
---|---|---|
@ApiModel | @ApiModel(value = "用户对象") | 描述一个实体对象 |
@ApiModelProperty | @ApiModelProperty(value = "用户ID", required = true, example = "1000") | 描述属性信息,执行描述,是否必须,给出示例 |
@Api | @Api(value = "用户操做 API(v1)", tags = "用户操做接口") | 用在接口类上,为接口类添加描述 |
@ApiOperation | @ApiOperation(value = "新增用户") | 描述类的一个方法或者说一个接口 |
@ApiParam | @ApiParam(value = "用户名", required = true) | 描述单个参数 |
更多的 Springfox 介绍,能够访问 Springfox 官方网站。
Springfox Reference Documentation (http://springfox.github.io)
就目前来讲 ,Springboot 框架是很是流行的微服务框架,在微服务框架下,不少时候咱们都是直接提供 REST API
的。REST API 若是没有文档的话,使用者就很头疼了。不过不用担忧,上面说了有一位叫 Marrty Pitt 的老外已经建立了一个发展成为 Springfox 的项目,能够方便的提供 JSON 格式的 OpenAPI 规范和文档支持。且扩展出了 springfox-swagger-ui 用于页面的展现。
须要注意的是,这里使用的所谓的 Swagger 其实和真正的 Swagger 并非一个东西,这里使用的是 Springfox 提供的 Swagger 实现。它们都是基于 OpenAPI 规范进行 API 构建。因此也均可以 Swagger-ui 进行 API 的页面呈现。
如何建立一个 Springboot 项目这里不提,你能够直接从 Springboot 官方 下载一个标准项目,也可使用 idea 快速建立一个 Springboot 项目,也能够顺便拷贝一个 Springboot 项目过来测试,总之,方式多种多样,任你选择。
下面演示如何在 Springboot 项目中使用 swagger2。
这里主要是引入了 springfox-swagger2,能够经过注解生成 JSON 格式的 OpenAPI 接口文档,而后因为 Springfox 须要依赖 jackson,因此引入之。springfox-swagger-ui 能够把生成的 OpenAPI 接口文档显示为页面。Lombok 的引入能够经过注解为实体类生成 get/set 方法。
<dependencies>
<!-- Spring Boot web 开发整合 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<exclusions>
<exclusion>
<artifactId>spring-boot-starter-json</artifactId>
<groupId>org.springframework.boot</groupId>
</exclusion>
</exclusions>
</dependency>
<!-- 引入swagger2的依赖-->
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
<version>2.9.2</version>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
<version>2.9.2</version>
</dependency>
<!-- jackson相关依赖 -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.5.4</version>
</dependency>
<!-- Lombok 工具 -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
</dependencies>
复制代码
Springfox-swagger 的配置经过一个 Docket 来包装,Docket 里的 apiInfo 方法能够传入关于接口整体的描述信息。而 apis 方法能够指定要扫描的包的具体路径。在类上添加 @Configuration 声明这是一个配置类,最后使用 @EnableSwagger2 开启 Springfox-swagger2。
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;
/** * <p> * Springfox-swagger2 配置 * * @Author niujinpeng * @Date 2019/11/19 23:17 */
@Configuration
@EnableSwagger2
public class SwaggerConfig {
@Bean
public Docket createRestApi() {
return new Docket(DocumentationType.SWAGGER_2)
.apiInfo(apiInfo())
.select()
.apis(RequestHandlerSelectors.basePackage("net.codingme.boot.controller"))
.paths(PathSelectors.any())
.build();
}
private ApiInfo apiInfo() {
return new ApiInfoBuilder()
.title("未读代码 API")
.description("公众号:未读代码(weidudaima) springboot-swagger2 在线借口文档")
.termsOfServiceUrl("https://www.codingme.net")
.contact("达西呀")
.version("1.0")
.build();
}
}
复制代码
文章不会把全部代码一一列出来,这没有太大意义,因此只贴出主要代码,完整代码会上传到 Github,并在文章底部附上 Github 连接。
参数实体类 User.java
,使用 @ApiModel
和 @ApiModelProperty
描述参数对象,使用 @NotNull
进行数据校验,使用 @Data
为参数实体类自动生成 get/set 方法。
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.format.annotation.DateTimeFormat;
import javax.validation.constraints.NotNull;
import java.util.Date;
/** * <p> * 用户实体类 * * @Author niujinpeng * @Date 2018/12/19 17:13 */
@Data
@NoArgsConstructor
@AllArgsConstructor
@ApiModel(value = "用户对象")
public class User {
/** * 用户ID * * @Id 主键 * @GeneratedValue 自增主键 */
@NotNull(message = "用户 ID 不能为空")
@ApiModelProperty(value = "用户ID", required = true, example = "1000")
private Integer id;
/** * 用户名 */
@NotNull(message = "用户名不能为空")
@ApiModelProperty(value = "用户名", required = true)
private String username;
/** * 密码 */
@NotNull(message = "密码不能为空")
@ApiModelProperty(value = "用户密码", required = true)
private String password;
/** * 年龄 */
@ApiModelProperty(value = "用户年龄", example = "18")
private Integer age;
/** * 生日 */
@DateTimeFormat(pattern = "yyyy-MM-dd hh:mm:ss")
@ApiModelProperty(value = "用户生日")
private Date birthday;
/** * 技能 */
@ApiModelProperty(value = "用户技能")
private String skills;
}
复制代码
编写 Controller 层,使用 @Api
描述接口类,使用 @ApiOperation
描述接口,使用 @ApiParam
描述接口参数。代码中在查询用户信息的两个接口上都添加了 tags = "用户查询"
标记,这样这两个方法在生成 Swagger 接口文档时候会分到一个共同的标签组里。
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import lombok.extern.slf4j.Slf4j;
import net.codingme.boot.domain.Response;
import net.codingme.boot.domain.User;
import net.codingme.boot.enums.ResponseEnum;
import net.codingme.boot.utils.ResponseUtill;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.*;
import javax.validation.Valid;
import javax.validation.constraints.NotNull;
import java.util.ArrayList;
import java.util.List;
/** * <p> * 用户操做 * * @Author niujinpeng * @Date 2019/11/19 23:17 */
@Slf4j
@RestController(value = "/v1")
@Api(value = "用户操做 API(v1)", tags = "用户操做接口")
public class UserController {
@ApiOperation(value = "新增用户")
@PostMapping(value = "/user")
public Response create(@Valid User user, BindingResult bindingResult) throws Exception {
if (bindingResult.hasErrors()) {
String message = bindingResult.getFieldError().getDefaultMessage();
log.info(message);
return ResponseUtill.error(ResponseEnum.ERROR.getCode(), message);
} else {
// 新增用户信息 do something
return ResponseUtill.success("用户[" + user.getUsername() + "]信息已新增");
}
}
@ApiOperation(value = "删除用户")
@DeleteMapping(value = "/user/{username}")
public Response delete(@PathVariable("username") @ApiParam(value = "用户名", required = true) String name) throws Exception {
// 删除用户信息 do something
return ResponseUtill.success("用户[" + name + "]信息已删除");
}
@ApiOperation(value = "修改用户")
@PutMapping(value = "/user")
public Response update(@Valid User user, BindingResult bindingResult) throws Exception {
if (bindingResult.hasErrors()) {
String message = bindingResult.getFieldError().getDefaultMessage();
log.info(message);
return ResponseUtill.error(ResponseEnum.ERROR.getCode(), message);
} else {
String username = user.getUsername();
return ResponseUtill.success("用户[" + username + "]信息已修改");
}
}
@ApiOperation(value = "获取单个用户信息", tags = "用户查询")
@GetMapping(value = "/user/{username}")
public Response get(@PathVariable("username") @NotNull(message = "用户名称不能为空") @ApiParam(value = "用户名", required = true) String username) throws Exception {
// 查询用户信息 do something
User user = new User();
user.setId(10000);
user.setUsername(username);
user.setAge(99);
user.setSkills("cnp");
return ResponseUtill.success(user);
}
@ApiOperation(value = "获取用户列表", tags = "用户查询")
@GetMapping(value = "/user")
public Response selectAll() throws Exception {
// 查询用户信息列表 do something
User user = new User();
user.setId(10000);
user.setUsername("未读代码");
user.setAge(99);
user.setSkills("cnp");
List<User> userList = new ArrayList<>();
userList.add(user);
return ResponseUtill.success(userList);
}
}
复制代码
最后,为了让代码变得更加符合规范和好用,使用一个统一的类进行接口响应。
@Data
@AllArgsConstructor
@NoArgsConstructor
@ApiModel(value = "响应信息")
public class Response {
/** * 响应码 */
@ApiModelProperty(value = "响应码")
private String code;
/** * 响应信息 */
@ApiModelProperty(value = "响应信息")
private String message;
/** * 响应数据 */
@ApiModelProperty(value = "响应数据")
private Collection content;
}
复制代码
直接启动 Springboog 项目,能够看到控制台输出扫描到的各个接口的访问路径,其中就有 /2/api-docs
。
这个也就是生成的 OpenAPI 规范的描述 JSON 访问路径,访问能够看到。
由于上面咱们在引入依赖时,也引入了 springfox-swagger-ui 包,因此还能够访问 API 的页面文档。访问路径是 /swagger-ui.html,访问看到的效果能够看下图。
也能够看到用户查询的两个方法会归到了一块儿,缘由就是这两个方法的注解上使用相同的 tag 属性。
springfox-swagger-ui 不只是生成了 API 文档,还提供了调用测试功能。下面是在页面上测试获取单个用户信息的过程。
**Try it out**
进入测试传参页面。下面是测试时的响应截图。
若是你在程序运行中常常发现像下面这样的报错。
java.lang.NumberFormatException: For input string: ""
at java.lang.NumberFormatException.forInputString(NumberFormatException.java:65) ~[na:1.8.0_111]
at java.lang.Long.parseLong(Long.java:601) ~[na:1.8.0_111]
at java.lang.Long.valueOf(Long.java:803) ~[na:1.8.0_111]
at io.swagger.models.parameters.AbstractSerializableParameter.getExample(AbstractSerializableParameter.java:412) ~[swagger-models-1.5.20.jar:1.5.20]
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:1.8.0_111]
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[na:1.8.0_111]
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:1.8.0_111]
at java.lang.reflect.Method.invoke(Method.java:498) ~[na:1.8.0_111]
at com.fasterxml.jackson.databind.ser.BeanPropertyWriter.serializeAsField(BeanPropertyWriter.java:536) [jackson-databind-2.5.4.jar:2.5.4]
at com.fasterxml.jackson.databind.ser.std.BeanSerializerBase.serializeFields(BeanSerializerBase.java:666) [jackson-databind-2.5.4.jar:2.5.4]
at com.fasterxml.jackson.databind.ser.BeanSerializer.serialize(BeanSerializer.java:156) [jackson-databind-2.5.4.jar:2.5.4]
at com.fasterxml.jackson.databind.ser.impl.IndexedListSerializer.serializeContents(IndexedListSerializer.java:113) [jackson-databind-2.5.4.jar:2.5.4]
复制代码
那么你须要检查使用了 @ApiModelProperty
注解且字段类型为数字类型的属性上,@ApiModelProperty
注解是否设置了 example 值,若是没有,那就须要设置一下,像下面这样。
@NotNull(message = "用户 ID 不能为空")
@ApiModelProperty(value = "用户ID", required = true, example = "1000")
private Integer id;
复制代码
文中代码都已经上传到 https://github.com/niumoo/springboot
<完>
我的网站:www.codingme.net
若是你喜欢这篇文章,能够关注公众号,一块儿成长。 关注公众号回复资源能够没有套路的获取全网最火的的 Java 核心知识整理&面试核心资料。