SpringBoot中使用springfox+swagger2書寫API文檔

隨着先後端的分離,藉口文檔變的尤爲重要,springfox是經過註解的形式自動生成API文檔,利用它,能夠很方便的書寫restful API,swagger主要用於展現springfox生成的API文檔。html

官網地址:http://springfox.github.io/springfox/java

Springfox大體原理jquery

springfox的大體原理就是,在項目啓動的過種中,spring上下文在初始化的過程,框架自動跟據配置加載一些swagger相關的bean到當前的上下文中,並自動掃描系統中可能須要生成api文檔那些類,並生成相應的信息緩存起來。若是項目MVC控制層用的是springMvc那麼會自動掃描全部Controller類,跟據這些Controller類中的方法生成相應的api文檔。git

Spring集成Springfox步驟及說明:程序員

1、添加Swagger2依賴github

<!-- Swagger --> <dependency> <groupId>io.springfox</groupId> <artifactId>springfox-swagger2</artifactId> <version>2.6.1</version> </dependency> <dependency> <groupId>io.springfox</groupId> <artifactId>springfox-swagger-ui</artifactId> <version>2.6.1</version> </dependency>

2、application.properties中添加配置web

#解決中文亂碼問題 banner.charset=UTF-8 server.tomcat.uri-encoding=UTF-8 spring.http.encoding.charset=UTF-8 spring.http.encoding.enabled=true spring.http.encoding.force=true spring.messages.encoding=UTF-8 #Swagger Configure Properties sop.swagger.enable=true sop.swagger.packageScan=com.example sop.swagger.title=UserController Restfull API sop.swagger.description=UserController Restfull API sop.swagger.version=3.0

3、建立SwaggerConfigProperties加載配置項ajax

package com.example.config; import java.io.Serializable; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.stereotype.Component; @ConfigurationProperties(prefix = "sop.swagger") @Component public class SwaggerConfigProperties implements Serializable { /** * 是否開啓Swagger */ private boolean enable = false; /** * 要掃描的包 */ private String packageScan; /** * 標題 */ private String title; /** * 描述 */ private String description; /** * 版本信息 */ private String version; public boolean isEnable() { return enable; } public void setEnable(boolean enable) { this.enable = enable; } public String getPackageScan() { return packageScan; } public void setPackageScan(String packageScan) { this.packageScan = packageScan; } public String getTitle() { return title; } public void setTitle(String title) { this.title = title; } public String getDescription() { return description; } public void setDescription(String description) { this.description = description; } public String getVersion() { return version; } public void setVersion(String version) { this.version = version; } }

4、建立Swagger2配置類正則表達式

package com.example.config;

import org.springframework.beans.factory.annotation.Autowired; 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; @Configuration @EnableSwagger2 public class Swagger2 { @Autowired private SwaggerConfigProperties scp; @Bean public Docket createRestApi() { return new Docket(DocumentationType.SWAGGER_2) .apiInfo(apiInfo()) .select() .apis(RequestHandlerSelectors.basePackage("com.example.web")) .paths(PathSelectors.any()) .build(); } private ApiInfo apiInfo() { return new ApiInfoBuilder() .title(scp.getTitle()) .description(scp.getDescription()) .version("1.0") .build(); } }

5、建立modelspring

