目錄
●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)加入權限,等等,筆者只介紹了最簡單的使用方法。大家如何真的需要使用的話,可以參考官方的文檔進行。今天,你學會了嗎?