目录
●What & Why
大家加入一个项目团队,接手一个全新的项目时有没有遇到这种情况:项目参与人数众多,开发周期长,没有很好的API文档,甚至代码中注释也寥寥无几,有时候一个方法,或者一个类只有一句话描述,必须要自己去通读代码才能理解。笔者作为新人,加入目前的项目之初就有这样的想法,当时觉得要是有一个完善的API文档该多好。
但现实中,想要输出一个好的API文档其实困难重重,首先,因为团队作战,风格统一是最大的一道坎,API文档应该怎么去写,有什么格式规定,必须要保持一致,否则难以维护;其次,大家工作都很饱和,想要挤出时间来生成与维护这样一份文档,其实是蛮花费时间和精力的一件事。
笔者最近学习SpringBoot时就发现了一个利器,可以很好的解决这个问题,它就是Swagger2。它能规范API文档的格式,并且它能自动生成API文档,给大家省了很多事儿!
Swagger2在日常使用中,其实是分为两部分的。一是Swagger2-core,它的作用是提供注解,程序员在开发过程中,对方法进行注解,该方法被标记成了API文档中的一项,之后他就能生成API文档的JSON字符串;二是Swagger2-UI,它的作用是解析生成的API文档JSON字符串,并将其在页面上进行展示。
唯一需要注意的是,Swagger2是基于OpenAPI3.0规范的,换句话说,它适用的API是RESTful风格的,举个例子,就像现在很多公司的开放平台,例如阿里、百度的那样,通过Url去调用对方RESTful风格的API。大家如果觉得抽象可以去阿里云这样的开放平台看看他们的API调用相关内容就了解了。
话不多说,我们来看看如何使用。
●引入依赖
根据刚才说的,我们需要引入两部分的依赖,以maven的pom文件为例:
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
<version>2.8.0</version>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
<version>2.8.0</version>
</dependency>
●正常开发
接下来就可以开始正常开发过程了,当然了,如果各位是在项目开发或者维护阶段,中途引入pom也是可以的,直接进行配置和注解就行。这里,我们将结合实际的开发来举例子说明。
我们使用SpringBoot工程为例,也顺便和大家一起学习下如何快速开始一个SpringBoot项目的搭建与开发。首先,我们进入https://start.spring.io/,通过官方网站快速生成一个空的SpringBoot项目,大家如果习惯用IDEA自己新建也是OK的。注意红框的地方,按需填写,然后点击Switch to the full version,选择其他相关依赖,笔者选了Web、JPA、MySQL,后期大家也可以随时根据项目需要的依赖直接添加到pom文件即可。最后点击Generate Project生成项目。
使用IDEA或者大家喜欢的IDE导入刚生成的maven项目,然后开始撸代码。假设有这么一个简单的业务场景:管理设备资源,可以对设备进行添加、删除以及查询操作。
先准备一个实体类:
@Entity
@Table(name = "device_info")
public class deviceInfo implements Serializable {
private static final long serialVersionUID = 1L;
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
@Column(name = "id", unique = true, nullable = false)
private Long id;
@Column(name = "device_name")
private String deviceName;
@Column(name = "device_owner")
private String deviceOwner;
/* 篇幅有限,省略构造函数和get、set函数 */
}
dao层接口使用Spring Data JPA来写,不清楚的朋友可以查看之前的一篇文章:
public interface DeviceInfoRepository extends JpaRepository<DeviceInfo, Long> {
/**
* 按归属者查找设备
* @param deviceOwner
* @return 设备列表
*/
List<DeviceInfo> findByDeviceOwner(String deviceOwner);
/**
* 按名字删除设备
* @param deviceName
* @return 状态值
*/
int deleteByDeviceName(String deviceName);
/**
* 修改设备名
* @param deviceName
* @param id
* @return 状态值
*/
@Modifying
@Query("update DeviceInfo d set d.deviceName = ?1 where d.id = ?2")
int modifyById(String deviceName, Long id);
}
service层包括以下几个方法,提供设备添加、删除、修改和查询:
@Service
public class DeviceInfoService {
@Autowired
DeviceInfoRepository deviceInfoRepository;
public void addDevice(List<DeviceInfo> deviceInfos){
deviceInfoRepository.saveAll(deviceInfos);
}
public int deteleByDeviceName(String deviceName){
return deviceInfoRepository.deleteByDeviceName(deviceName);
}
public int modifyDeviceNameById(String deviceName , Long id){
return deviceInfoRepository.modifyById(deviceName,id);
}
public List<DeviceInfo> findByOwner(String deviceOwner){
return deviceInfoRepository.findByDeviceOwner(deviceOwner);
}
public DeviceInfo findById(Long id){
return deviceInfoRepository.findById(id).orElseGet(() -> new DeviceInfo());
}
}
最后是controller层,也就是对外提供API的地方,值得注意的是,RESTful的接口命名要规范,不要在URL中暴露动词:
@RestController
@RequestMapping(value="/device_info")
public class DeviceInfoController {
@Autowired
DeviceInfoService deviceInfoService;
@RequestMapping(value="/{owner}", method=RequestMethod.GET)
public List<DeviceInfo> getDeviceListByDeviceOwner(@PathVariable String owner) {
// 处理"/device_info/"的GET请求,用来获取设备列表
List<DeviceInfo> r = deviceInfoService.findByOwner(owner);
return r;
}
@RequestMapping(value="/{id}", method=RequestMethod.GET)
public DeviceInfo getDeviceById(@PathVariable Long id) {
// 处理"/device_info/{id}"的GET请求,用来获取url中id值的User信息
return deviceInfoService.findById(id);
}
@RequestMapping(value="/", method=RequestMethod.POST)
public String addDevice(@ModelAttribute DeviceInfo deviceInfo) {
// 处理"/device_info/"的POST请求,用来新增设备
List<DeviceInfo> deviceInfos = new ArrayList<>();
deviceInfos.add(deviceInfo);
deviceInfoService.addDevice(deviceInfos);
return "success";
}
@RequestMapping(value="/{deviceName}", method=RequestMethod.DELETE)
public String deleteDevice(@PathVariable String deviceName) {
// 处理"/device_info/{deviceName}"的DELETE请求,用来删除设备
deviceInfoService.deteleByDeviceName(deviceName);
return "success";
}
@RequestMapping(value="/{id}", method=RequestMethod.PUT)
public String modifyDevice(@PathVariable String deviceName,@PathVariable Long id) {
// 处理"/device_info/{id}"的PUT请求,用来更新设备信息
deviceInfoService.modifyDeviceNameById(deviceName,id);
return "success";
}
}
以上,就是常规的从domain→dao→service→controller分层的写法,大家应该已经很熟悉了,至此我们就模拟了一个已经开发了的项目。接下来我们要做的就是对这个已经存在的项目进行一定的改造,让Swagger2为我们自动生成API文档。
●Swagger2配置
Swagger2的配置很简单,我们采用java类的形式来写。直接在项目的启动类同级新建一个Swagger2类,如下:
@Configuration
@EnableSwagger2
public class Swagger2 {
@Bean
public Docket createRestApi() {
return new Docket(DocumentationType.SWAGGER_2)
.apiInfo(apiInfo())
.select()
.apis(RequestHandlerSelectors.basePackage("com.xenophon.swagger2demo.controller"))
.paths(PathSelectors.any())
.build();
}
private ApiInfo apiInfo() {
return new ApiInfoBuilder()
.title("Spring Boot中使用Swagger2构建RESTful APIs")
.description("第一个Swagger2自动构建演示项目")
.termsOfServiceUrl("https://blog.csdn.net/jui121314?t=1")
.contact("色诺芬")
.version("1.0")
.build();
}
}
需要注意RequestHandlerSelectors.basePackage()中填写需要扫描的API所在的包名,apiInfo()中填写相关的信息即可。
●编辑API接口
我们选择需要加入API文档的接口,添加上注解就OK了,是不是很简单。我们来学习一下Swagger2提供的注解以及用法,我们将刚才controller层中的接口全部添加到文档中:
@RestController
@RequestMapping(value="/device_info")
@Api(tags = "设备信息相关API")
public class DeviceInfoController {
@Autowired
DeviceInfoService deviceInfoService;
@ApiOperation(value="获取归属者设备列表", notes="")
@ApiImplicitParam(name = "owner", value = "设备归属者姓名", required = true, dataType = "String")
@RequestMapping(value="/{owner}", method=RequestMethod.GET)
public List<DeviceInfo> getDeviceListByDeviceOwner(@PathVariable String owner) {
// 处理"/device_info/"的GET请求,用来获取设备列表
// 还可以通过@RequestParam从页面中传递参数来进行查询条件或者翻页信息的传递
List<DeviceInfo> r = deviceInfoService.findByOwner(owner);
return r;
}
@ApiOperation(value="获取设备详细信息", notes="根据url的id来获取设备详细信息")
@ApiImplicitParam(name = "id", value = "设备ID", required = true, dataType = "Long")
@RequestMapping(value="/{id}", method=RequestMethod.GET)
public DeviceInfo getDeviceById(@PathVariable Long id) {
// 处理"/device_info/{id}"的GET请求,用来获取url中id值的User信息
// url中的id可通过@PathVariable绑定到函数的参数中
return deviceInfoService.findById(id);
}
@ApiOperation(value="新增设备", notes="根据Device对象创建设备")
@ApiImplicitParam(name = "deviceInfo", value = "设备详细实体deviceInfo", required = true, dataType = "DeviceInfo")
@RequestMapping(value="/", method=RequestMethod.POST)
public String addDevice(@ModelAttribute DeviceInfo deviceInfo) {
// 处理"/device_info/"的POST请求,用来新增设备
// 除了@ModelAttribute绑定参数之外,还可以通过@RequestParam从页面中传递参数
List<DeviceInfo> deviceInfos = new ArrayList<>();
deviceInfos.add(deviceInfo);
deviceInfoService.addDevice(deviceInfos);
return "success";
}
@ApiOperation(value="删除设备", notes="根据url的设备名来指定删除对象")
@ApiImplicitParam(name = "deviceName", value = "设备名", required = true, dataType = "String")
@RequestMapping(value="/{deviceName}", method=RequestMethod.DELETE)
public String deleteDevice(@PathVariable String deviceName) {
// 处理"/device_info/{deviceName}"的DELETE请求,用来删除设备
deviceInfoService.deteleByDeviceName(deviceName);
return "success";
}
@ApiOperation(value="更新设备名", notes="根据url的id来指定更新对象,并根据传过来的deviceName来更新设备名")
@ApiImplicitParams({
@ApiImplicitParam(name = "deviceName", value = "设备名", required = true, dataType = "String"),
@ApiImplicitParam(name = "id", value = "设备ID", required = true, dataType = "Long"),
})
@RequestMapping(value="/{id}", method=RequestMethod.PUT)
public String modifyDevice(@PathVariable String deviceName, @PathVariable Long id) {
// 处理"/device_info/{id}"的PUT请求,用来更新设备信息
deviceInfoService.modifyDeviceNameById(deviceName,id);
return "success";
}
}
相比之前,变化仅仅在于方法之前添加的几个注解。我们逐一解释一下:
首先,我们在类上加了一个@Api的注解。该注解用在请求的类上,表示对类的说明。其中,常用的参数包括:tags,用来对类的作用进行说明;hidden,默认为false, 配置为true 将在文档中隐藏(下面几个注解都具备,不再一一解释)。
其次,我们在方法上加了一个@ApiOperation的注解。该注解用在请求的方法上,说明方法的用途、作用。其中,常用的参数包括:value,说明方法的用途、作用;notes,方法的备注说明;response,响应类型。
然后,我们在方法上加了一个@ApiImplicitParam(s)的注解。如果是多个请求的情况,@ApiImplicitParams用在请求的方法上,表示一组参数说明;@ApiImplicitParam用在@ApiImplicitParams注解中,指定一个请求参数的说明。常用的参数包括:name,参数名;value,参数的说明、解释;required,参数是否必须传,默认为false,进行POST等请求必须要求参数时为true;dataType,参数类型,默认String,可以是任意Java对象,例如刚才定义的设备实体类DeviceInfo;defaultValue,参数的默认值。
至此,程序员需要人工做的就全部完成了
●Swagger-UI查看文档
现在,Swagger2已经帮我们生成好API文档了,我们要做的就是启动项目,进入http://localhost:8080/swagger-ui.html。当然,根据配置的IP、端口、路径,这个访问地址会有所不同,笔者仅举例默认情况下的访问路径。打开后我们就可以通过网页查看API文档了,如图:
每个方法都可以具体点开查看,甚至还能点击Try it out直接调用!
至此,Swagger2就算成功使用了,大家完全可以拿出以前的项目来试试,仅仅是导入依赖,添加配置,给方法写注解这么简单。
●彩蛋:swagger-ui-layer
正片结束,给大家放送一个彩蛋,第三方开源UI组件swagger-ui-layer。只需要引入一个依赖,就可以给Swagger2的UI界面换一套衣服。实际用下来,感觉还是蛮不错的,看上去挺舒服的。也许是Swagger2当时考虑用户会自己开发UI,所以随意给了个将就看得过去的页面设计。
swagger-ui-layer需要引入的依赖如下:
dependency>
<groupId>com.github.caspar-chen</groupId>
<artifactId>swagger-ui-layer</artifactId>
<version>1.1.0</version>
</dependency>
打开默认的访问链接http://localhost:8080/docs.html界面如下:
对的!你没看错,只需要引入一个依赖,更换一个访问地址就行,其余地方完全不用修改。页面更舒服耐看,同样也支持调试。唯一需要注意的是,该第三方组件截止撰文时依旧没有支持Swagger2的分组,因此大家在配置的时候,如果有group的设置,需要去掉。
●小结
其实Swagger2的可用性还是蛮高的。例如可以自己开发SwaggerUI页面,可以用更多的注解,可以分组,可以搭配其他框架(例如spring security)加入权限,等等,笔者只介绍了最简单的使用方法。大家如何真的需要使用的话,可以参考官方的文档进行。今天,你学会了吗?