package com.example.model; import io.swagger.annotations.ApiModelProperty; public class User { @ApiModelProperty(value = "主鍵") private Long id; @ApiModelProperty(value = "名字") private String name; @ApiModelProperty(value = "年齡") private Integer age; @ApiModelProperty(value = "密碼") private String password; public Long getId() { return id; } public void setId(Long id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public Integer getAge() { return age; } public void setAge(Integer age) { this.age = age; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } @Override public String toString() { return "User{" + "id=" + id + ", name='" + name + '\'' + ", age=" + age + ", password=" + password + '}'; } }

6、建立Controller

package com.example.web;

import java.util.Collections; import java.util.HashMap; import java.util.Map; import com.example.model.User; import io.swagger.annotations.Api; import io.swagger.annotations.ApiImplicitParam; import io.swagger.annotations.ApiImplicitParams; import io.swagger.annotations.ApiOperation; import io.swagger.annotations.ApiResponse; import io.swagger.annotations.ApiResponses; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestHeader; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; import springfox.documentation.annotations.ApiIgnore; @RestController @Api("userController相關api") public class UserController { @ApiOperation("獲取用戶信息") @ApiImplicitParams({ @ApiImplicitParam(paramType = "header", name = "username", dataType = "String", required = true, value = "用戶的姓名", defaultValue = "xiaoqiang"), @ApiImplicitParam(paramType = "query", name = "password", dataType = "String", required = true, value = "用戶的密碼", defaultValue = "xiaoxiong") }) @ApiResponses({ @ApiResponse(code = 400, message = "請求參數沒填好"), @ApiResponse(code = 404, message = "請求路徑沒有或頁面跳轉路徑不對") }) @RequestMapping(value = "/getUser", method = RequestMethod.GET) public User getUser(@RequestHeader("username") String username, @RequestParam("password") String password) { User user = new User(); user.setName(username); user.setPassword(password); return user; } @ApiOperation(value = "建立用戶", notes = "根據User對象建立用戶") @ApiImplicitParam(name = "user", value = "用戶詳細實體user", required = true, dataType = "User") @RequestMapping(value = "", method = RequestMethod.POST) public String postUser(@RequestBody User user) { Map<Long, User> users = Collections.synchronizedMap(new HashMap<Long, User>()); users.put(user.getId(), user); return "success"; } @ApiIgnore @RequestMapping(value = "/", method = RequestMethod.GET) public String home() { return "hello"; } }

完成上述代碼添加上,啓動Spring Boot程序,訪問:http://localhost:8080/swagger-ui.html
。就能看到前文所展現的RESTful API的頁面。咱們能夠再點開具體的API請求,以POST類型的/users請求爲例,可找到上述代碼中咱們配置的Notes信息以及參數user的描述信息,以下圖所示。

7、API文檔訪問與調試

在上圖請求的頁面中,咱們看到user的Value是個輸入框?是的,Swagger除了查看接口功能外,還提供了調試測試功能,咱們能夠點擊上圖中右側的Model Schema(黃色區域:它指明瞭User的數據結構),此時Value中就有了user對象的模板,咱們只須要稍適修改,點擊下方「Try it out!」按鈕,便可完成了一次請求調用!

此時,你也能夠經過幾個GET請求來驗證以前的POST請求是否正確。

相比爲這些接口編寫文檔的工做,咱們增長的配置內容是很是少並且精簡的,對於原有代碼的侵入也在忍受範圍以內。所以,在構建RESTful API的同時,加入swagger來對API文檔進行管理,是個不錯的選擇。

8、springfox、swagger.annotations註解部分參數介紹

在上面只展現瞭如何使用,這裏將對上面添加的swagger註解進行說明,筆記使用時參考了swagger annotations Api 手冊,接下來進行部分經常使用註解使用說明介紹。

  • @ApiIgnore 忽略註解標註的類或者方法,不添加到API文檔中
  • @ApiOperation 展現每一個API基本信息
    • value api名稱
    • notes 備註說明
  • @ApiImplicitParam 用於規定接收參數類型、名稱、是否必須等信息
    • name 對應方法中接收參數名稱
    • value 備註說明
    • required 是否必須 boolean
    • paramType 參數類型 body、path、query、header、form中的一種
    • body 使用@RequestBody接收數據 POST有效
    • path 在url中配置{}的參數
    • query 普通查詢參數 例如 ?query=q ,jquery ajax中data設置的值也能夠,例如 {query:」q」},springMVC中不須要添加註解接收
    • header 使用@RequestHeader接收數據
    • form 筆者未使用,請查看官方API文檔
    • dataType 數據類型,若是類型名稱相同,請指定全路徑,例如 dataType = 「java.util.Date」,springfox會自動根據類型生成模型
  • @ApiImplicitParams 包含多個@ApiImplicitParam
  • @ApiModelProperty 對模型中屬性添加說明,例如 上面的PageInfoBeen、BlogArticleBeen這兩個類中使用,只能使用在類中。
    • value 參數名稱
    • required 是否必須 boolean
    • hidden 是否隱藏 boolean
    • 其餘信息和上面同名屬性做用相同,hidden屬性對於集合不能隱藏,目前不知道緣由
  • @ApiParam 對單獨某個參數進行說明,使用在類中或者controller方法中均可以。註解中的屬性和上面列出的同名屬性做用相同

其餘註解:https://github.com/swagger-api/swagger-core/wiki/Annotations#apimodel

9、springfox中的那些坑

springfox第一大坑:Controller類的參數,注意防止出現無限遞歸的狀況。

Spring mvc有強大的參數綁定機制,能夠自動把請求參數綁定爲一個自定義的命令對像。因此,不少開發人員在寫Controller時,爲了偷懶,直接把一個實體對像做爲Controller方法的一個參數。好比下面這個示例代碼:
@RequestMapping(value = "update")
public String update(MenuVo menuVo, Model model){
}
這是大部分程序員喜歡在Controller中寫的修改某個實體的代碼。在跟swagger集成的時候,這裏有一個大坑。若是MenuVo這個類中全部的屬性都是基本類型,那還好,不會出什麼問題。但若是這個類裏面有一些其它的自定義類型的屬性,並且這個屬性又直接或間接的存在它自身類型的屬性,那就會出問題。例如:假如MenuVo這個類是菜單類,在這個類時又含有MenuVo類型的一個屬性parent表明它的父級菜單。這樣的話,系統啓動時swagger模塊就因沒法加載這個api而直接報錯。報錯的緣由就是,在加載這個方法的過程當中會解析這個update方法的參數,發現參數MenuVo不是簡單類型,則會自動以遞歸的方式解釋它全部的類屬性。這樣就很容易陷入無限遞歸的死循環。

爲了解決這個問題,我目前只是本身寫了一個OperationParameterReader插件實現類以及它依賴的ModelAttributeParameterExpander工具類,經過配置的方式替換掉到srpingfox原來的那兩個類,偷樑換柱般的把參數解析這個邏輯替換掉,並避開無限遞歸。固然,這至關因而一種修改源碼級別的方式。我目前尚未找到解決這個問題的更完美的方法,因此,只能建議你們在用spring-fox Swagger的時候儘可能避免這種無限遞歸的狀況。畢竟,這不符合springmvc命令對像的規範,springmvc參數的命令對像中最好只含有簡單的基本類型屬性。

springfox第二大坑:api分組相關,Docket實例不能延遲加載

springfox默認會把全部api分紅一組,這樣經過相似於http://127.0.0.1:8080/jadDemo/swagger-ui.html這樣的地址訪問時,會在同一個頁面里加載全部api列表。這樣,若是系統稍大一點,api稍微多一點,頁面就會出現假死的狀況,因此頗有必要對api進行分組。api分組,是經過在ApiConf這個配置文件中,經過@Bean註解定義一些Docket實例,網上常見的配置以下:

@EnableWebMvc @EnableSwagger2 public class ApiConfig { @Bean public Docket customDocket() { return new Docket(DocumentationType.SWAGGER_2).apiInfo(apiInfo()); } }

上述代碼中經過@Bean注入一個Docket,這個配置並非必須的,若是沒有這個配置,框架會本身生成一個默認的Docket實例。這個Docket實例的做用就是指定全部它能管理的api的公共信息,好比api版本、做者等等基本信息,以及指定只列出哪些api(經過api地址或註解過濾)。
Docket實例能夠有多個,好比以下代碼:

@EnableWebMvc @EnableSwagger2 public class ApiConfig { @Bean public Docket customDocket1() { return new Docket(DocumentationType.SWAGGER_2) .groupName("apiGroup1").apiInfo(apiInfo()).select() .paths(PathSelectors.ant("/sys/**")); } @Bean public Docket customDocket2() { return new Docket(DocumentationType.SWAGGER_2) .groupName("apiGroup2").apiInfo(apiInfo()) .select() .paths(PathSelectors.ant("/shop/**")); } }

當在項目中配置了多個Docket實例時,也就能夠對api進行分組了,好比上面代碼將api分爲了兩組。在這種狀況下,必須給每一組指定一個不一樣的名稱,好比上面代碼中的"apiGroup1"和"apiGroup2",每一組能夠用paths經過ant風格的地址表達式來指定哪一組管理哪些api。好比上面配置中,第一組管理地址爲/sys/開頭的api第二組管理/shop/開頭的api。固然,還有不少其它的過濾方式,好比跟據類註解、方法註解、地址正則表達式等等。分組後,在api 列表界面右上角的下拉選項中就能夠選擇不一樣的api組。這樣就把項目的api列表分散到不一樣的頁面了。這樣,即方便管理,又不致於頁面因須要加載太多api而假死。
然而,同使用@Configuration同樣,我並不同意使用@Bean來配置Docket實例給api分組。由於這樣,一樣會把代碼寫死。因此,我推薦在xml文件中本身配置Docket實例實現這些相似的功能。固然,考慮到Docket中的衆多屬性,直接配置bean比較麻煩,能夠本身爲Docket寫一個FactoryBean,而後在xml文件中配置FactoryBean就好了。然而將Docket配置到xml中時。又會遇到一個大坑,就那是,spring對bean的加載方式默認是延遲加載的,在xml中直接配置這些Docket實例Bean後。你會發現,沒有一點效果,頁面左上角的下拉列表中跟本沒有你的分組項。
這個問題曾困擾過我好幾個小時,後來憑經驗推測出多是由於sping bean默認延遲加載,這個Docket實例還沒加載到spring context中。實事證實,個人猜想是對的。我不知道這算是springfox的一個bug,仍是由於我跟本不應把對Docket的配置從原來的java代碼中搬到xml配置文件中來。

springfox其它的坑:

springfox還有些其它的坑,好比@ApiOperation註解中,若是不指定httpMethod屬性具體爲某個get或post方法時,api列表中,會它get,post,delete,put等全部方法都列出來,搞到api列表重複的太多,很難看。另外,還有在測試時,遇到登陸權限問題,等等。這一堆堆的比較容易解決的小坑,由於篇幅有限,我就很少說了。還有好比@Api、@ApiOperation及@ApiParam等等註解的用法,網上不少這方面的文檔,我就不重複了。


開源:Swagger Butler 1.1.0發佈,利用ZuulRoute信息簡化配置內容

Swagger Butler是一個基於Swagger與Zuul構建的API文檔聚集工具。經過構建一個簡單的Spring Boot應用,增長一些配置就能將現有整合了Swagger的Web應用的API文檔都彙總到一塊兒,方便查看與測試。

項目地址

快速入門

該工具的時候很是簡單,先經過下面幾步簡單入門:

第一步:構建一個基礎的Spring Boot應用

如您還不知道如何建立Spring Boot應用,能夠先閱讀本篇入門文章

第二步:在pom.xml中引入依賴

<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.5.10.RELEASE</version>
</parent>

<dependencies>
<dependency>
<groupId>com.didispace</groupId>
<artifactId>swagger-butler-core</artifactId>
<version>1.1.0</version>
</dependency>
</dependencies>

第三步:建立應用主類,增長@EnableSwaggerButler註解開啓Swagger Butler功能

@EnableSwaggerButler
@SpringBootApplication
public class StaticApplication {

public static void main(String[] args) {
SpringApplication.run(StaticApplication.class);
}

}

第四步:配置文件中增長Swagger文檔的地址配置

spring.application.name=swagger-butler-example-static
server.port=11000

# default config
swagger.butler.api-docs-path=/v2/api-docs
swagger.butler.swagger-version=2.0

# swagger resource
zuul.routes.user.path=/service-a/**
zuul.routes.user.url=http://localhost:10010/
swagger.butler.resources.user.name=user-service

# swagger resource
zuul.routes.product.path=/service-b/**
zuul.routes.product.url=http://localhost:10020/
swagger.butler.resources.product.name=product-service
swagger.butler.resources.product.api-docs-path=/xxx/v2/api-docs
swagger.butler.resources.product.swagger-version=2.0

上面配置了兩個文檔位置,因爲這裏尚未引入服務發現機制,因此Zuul的路由須要咱們本身配置。而後在配置resource信息的時候,從1.1.0版本開始作了較大的調整,因爲具體的訪問路徑是能夠經過路由信息產生的,因此對於resource的配置信息只關注三個內容:

  • name:API文檔在swagger中展示名稱
  • api-docs-path:要獲取的swagger文檔的具體路徑;若是不配置會使用全局的swagger.butler.api-docs-path配置,默認爲/v2/api-docs。;這裏的配置主要用戶一些特殊狀況,好比服務自身設置了context-path,或者修改了swagger默認的文檔路徑
  • swagger-version:swagger版本信息;若是不配置會使用全局的swagger.butler.swagger-version配置,默認爲2.0

第五步:訪問http://localhost:11000/swagger-ui.html

ExampleExample

代碼示例具體可見swagger-butler-example-static目錄

Zuul的路由與SwaggerResources配置之間的關係

如上示例中<route-name>展現了Zuul的路由名稱與SwaggerResources配置之間的關聯關係

zuul.routes.<route-name>.path=/service-b/**
zuul.routes.<route-name>.url=http://localhost:10020/

swagger.butler.resources.<route-name>.name=product-service
swagger.butler.resources.<route-name>.api-docs-path=/xxx/v2/api-docs
swagger.butler.resources.<route-name>.swagger-version=2.0

注意:在沒有使用自動配置或整合服務治理的時候,要生成Swagger文檔的時候,resources信息中的name屬性是必須配置的,api-docs-pathswagger-version不配置的時候會使用默認的全局配置

全局配置

對於Swagger文檔獲取的全局配置內容,目前主要包含下面幾個參數:

swagger.butler.api-docs-path=/v2/api-docs
swagger.butler.swagger-version=2.0

使用Zuul中的路由自動配置(新特性)

在快速入門示例中咱們配置了兩個路由信息,同時爲這兩個路由信息配置了對應的Swagger信息來獲取API文檔詳情,從1.1.0版本開始,增長了幾個經過Zuul的路由配置來自動生成文檔信息的參數,這樣能夠減小快速入門示例中那些繁瑣的配置。對於快速入門例子,咱們能夠作以下改造:

# swagger resource
zuul.routes.user.path=/service-a/**
zuul.routes.user.url=http://localhost:10010/

# swagger resource
zuul.routes.product.path=/service-b/**
zuul.routes.product.url=http://localhost:10020/

# use zuul routes generate swagger resources
swagger.butler.auto-generate-from-zuul-routes=true

在設置了swagger.butler.auto-generate-from-zuul-routes=true以後會默認的根據zuul中的路由信息來生成SwaggerResource。其中,原來resource中的name會使用zuul route的名稱(好比:上面的user和product),而api-docs-pathswagger-version配置會使用默認的全局配置。若是resource中的三個參數有特殊狀況要處理,能夠採用快速入門中的配置方式來特別指定便可。

忽略某些路由生成

# swagger resource
zuul.routes.user.path=/service-a/**
zuul.routes.user.url=http://localhost:10010/

# swagger resource
zuul.routes.product.path=/service-b/**
zuul.routes.product.url=http://localhost:10020/

# use zuul routes generate swagger resources
swagger.butler.auto-generate-from-zuul-routes=true
swagger.butler.ignore-routes=product

如上示例,經過swagger.butler.ignore-routes參數能夠從當前配置的路由信息中排除某些路由內容不生成文檔,配置內容爲zuul中的路由名稱,配置多個的時候使用,分割。

注意:swagger.butler.ignore-routesswagger.butler.generate-routes不能同時配置。這兩個參數都不配置的時候,默認爲zuul中的全部路由生成文檔。

指定某些路由生成

# swagger resource
zuul.routes.user.path=/service-a/**
zuul.routes.user.url=http://localhost:10010/

# swagger resource
zuul.routes.product.path=/service-b/**
zuul.routes.product.url=http://localhost:10020/

# use zuul routes generate swagger resources
swagger.butler.auto-generate-from-zuul-routes=true
swagger.butler.generate-routes=product

如上示例,經過swagger.butler.generate-routes參數能夠從當前配置的路由信息中指定某些路由內容生成文檔,配置內容爲zuul中的路由名稱,配置多個的時候使用,分割。

注意:swagger.butler.ignore-routesswagger.butler.generate-routes不能同時配置。這兩個參數都不配置的時候,默認爲zuul中的全部路由生成文檔。

與服務治理整合

與eureka整合

在整合eureka獲取全部該註冊中心下的API文檔時,只須要在上面工程的基礎上增長下面的配置:

第一步pom.xml中增長eureka依賴,好比:

<dependencies>
<dependency>
<groupId>com.didispace</groupId>
<artifactId>swagger-butler-core</artifactId>
<version>1.1.0</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka</artifactId>
<version>1.3.2.RELEASE</version>
</dependency>
</dependencies>

第二步:應用主類增長@EnableDiscoveryClient,好比:

@EnableDiscoveryClient
@EnableSwaggerButler
@SpringBootApplication
public class EurekaApplication {

public static void main(String[] args) {
SpringApplication.run(EurekaApplication.class);
}

}

第三步:修改配置文件,增長eureka的配置,好比:

spring.application.name=swagger-butler-example-eureka
server.port=11001

eureka.client.service-url.defaultZone=http://eureka.didispace.com/eureka/

swagger.butler.auto-generate-from-zuul-routes=true
swagger.butler.generate-routes=swagger-service-a, swagger-service-b

swagger.butler.resources.swagger-service-b.api-docs-path=/xxx/v2/api-docs

因爲整合了eureka以後,zuul會默認爲全部註冊服務建立路由配置(默認的路由名爲服務名),因此只須要經過swagger.butler.auto-generate-from-zuul-routes=true參數開啓根據路由信息生成文檔配置的功能,配合swagger.butler.ignore-routesswagger.butler.generate-routes參數就能夠指定要生成的範圍了,若是某些服務須要特殊配置,也能夠經過wagger.butler.resources.*的配置來覆蓋默認設置,好比上面的swagger.butler.resources.swagger-service-b.api-docs-path=/xxx/v2/api-docs指定了swagger-service-b服務獲取swagger文檔的請求路徑爲/xxx/v2/api-docs

代碼示例具體可見swagger-butler-example-eureka目錄

與consul整合

在整合eureka獲取全部該註冊中心下的API文檔時,只須要在上面工程的基礎上增長下面的配置:

第一步pom.xml中增長consul依賴,好比:

<dependencies>
<dependency>
<groupId>com.didispace</groupId>
<artifactId>swagger-butler-core</artifactId>
<version>1.1.0</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-consul-discovery</artifactId>
<version>1.3.2.RELEASE</version>
</dependency>
</dependencies>

第二步:應用主類增長@EnableDiscoveryClient,好比:

@EnableDiscoveryClient
@EnableSwaggerButler
@SpringBootApplication
public class EurekaApplication {

public static void main(String[] args) {
SpringApplication.run(EurekaApplication.class);
}

}

第三步:配置文件中增長eureka的配置,好比:

spring.application.name=swagger-butler-example-consul
server.port=11002

spring.cloud.consul.host=localhost
spring.cloud.consul.port=8500

swagger.butler.auto-generate-from-zuul-routes=true
swagger.butler.generate-routes=swagger-service-a, swagger-service-b

swagger.butler.resources.swagger-service-b.api-docs-path=/xxx/v2/api-docs

這裏除了consul自身的配置以外,其餘內容與整合eureka時候的是同樣的。

代碼示例具體可見swagger-butler-example-consul目錄

相關文章
相關標籤/搜索